How to know a sprite position inside a view, relative to window? - c++

I have this sprite of a car that moves with varied speed.
It is inside a view and the view is moved to the left to keep the car always in the center of the window.
The view accompanies the displacement of the car, ie it is shifted to the left as the car accelerates or brakes.
This way the car will always appear in the center.
But if for example it is overtaken by another car, it will be left behind.
For it not to disappear from the window, I have to zoom in the view so that all the cars appear.
But for this, I need to know the position of the car in relation to the window (not in relation to the view).
getGlobalBounds().left or getPosition().x show the same value, which is the position relative to the view, not relative to the window, as shown in the image.
How to know a sprite position inside a view, relative to window?

After several hours of research, I finally find the easy way of achieve this. And yes, it was ridiculously easy.
But first, I would like to clear up some misconceptions.
getGlobalBounds().left or getPosition().x show the same value,
which is the position relative to the view, not relative to the
window, as shown in the image.
In fact, those methods return the position in the world, not in the view nor in the window.
You can have, for instance, a 500x500 window, with a 400x400 view, in a 10000x10000 world. You can place things in the world, outside of the view or the window. When the world is rendered, then the transformations of the view (translations, rotations, zoom, ...) are applied to the world and things are finally shown in the window.
To know where a coordinate in the world is represented in the window (or any other RenderTarget) and vice versa, SFML actually have a couple of functions:
RenderTarget.mapCoordsToPixel(Vector2f point)
Given a point in the world gives you the corresponding point in the RenderTarget.
RenderTarget.mapPixelToCoords(Vector2f point)
Given a point in the RenderTarget gives you the corresponding point in the world. (this is useful to map mouse clicks to corresponding points in your world)
Result
Code
int main()
{
RenderWindow window({ 500, 500 }, "SFML Views", Style::Close);
sf::View camera(sf::FloatRect(0, 0, window.getSize().x, window.getSize().y));
sf::Vector2f orig(window.getSize().x / 2, window.getSize().y / 2);
camera.setCenter(orig);
sf::Font f;
f.loadFromFile("C:/Windows/Fonts/Arial.ttf");
sf::Text t;
t.setFont(f);
sf::RectangleShape r;
r.setPosition(10, 10);
r.setSize(sf::Vector2f(20, 20));
r.setOutlineColor(sf::Color::Blue);
r.setFillColor(sf::Color::Blue);
t.setPosition(10, 40);
while (window.isOpen())
{
for (Event event; window.pollEvent(event);)
if (event.type == Event::Closed)
window.close();
else if (event.type == Event::KeyPressed){
camera.move(-3, 0);
camera.rotate(5.0);
camera.zoom(1.1);
}
auto realPos = window.mapCoordsToPixel(r.getPosition());
std::string str = "Pos: (" + std::to_string(realPos.x) +","+ std::to_string(realPos.y) + ")";
t.setString(str);
window.clear();
window.setView(camera);
window.draw(r);
window.draw(t);
window.display();
}
return EXIT_SUCCESS;
}

Related

How do I capture(trap) a mouse in a window in c++?

I am writing a tile map editor in SFML and C++. I have been having all sorts of troubles with the mouse. I am using the built in SFML Mouse:: static functions and recently managed to get a custom cursor moving on the screen and pointing accurately to a tile by doing as follows:`
Sprite cursor;
bool focus = false;
RenderWindow window(VideoMode(512, 288), "Tilemap editor");
window.setFramerateLimit(60);
Texture cursorTexture;
if(!cursorTexture.loadFromFile("Graphics/Cursor.png")) {
std::cout << "Failed to load cursor texture\n";
return 0;
}
cursor.setTexture(cursorTexture);
Mouse::setPosition(mousePos);
While(window.isOpen()) {
window.setMouseCursorVisible(focus);
if(Mouse::getPosition().x != lastMousePos.x) {
mousePos.x = mousePos.x + (Mouse::getPosition().x - lastMousePos.x);
}
if(Mouse::getPosition().y != lastMousePos.y) {
mousePos.y = mousePos.y + (Mouse::getPosition().y - lastMousePos.y);
}
cursor.setPosition(mousePos.x, mousePos.y);
lastMousePos = Mouse::getPosition();
window.clear();
window.draw(cursor)
window.display()
}
The built-in Mouse functions only display relativity to the desktop or the window and as I am using this app in a small window in which my view moves, I can't use either. The solution above moves a cursor independent of the desktop and with the ability to move the cursor if and when I want to move my view.
The issue is that my mouse will move off the side of the app when I try to click items in the top left corner.
Is there a good cross-platform (I'm on Linux BTW) way to trap the mouse inside of the window unless I enter a keystroke (like a VM window)? Also, is there a better way to do cross-platform mouse support in general? SFML kinda sucks. (Code obviously needs to be in a main function and the namespace must be sf with SFML/Graphics.hpp included)
There is already a method for that.
void setMouseCursorGrabbed (bool grabbed)
// Grab or release the mouse cursor.
You can also use these methods to convert your screen coordinates to mouse coordinates and vice versa.
Vector2f mapPixelToCoords (const Vector2i &point) const
// Convert a point from target coordinates to world coordinates, using the current view.
Vector2f mapPixelToCoords (const Vector2i &point, const View &view) const
// Convert a point from target coordinates to world coordinates.
Vector2i mapCoordsToPixel (const Vector2f &point) const
// Convert a point from world coordinates to target coordinates, using the current view.
Vector2i mapCoordsToPixel (const Vector2f &point, const View &view) const
// Convert a point from world coordinates to target coordinates.
sf::RenderWindow Class Reference

Change Cursor Image While Hovering Over An Object Drawn At OpenGL And Displaying Object's Variables

I am currently working on a project that I use C++, OpenGL, Qt 5.9.2 and Microsoft Visual Studio Professional 2015 on a 64 bit Operating System, Windows 10 Pro.
I have a user interface that I have created and in that user interface, there is a QGLWidget, that I am using for draw processes, with other widgets like push buttons and a dockWidget. I have a class Ball and it has variables(distance(double) and angle(int)) that determines where an instance of that Ball is going to be drawn inside the QGLWidget. Ball class has got 2 more variables that is, id(int), model(String) and year(int) Every other draw process draws lines except Ball drawings.
Drawings are 2 dimensional.
Every Ball has the same color(rgb)!
First problem: I want to left click to one of the Ball instances and I want to display it's id, model and year at The dockWidget.
Second Problem: While doing the stuff that I have mentioned at the First Problem section. I want the cursor image to change while hovering above any of the Ball instances, and change back to default Windows mouse cursor while not.
I have created a function that checks if the MouseEvent is LeftClick:
void DisplayManager::mousePressEvent(QMouseEvent* ev) {
if (ev->buttons() & Qt::LeftButton) { // Balls Are Green
if(// CHECK IF THERE IS A BALL AT THE CLICKED COORDINATES) {
// DISPLAY THE X and Y OF THE BALL AT THE DOCK WIDGET
}
}
}
This is my initializeGL function: (DisplayManager is the name of my QGLWidget)
void DisplayManager::initializeGL() {
glEnable(GL_COLOR_MATERIAL); // Enables the changing of the draw color with glColor() functions
glColor3f(0.0, 1.0, 0.0);
glEnable(GL_DEPTH_TEST);
glClearColor(0, 0, 0, 1); //sets a black background 1 0 0 1
}
On the basis this is a Picking problem and there are several information about it at the internet but I am not using GLUT and I am not using any shader. So in the light of all these I was unable to find any effective solution or clue about how can I accomplish all that I want.
I would be really glad if someone could help me with at least one of these problems.
I have currently finished working with the project. I thought that I should provide an answer to my question in case someone with a similar problem comes across with my question in the future.
void DisplayManager::mousePressEvent(QMouseEvent* ev) {
// The processes that are being executed after a left mouse button click on the DisplayManager.
if (ev->buttons() & Qt::LeftButton) {
double aspectRatio = openGLWidgetWidth / openGLWidgetHeight;
int xOfClicked = ev->x() - (openGLWidgetWidth / 2);
int yOfClicked = - (ev->y() - (openGLWidgetHeight / 2));
// The variable to be used for calculating fault tolerance of the click event.
int distanceBetweenPressAndDraw = 0;
// Executes the processes inside for every ball in vector.
for (int i = 0; i < ballVector.length(); i++) {
// Calculates the screen coordinates of the i'th ball.
int rangeOfBallInScreenDistance = rangeToScreenDistance(ballVector.at(i).range);
double screenXOfBall = rangeOfBallInScreenDistance * sin(ballVector.at(i).degree * DEGTORAD);
double screenYOfBall = rangeOfBallInScreenDistance * cos(ballVector.at(i).degree * DEGTORAD);
// Calculates the distance between pressed position and the i'th ball according to the screen coordinates.
distanceBetweenPressAndDraw = sqrt(pow((screenXOfBall - xOfClicked), 2) + pow((screenYOfBall - yOfClicked), 2));
// Decides if the clicked position is a ball (considering the fault tolerance).
if (distanceBetweenPressAndDraw < 10) {
emit printXY(QPointF(xOfClicked, yOfClicked)); // Prints the screen coordinates of the clicked positions (At the Dock Widget inside Main Window).
}
}
}
}
This was the solution for my First Problem. I would be glad though if someone could answer my Second problem in a comment or answer somehow.

Why the texture appears only in the first quadrant

What's wrong with this code using SFML?
In the code below, I have this image (1000x1000) and I want to show it in a window (500x500) using sf::RenderTexture.
However, only part of the image appears in the first quadrant:
#include <SFML/Graphics.hpp>
using namespace sf;
int main()
{
RenderWindow window({500, 500}, "SFML Views", Style::Close);
View camera;
camera.setSize(Vector2f(window.getSize()));
Texture background;
background.loadFromFile("numeros.png");
Sprite numeros (background);
RenderTexture texture;
texture.create(window.getSize().x, window.getSize().y);
Sprite content;
content.setTexture(texture.getTexture());
texture.draw(numeros);
texture.display();
while (window.isOpen())
{
for (Event event; window.pollEvent(event);)
if (event.type == Event::Closed)
window.close();
window.clear();
window.setView(camera);
window.draw(content);
window.display();
}
return EXIT_SUCCESS;
}
As far as I can understand, the code should generate the original image (1000x1000) automatically adjusted to 500x500.
Could anyone tell you what is wrong?
You're facing, in fact, two distinct problems:
First one:
As far as I can understand, the code should generate the original
image (1000x1000) automatically adjusted to 500x500.
This is not really true. SFML handles the sprites with the real size of the texture. If your image is 1000x1000, but you want representing it as 500x500, you should assign the texture to a sprite, as you do:
Sprite numeros(background);
and then scale this sprite to fit in a 500x500 window, this is:
numeros.setScale(0.5, 0.5);
With this change you should view the whole image, but...
Second one:
You're messing with the view of the window. If we check SFML documentation, we can see that sf::View expects:
A sf::FloatRect: this is, a coordinate (x,y) - in this case the top-left corner - and a size(width, height)
or
Two sf::Vector2f: one corresponding to the coordinates of the center and the other corresponding to the size of the view.
Assuming you want to use the second one, you're missing the first parameter, the center coordinates, but this is not really necessary. If you simply don't apply the view, the image should be shown in the whole window.
So you simply need to remove:
window.setView(camera);
The code I've tried:
int main()
{
RenderWindow window({ 500, 500 }, "SFML Views", Style::Close);
View camera;
camera.setSize(Vector2f(window.getSize()));
Texture background;
background.loadFromFile("numeros.png");
Sprite numeros(background);
numeros.setScale(0.5, 0.5); // <-- Add this
RenderTexture texture;
texture.create(window.getSize().x, window.getSize().y);
Sprite content;
content.setTexture(texture.getTexture());
texture.draw(numeros);
texture.display();
while (window.isOpen())
{
for (Event event; window.pollEvent(event);)
if (event.type == Event::Closed)
window.close();
window.clear();
//window.setView(camera); <-- Remove this
window.draw(content);
window.display();
}
return EXIT_SUCCESS;
}
And my result:
Just to add another option to #alseether 's excellent response, I realized that the whole issue consisted of that bad View initialization.
This way you can also set the size of the view = to the size of the background image (1000,1000) and finally set the center of the view to the windows's upper left corner.
As the view is larger than the window size (500,500) it will automatically be adjusted to this new size.
In short, the section to be changed would be:
View camera;
camera.setSize(Vector2f(background.getSize().x, background.getSize().y));
camera.setCenter(Vector2f(window.getSize()));

SFML Views setCenter vs rotation

I have a view with same dimensions of original window (500,300)
I apply view.zoom(2) to leave the view at half the size.
Now the view is centered. I want to move the view to the upper left corner of the original window. So I put view.setCenter(500,300);
The view is now correctly positioned in the upper corner of the original window. But now I want to rotate the view, making the center of the view its own top left corner, ie (0,0): view.setRotation(5);
As you can see, the center of the axis of rotation should be 0.0 but not respected.
The problem is that if I do view.setCenter (0,0), the whole view returns to the middle of the original window.
How to solve this?
Instead of using view.setCenter(500,300); move it via view.move(x_offset, y_offset);. Then applying setCenter(...) won't redefine the center and it won't get reset.
I recommend consulting the API reference of View for further reading.
You might also be interested in void sf::View::setViewport(const FloatRect& viewport) or void sf::View::reset(const FloatRect& rectangle).
This code, kindly provided by Geheim, solves the problem and also teaches a more practical approach to SFML.
#include <SFML/Graphics.hpp>
int main()
{
sf::RenderWindow window({500, 300}, "SFML Views", sf::Style::Close);
window.setFramerateLimit(120);
bool useViewPort {true}; // change this to false to see the other version
sf::View camera {{0, 0}, static_cast<sf::Vector2f>(window.getSize())};
if (useViewPort)
{
camera.setViewport({-0.5f, -0.5f, 1, 1});
camera.rotate(5);
}
else
camera.setCenter(camera.getSize());
camera.zoom(2);
window.setView(camera);
sf::RectangleShape background {camera.getSize()};
sf::RectangleShape square {{50, 50}};
square.setFillColor(sf::Color::Red);
sf::RenderTexture texture;
texture.create(window.getSize().x, window.getSize().y);
while (window.isOpen())
{
for (sf::Event event; window.pollEvent(event);)
if (event.type == sf::Event::Closed)
window.close();
window.clear();
if (useViewPort)
{
window.draw(background);
window.draw(square);
}
else
{
texture.clear();
texture.draw(background);
texture.draw(square);
texture.display();
sf::Sprite content {texture.getTexture()};
content.rotate(-5); // you have to rotate in the other disquareion here, do you know why?
window.draw(content);
}
window.display();
}
return EXIT_SUCCESS;
}
I'm glad you got the result you wanted by applying viewports using Geheim's code.
However, if you don't want to be using viewports to clip areas of the window and such, you can still rotate a view around a specific point other than its centre. You just need a little bit of mathematics...
Take the different between the target point (in the view's co-ordinate system) and the view's centre and rotate that point by the amount you wish to rotate the view and around the view's centre. Then, calculate the difference between those points (the target point and the rotated point). Once you have this difference, simply rotate the view (around its centre as normal) but then move the view by that difference.
It might sound complicated so you might want to just use this free function that I made that does it all automatically; it's on the SFML Wiki:
RotateViewAt

Centering Shapes/Objects in SFML for C++

So recently, I have begun using SFML to make games in Visual Studio.
After setting everything up, and writing some sample code, I devised this:
#include <SFML/Graphics.hpp>
int main()
{
sf::RenderWindow window(sf::VideoMode(600, 600), "Move the Shape");
sf::CircleShape shape(100.f);
shape.setFillColor(sf::Color::Green);
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
window.clear();
window.draw(shape);
window.display();
}
return 0;
}
The program produces the following result:
How do I place the circle in the center? I want to set up some code after that lets the user move the circle with the keyboard's arrow keys, so I need the circle in the center.
You need to set the position of shape with shape.setPosition(x, y). You know the width and height of the window (600px each way), and you know the radius of the circle (100px), so you can calculate the x and y that the circle needs to be moved to be centered. I'll leave that as an exercise for you.
You may also want to consider setting the origin of your circle so that you can position it by its center point (see setOrigin).
Actually, I've answered my own question. To set the position of an item, write:
shape.setPosition(x, y);
If you want to center a circle you can do something like this
circle.setPosition((window.getSize().x / 2.f) - circle.getRadius(), (window.getSize().y / 2.f) - circle.getRadius());
First, you should set the origin of the circle in the middle of it:
circle.setOrigin( circle.getRadius() / 2 , circle.getRadius() / 2 );
The, just move the center of the circle in the middle of the scree:
circle.setPosition( window.getSize().x / 2 , window.getSize().y / 2 );