SFML community forums

General => Feature requests => Topic started by: TheMaskedFox on October 10, 2012, 03:41:22 am

Title: Relative Mouse Movement (Mouse Capture)
Post by: TheMaskedFox on October 10, 2012, 03:41:22 am
This proposal stems from an issue raised on the French forum.

http://fr.sfml-dev.org/forums/index.php?topic=9075.msg62994#msg62994 (http://fr.sfml-dev.org/forums/index.php?topic=9075.msg62994#msg62994)

For first-person games with mouse control, developers need relative mouse movement, as opposed to absolute coordinates.  SFML needs either a window style or a window method that locks the mouse, hides the cursor, and sends sf::Event::MouseMove events containing relative x and y updates.

Windows, X11, and OS X will all accomplish this differently, so this feature could take some time to fully implement.  It's critical for providing mouse support in an FPS though, so it would be well worth the time spent.

To accomplish this on OS X, the mouse cursor has to be hidden, the mouse has to be disconnected from the cursor, and the mouse delta has to be polled by the application for changes.  I've posted a sort of "starter kit" for accomplishing this over on Github (https://github.com/SFML/SFML/issues/290#issuecomment-9279071 (https://github.com/SFML/SFML/issues/290#issuecomment-9279071)).

It's been so long since I did any WinAPI or X11 development that I don't have immediate suggestions for how to accomplish mouse locking on those platforms.  Relative mouse movement is a feature common to many games, but its implementation is platform specific.  It's the kind of input abstraction that belongs in SFML.

Developers would either call something like window.captureMouse(), or it would be passed as an option on window creation with a style like sf::Style::CaptureMouse.  In either case, they'd receive a window that has essentially "captured" the mouse, locking the cursor down and hiding it so that all mouse movements are reported as some distance from zero.  Anytime the mouse stops moving, it automatically returns to zero, and any further movement is again reported as some distance from zero.

As an example, on the X axis a reading of -5 would mean tell the game that the user moved the mouse slightly to the left.  Another reading of -35 would mean the user moved the mouse to the left faster.  A subsequent reading of 100 would indicate that the user has snapped the mouse quickly to the right.  This is a rough example, but illustrates how relative mouse movements are helpful.
Title: Re: Relative Mouse Movement (Mouse Capture)
Post by: model76 on October 10, 2012, 05:11:14 am
This is possible with the current API. Here is how to do it:

Set the mouse cursor's position to the center of the screen. Whenever the mouse is moved, simply calculate the offset and reset cursor's position to the center of the screen.
Title: Re: Relative Mouse Movement (Mouse Capture)
Post by: eXpl0it3r on October 10, 2012, 09:48:50 am
Yes it's possible to some extend, but the mouse doesn't really get locked, thus if you move it fast in window mode or with two screens the cursor will jump out of the window. Now the big problem with that is when you click, in the instance the mouse is outside the window, the SFML window will lose focus and on fullscreen mode it might change resolutions and switch back to the desktop, which makes it kind of impossible to create a FPS.

Instead of delta movement I'd rather request for a mouse lock style/function. ;)
Title: Re: Relative Mouse Movement (Mouse Capture)
Post by: model76 on October 10, 2012, 01:22:57 pm
Now the big problem with that is when you click, in the instance the mouse is outside the window
That should not be possible with the method I described.
Title: Re: Relative Mouse Movement (Mouse Capture)
Post by: eXpl0it3r on October 10, 2012, 01:56:10 pm
In theory with unlimited FPS count yes, but if the FPS is around 60 you can easily move the mouse faster than the width/height of the window.
Title: Re: Relative Mouse Movement (Mouse Capture)
Post by: model76 on October 10, 2012, 03:36:59 pm
I suppose if you were able to move the cursor outside the window and also produce a mouse down event, within the same update cycle, maybe a problem could arise.
But this is all very theoretical, and a lot of ifs and maybes, so I would like to know if you are actually able to produce an error like this in practice?

I have successfully used this method in the past with no problems, but it was a long time ago, and I think with SFML 1.x.
Title: Re: Relative Mouse Movement (Mouse Capture)
Post by: eXpl0it3r on October 10, 2012, 05:11:11 pm
I suppose if you were able to move the cursor outside the window and also produce a mouse down event, within the same update cycle, maybe a problem could arise.
This has nothing to do with events. Events are bound to the window but the cursor isn't. ;)

Well it's kind of hard to get a nice demo since the recording does heavily influence the performance but I've made a demo anyways (the code is below):
http://www.youtube.com/watch?v=MH_Zm8kp-IQ

#include <SFML/Graphics.hpp>
#include <iostream>
#include <sstream>

int main ( int argc, char** argv )
{
    sf::RenderWindow window(sf::VideoMode(800, 600), "SFML thread example");
    window.setFramerateLimit(120); // Limited FPS

    sf::Clock clock;
    sf::Time dt;

    sf::Font font;
    font.loadFromFile("C:/Windows/Fonts/Arial.ttf");
    sf::Text text("0", font);

    sf::Vector2i deltas;
    sf::Vector2i fixed(window.getSize());

    while (window.isOpen())
    {
        dt = clock.restart();

        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                window.close();

            else if (event.type == sf::Event::LostFocus)
                std::cout << "Lost focus D:" << std::endl;
        }

        deltas = fixed-sf::Mouse::getPosition();
        if(deltas != sf::Vector2i(0, 0))
            sf::Mouse::setPosition(fixed);

        std::stringstream ss;
        ss << 1/dt.asSeconds();
        text.setString(ss.str());

        window.clear();
        window.draw(text);
        window.display();
    }

    return 0;
}
 

For the recording I used 120 fps in the limiter but it also drops down further while recording. You can try the code yourself and if you don't want to you just need to belief me that this is nothing 'theoretical'. ;)
Also the behavior is quite logical, since we limit the FPS the application isn't able to capture everything what's happening (sf::sleep(x) is called = the thread sleeps for x milliseconds), thus he doesn't notice the mouse movement until he 'wakes' up again.
Now one could argue and say that you shouldn't limit the fps then. This will probably work for a while but has two flaws: a) The CPU will be unnecessarily maxed out b) if the application's logic & rendering get more time costly the fps will get limited automatically and you'd miss out on some stuff that happens.
So I conclude that the only way to really make sure the mouse gets reset fast enough is having one thread the runs at it's maximum speed and constantly checks if the mouse position has changed.
Title: Re: Relative Mouse Movement (Mouse Capture)
Post by: model76 on October 10, 2012, 08:36:18 pm
This has nothing to do with events. Events are bound to the window but the cursor isn't. ;)
Well surely, you would have to cause an event somewhere in order for the window to loose focus, would you not? ;)

I implemented the solution myself, and it seems that 2 things have changed since SFML 1.x:

#include <SFML/Graphics.hpp>

int main()
{
    sf::RenderWindow window( sf::VideoMode( 800, 600 ), "SFML" );
    window.setVerticalSyncEnabled( true );

    const sf::Vector2i windowCenter( 400, 300 );

    // Set the mouse cursor's position to the center of the screen.
    sf::Mouse::setPosition( windowCenter, window );

    while( window.isOpen() )
    {
        sf::Event event;
        while( window.pollEvent( event ) )
        {
            if( event.type == sf::Event::Closed )
                window.close();
            else if( ( event.type == sf::Event::MouseLeft || event.type == sf::Event::MouseMoved ) &&
                     sf::Mouse::getPosition( window ) != windowCenter )
            {
                // Whenever the mouse is moved or leaves the window, simply calculate the offset
                // and reset cursor's position to the center of the screen.
                // For this test I am not going to calculate the offset.
                sf::Mouse::setPosition( windowCenter, window );
            }
        }

        window.clear();
        window.display();
    }
}
 

Can you make the window loose focus with the mouse with this solution? If you can, then perhaps Laurent should take a look at it.
Title: Re: Relative Mouse Movement (Mouse Capture)
Post by: eXpl0it3r on October 10, 2012, 09:03:18 pm
Well surely, you would have to cause an event somewhere in order for the window to loose focus, would you not? ;)
Sure but like you said somewhere and that doesn't have anything to do with SFML. ;)

sf::Mouse::setPosition now causes an sf::EventMouseMoved. To get around that, check if the mouse cursor is already in the center of the window before setting it. Otherwise you will have an endless loop.
Wrong sf::Mouse and the window event system don't have anything in common. Note events are triggered by the OS and sent to the window, while sf::Mouse access the device itself. ;)

Moving the mouse cursor outside the window will cause the SFML window to stop producing MouseMoved events. To get around this one, simply also check for sf::Event::MouseLeft.
MouseMoved event shouldn't have been triggered in SFML 1.6

Can you make the window loose focus with the mouse with this solution? If you can, then perhaps Laurent should take a look at it.
Sure I can get reproduce the problem. Additionally my 'logical' argumentation hasn't changed either. ;)

Btw you're implementation has twice sf::Event event; declared. And since you're checking sf::Mouse::getPosition() inside the event loop it probably doesn't event reset the position for all the possible interations... ;)
Title: Re: Relative Mouse Movement (Mouse Capture)
Post by: model76 on October 11, 2012, 02:40:27 am
Sure but like you said somewhere and that doesn't have anything to do with SFML. ;)
I never said it did.

Wrong sf::Mouse and the window event system don't have anything in common. Note events are triggered by the OS and sent to the window, while sf::Mouse access the device itself. ;)
So are you saying that calling sf::Mouse::setPosition does not cause an sf::EventMouseMoved event to happen?
#include <SFML/Graphics.hpp>

int main()
{
    sf::RenderWindow window( sf::VideoMode( 800, 600 ), "SFML" );

    while( window.isOpen() )
    {
        sf::Event event;
        while( window.pollEvent( event ) )
        {
            sf::Mouse::setPosition( sf::Vector2i( 400, 300 ), window );

            if( event.type == sf::Event::Closed )
                window.close();
        }

        window.clear();
        window.display();
    }
}
 
On my system, this program gets stuck in the event loop, and the window is never cleared.

Sure I can get reproduce the problem.
I checked, and it seems to be the case. By moving the mouse quickly and pressing a button, it is possible to make the window loose focus. It will be interesting to hear what Laurent has to say about it.

Btw you're implementation has twice sf::Event event; declared.
It certainly did. Must have happened when I was replacing the TAB characters with spaces. Anyway, it is now corrected.

And since you're checking sf::Mouse::getPosition() inside the event loop it probably doesn't event reset the position for all the possible interations... ;)
I am not sure it matters, since I believe that the global inputs and events are synchronized in SFML 2. I haven't checked the documentation or done any tests, though. It was just something I talked to Laurent about long ago in 1.x where they were not synchronized.
Title: Re: Relative Mouse Movement (Mouse Capture)
Post by: eXpl0it3r on October 11, 2012, 07:20:12 am
So are you saying that calling sf::Mouse::setPosition does not cause an sf::EventMouseMoved event to happen?
On my system, this program gets stuck in the event loop, and the window is never cleared.
Sure this ends up in a infinity loop, but that does say anything about the two systems begin connected.
The only thing that happens is that you move the mouse over the window which triggers an sf::EventMouseMoved event by the OS that gets send to the event loop which triggers and event and so on, but it's not SFML that triggers the event.
You can for instance set the mouse pointer outside the window and not get stuck in the event loop.

I am not sure it matters, since I believe that the global inputs and events are synchronized in SFML 2. I haven't checked the documentation or done any tests, though. It was just something I talked to Laurent about long ago in 1.x where they were not synchronized.
Well then you certaintly should take a look again at the implementation (as I have) and they are not connected, which also is what Laurent has said many times and what the whole sense was when splitting up sf::Mouse, sf::Keyboard and sf::Joystick from the sf::Window class.
As example don't use a sf::Window/sf::RenderWindow but use a plain old terminal and the include the Mouse.hpp header and retrive the mouse position etc. If the events and sf::Mouse where connected things wouldn't work (since you don't have an window = you don't have a event queue).
So the systems are not connected. :)
Title: Re: Relative Mouse Movement (Mouse Capture)
Post by: Robert42 on October 14, 2012, 03:59:33 pm
I think it's also important to discuss, whether this feature fits to SFML's design.

If not, I would suggest to create a wiki-page-tutorial howto implement relative mouse movement working on slow machines.
Title: Re: Relative Mouse Movement (Mouse Capture)
Post by: eXpl0it3r on October 14, 2012, 05:39:55 pm
If not, I would suggest to create a wiki-page-tutorial howto implement relative mouse movement working on slow machines.
Relative mouse positions are already possible with SFML... (sf::Mouse)
Also how is this connected to slow machines? ;)
Title: Re: Relative Mouse Movement (Mouse Capture)
Post by: Robert42 on October 14, 2012, 06:09:47 pm
Relative mouse positions are already possible with SFML... (sf::Mouse)
I have only browsed over this thread very fast. I think you've written that low fps (that's why I said slow machines) and window mode can cause the window to loose focus.

Correct me if I am wrong, but I also think to have read, that you've written that a solution for this would be using a seperate thread to get the mouse delta using sf::Mouse

So yes it is possible to implement relative mouse movement using sfml only. But it seems to require some extra amount of work (most implementation would make some mistake when using multiple threads) implemented multiple times for different projects.
So writing library or writing a tutorial could be useful.

This thread is a feature request about doing exactly this in sfml. As a feature itself it would be clearly useful. So it's imho a design question whether to add this to sfml.
Title: Re: Relative Mouse Movement (Mouse Capture)
Post by: eXpl0it3r on October 14, 2012, 06:28:55 pm
Okay let me clarify things again:

Relative mouse position (deltas) can be used with the current SFML 2 and it works quite well if you have a high enough update rate (i.e. you can separate the updating & drawing).

The only problem at this point (and that's the only thing I'd request as a feature) is that the mouse pointer isn't locked within the window, thus with a fast mouse movement in combination with a lower update rate it's possible that the mouse cursor leaves the window for a split second, within which you could click outside the window by accident (see video above).

Correct me if I am wrong, but I also think to have read, that you've written that a solution for this would be using a seperate thread to get the mouse delta using sf::Mouse
That was more a theoretical answer than one that should be implemented in any way. The theory behind was that you could max out that full processor (given that you're on a multi-processor architecture) and thus you had a very high update rate which reduces the chances of you being able to move the mouse outside the window. On the other hand you'd probably could run into troubles with getting useful deltas and you'd max out one CPU core which you shouldn't do (if not nessecary).

So at the moment you can implement the original requested features but it has some (possible) problems which could be fixed with having a way to lock the mouse within the window.
Title: Re: Relative Mouse Movement (Mouse Capture)
Post by: Robert42 on October 14, 2012, 08:58:24 pm
can sf::sleep be used in multiple threads?
Title: Re: Relative Mouse Movement (Mouse Capture)
Post by: eXpl0it3r on October 14, 2012, 09:01:45 pm
Sure but you have to call it from each thread, i.e. if just call it in the main thread, thread X won't be affected.
sf::sleep simply lets the current thread sleep for x time units. ;)
Title: Re: Relative Mouse Movement (Mouse Capture)
Post by: greeniekin on November 02, 2012, 09:13:55 pm
I actually have troubles with this in source engine games.

I have 2 screens so even fullscreen(only one screen) I can still loose focus if I move the mouse really quickly to the right and left click.

It infuriates me that the source engine still has not worked that out.

This should be pretty easy to implement. I have played with this before on windows.

Here is all you need. It locks the cursor to an area. You will need to resize it with the window.
http://msdn.microsoft.com/en-us/library/windows/desktop/ms648383(v=vs.85).aspx

XGrabPointer might work on linux. Never used it though.

With mac I have no idea.

I could make a patch for this with the go ahead on the api implementation from Laurent.(not mac. I do not own one)
Of course for the time for him to decide on the api he could implement it himself.


I suppose something like this
void Window::lockMouseCursor(bool locked)
bool Window::isLockMouseCursor()

though sfml uses set and get everywhere. So i suppose

void Window::setMouseCursorLocked(bool locked)
bool Window::getMouseCursorLocked()
Title: Re: Relative Mouse Movement (Mouse Capture)
Post by: Oberon on November 03, 2012, 04:34:34 pm
If this is ever going to be implemented, Chrome might serve as a reference implementation: http://code.google.com/p/chromium/source/search?q=%3A%3ALockMouse%5C%28%5C%29 (http://code.google.com/p/chromium/source/search?q=%3A%3ALockMouse%5C%28%5C%29).
Title: Re: Relative Mouse Movement (Mouse Capture)
Post by: Hiura on November 04, 2012, 11:05:02 am
thanks! it's indeed useful if we do it one day.
Title: Re: Relative Mouse Movement (Mouse Capture)
Post by: Robert42 on November 04, 2012, 12:07:59 pm
We could create a fork, implement it by ourselves (1 persen per system) and create a pull request
Title: Re: Relative Mouse Movement (Mouse Capture)
Post by: Weeve on January 19, 2013, 08:49:20 pm
Notes:

Event triggers for focus:
sf::Event Event;
while (getApp()->GetEvent(Event)){
        if (Event.Type == sf::Event::Closed){
                getApp()->Close();
        }else if (Event.Type == sf::Event::Resized){
                _ScreenSize = sf::Vector2<int> (Event.Size.Width, Event.Size.Height);
                if (_ScreenSize.y < _ScreenSize.x){
                        glViewport(0, (_ScreenSize.y-_ScreenSize.x)/2, _ScreenSize.x, _ScreenSize.x);
                }else{
                        glViewport((_ScreenSize.x-_ScreenSize.y)/2, 0, _ScreenSize.y, _ScreenSize.y);
                }
        }else if (Event.Type == sf::Event::GainedFocus){
                _InFocus = true;
        }else if (Event.Type == sf::Event::LostFocus){
                _InFocus = false;
        }else if (Event.Type == sf::Event::MouseButtonPressed){
                _InFocus = true;
        }else if ((Event.Type == sf::Event::KeyPressed) && (Event.Key.Code == sf::Key::Escape)){
                _InFocus = false;
        }
}
 

Resetting the mouse:
if (Cam->_CursorLocked){
        if (_InFocus){
                _App.ShowMouseCursor(false);
                _App.SetCursorPosition(_ScreenSize.x/2,_ScreenSize.y/2);
        }else{
                _App.ShowMouseCursor(true);
        }
}else{
        _App.ShowMouseCursor(true);
}
 

Getting relative mouse movements:
sf::Vector2<float> MouseMove = sf::Vector2<float> (App->GetInput().GetMouseX()-ScreenSize.x/2 ,App->GetInput().GetMouseY()-ScreenSize.y/2);