0

I am building a basic program in C++ using SFML, which allows the user to zoom in to an object and drag the screen around. I have been able to make the screen move however the movement is quite out of sync with the mouse and isn't smooth. Does anyone have any suggestions as to how i could rewrite my code so that the screen moves properly with the mouse?

        Time time = milliseconds(5);
        x1 = Mouse::getPosition().x;
        y1 = Mouse::getPosition().y;

        if (Mouse::isButtonPressed(Mouse::Left))
        {
            std::cout << x1 - Mouse::getPosition().x << " " << y1 - Mouse::getPosition().y << std::endl;
            sleep(time);
            view.move((x1 - Mouse::getPosition().x)*2, (y1 - Mouse::getPosition().y)*2);
        }
billy606
  • 71
  • 2
  • 7

5 Answers5

3

There are multiple different approaches to actually achieve this. The most straightforward one is probably to simply update your window's view using sf::Window::setView().

You could do something like the following example. Try to understand the code rather than just copying it.

#include <SFML/Graphics.hpp>

int main()
{
    // Let's setup a window
    sf::RenderWindow window(sf::VideoMode(640, 480), "SFML View Transformation");

    // Create something simple to draw
    sf::Texture texture;
    texture.loadFromFile("background.jpg");
    sf::Sprite background(texture);

    sf::Vector2f oldPos;
    bool moving = false;

    float zoom = 1;

    // Retrieve the window's default view
    sf::View view = window.getDefaultView();

    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            switch (event.type) {
                case sf::Event::Closed:
                    window.close();
                    break;
                case sf::Event::MouseButtonPressed:
                    // Mouse button is pressed, get the position and set moving as active
                    if (event.mouseButton.button == 0) {
                        moving = true;
                        oldPos = window.mapPixelToCoords(sf::Vector2i(event.mouseButton.x, event.mouseButton.y));
                    }
                    break;
                case  sf::Event::MouseButtonReleased:
                    // Mouse button is released, no longer move
                    if (event.mouseButton.button == 0) {
                        moving = false;
                    }
                    break;
                case sf::Event::MouseMoved:
                    {
                        // Ignore mouse movement unless a button is pressed (see above)
                        if (!moving)
                            break;
                        // Determine the new position in world coordinates
                        const sf::Vector2f newPos = window.mapPixelToCoords(sf::Vector2i(event.mouseMove.x, event.mouseMove.y));
                        // Determine how the cursor has moved
                        // Swap these to invert the movement direction
                        const sf::Vector2f deltaPos = oldPos - newPos;

                        // Move our view accordingly and update the window
                        view.setCenter(view.getCenter() + deltaPos);
                        window.setView(view);

                        // Save the new position as the old one
                        // We're recalculating this, since we've changed the view
                        oldPos = window.mapPixelToCoords(sf::Vector2i(event.mouseMove.x, event.mouseMove.y));
                        break;
                    }
                case sf::Event::MouseWheelScrolled:
                    // Ignore the mouse wheel unless we're not moving
                    if (moving)
                        break;

                    // Determine the scroll direction and adjust the zoom level
                    // Again, you can swap these to invert the direction
                    if (event.mouseWheelScroll.delta <= -1)
                        zoom = std::min(2.f, zoom + .1f);
                    else if (event.mouseWheelScroll.delta >= 1)
                        zoom = std::max(.5f, zoom - .1f);

                    // Update our view
                    view.setSize(window.getDefaultView().getSize()); // Reset the size
                    view.zoom(zoom); // Apply the zoom level (this transforms the view)
                    window.setView(view);
                    break;
            }
        }

        // Draw our simple scene
        window.clear(sf::Color::White);
        window.draw(background);
        window.display();
    }
}

Also note that the code will just zoom in/out, it won't zoom towards your cursor position, but that should be trivial to add (basically just move the view similar to what I've done for mouse movement).

Mario
  • 35,726
  • 5
  • 62
  • 78
  • Just to note: The actual value reported as your scroll wheel `delta` can be driver and platform dependent and might require some testing until you think it's feeling right (and working everywhere as expected). – Mario Jan 22 '17 at 09:45
  • Hi Mario thanks for your detailed response. I have never used mapPixelToCoords(). Could you briefly explain what the function does or direct me to some detailed documentation on it. – billy606 Jan 22 '17 at 11:07
  • Haha stupid me Lorence Hernandez just answered that. – billy606 Jan 22 '17 at 11:18
1

I've been trying something similar and I'd like to improve @Mario's answer.

In fact, the use of mapPixelToCoords ends being somehow expensive. This is something weird, because mapPixelToCoords only applies some matrices operations to the coordinates, but the thing is when I use mapPixelToCoords, my image shakes like crazy when the zoom wasn't the original one.

In my case, I prefer to keep the accumulated zoom and multiply that deltaPos by the zoom level.

Changes from @Mario's code

New Variable accumZoom:
double accumZoom = 1;
Button Pressed Event:
case sf::Event::MouseButtonPressed:
    // Mouse button is pressed, get the position and set moving as active
    if (event.mouseButton.button == 0) {
        moving = true;
        oldPos = sf::Vector2f(sf::Mouse::getPosition(window)); // No call to mapPixelToCoords
    }
    break;
Mouse Moved Event:
case sf::Event::MouseMoved:
{
    // Ignore mouse movement unless a button is pressed (see above)
    if (!moving)
        break;
    // Determine the new position in world coordinates
    const sf::Vector2f newPos = sf::Vector2f(event.mouseMove.x, event.mouseMove.y);
    // Determine how the cursor has moved
    // Swap these to invert the movement direction
    const sf::Vector2f deltaPos = oldPos - newPos;

    // Applying zoom "reduction" (or "augmentation")
    deltaPos.x *= accumZoom;
    deltaPos.y *= accumZoom;

    // Move our view accordingly and update the window
    view.move(deltaPos); // <-- Here I use move
    window.setView(view);

    // Save the new position as the old one
    // We're recalculating this, since we've changed the view
    oldPos = newPos; // With move, I don't need to recalculate
    break;
}
Mouse Wheel Scrolled Event:
case sf::Event::MouseWheelScrolled:
    // Ignore the mouse wheel unless we're not moving
    if (moving)
        break;

    // Determine the scroll direction and adjust the zoom level
    // Again, you can swap these to invert the direction
    if (event.mouseWheelScroll.delta <= -1)
        zoom = std::min(2.f, zoom + .1f);
    else if (event.mouseWheelScroll.delta >= 1)
        zoom = std::max(.5f, zoom - .1f);

    accumZoom *= zoom; // <-- accumulate zoom
    // Update our view
    view.setSize(window.getDefaultView().getSize()); // Reset the size
    view.zoom(zoom); // Apply the zoom level (this transforms the view)
    window.setView(view);
    break;
alseether
  • 1,889
  • 2
  • 24
  • 39
  • think I just ran into this. the reason its shaking so crazily is because the movement of the view causes the mouse's Coordinates to move, which causes the view to move, which causes the mouses' Coordinates move, etc... – robotic.laugh Mar 19 '21 at 03:18
0

(replying with an answer instead of a comment because I don't have enough reputation)

Have you tried removing the sleep and cout? I'm pretty sure these two are causing it

user3881815
  • 103
  • 7
  • The cout being there or not being there doesnt really affect much. Ive put the sleep there because i need to get initial mouse coord and final mouse coords then subtract them to get an offset. Without the sleep the offset would be 0. – billy606 Jan 22 '17 at 08:46
  • If you want I can post my full code so you can get a better idea of what im trying to do. – billy606 Jan 22 '17 at 08:47
0

When you use a custom view, or when you resize the window, pixels displayed on the target no longer match units in the 2D world. For example, clicking on pixel (10, 50) may hit the point (26.5, -84) of your world. You end up having to use a conversion function to map your pixel coordinates to world coordinates: mapPixelToCoords.

// get the current mouse position in the window
sf::Vector2i pixelPos = sf::Mouse::getPosition(window);

// convert it to world coordinates
sf::Vector2f worldPos = window.mapPixelToCoords(pixelPos);

By default, mapPixelToCoords uses the current view. If you want to convert the coordinates using view which is not the active one, you can pass it as an additional argument to the function.

The opposite, converting world coordinates to pixel coordinates, is also possible with the mapCoordsToPixel function.

you can see it here at the bottom of the page: https://www.sfml-dev.org/tutorials/2.0/graphics-view.php

Lorence Hernandez
  • 1,189
  • 12
  • 23
0

as user3881815 suggest the problem is with your sleep(time) because it will not guarantee that will sleep for time [ms]. Instead any count of OS scheduling granularity interval can be added causing the choppyness. Not to mention it will block execution ... Also you are not moving by the mouse change but by some ratio instead (so no surprise it is out of sync). So how to get rid of it?

You need to remember last mouse position and use that every run in your processing loop. For example:

double mx=0,my=0,mx0=0,my0=0; // globals with actual and last mouse position

// here your main loop or event or whatever
for (bool exit=false;!exit;)
 {
 // here your other stuff

 // mouse handler
 mx0=mx; mx = Mouse::getPosition().x;
 my0=my; my = Mouse::getPosition().y;
 // here convert mx,my to coordinate system the view.move needs
 if (Mouse::isButtonPressed(Mouse::Left)) view.move(mx-mx0,my-my0);
 }

If your view uses zoom then you most likely need to apply coordinate transform to the mx,my but that depends in what the view.move wants.

I do not code with SFML so I have no experience with your view but you can try

view.move((mx-mx0)*view.zoom,(my-my0)*view.zoom);

or

view.move((mx-mx0)/view.zoom,(my-my0)/view.zoom);

To make the movement in sync with your mouse where view.zoom is the scale of your view. Which of the two depends on the view notation.

To better handle the mouse you should also have last and actual state of the buttons so you can handle mousedown,mouseup,mousemove events. For more info see:

Community
  • 1
  • 1
Spektre
  • 49,595
  • 11
  • 110
  • 380