Infinite mouse controller camera movement


#1

Hi,

I’m not sure if it is, and I apologise in advance if this is not Cinder related question.

I would like to be able to control the camera such that when panning, rotating etc, the application continues to respond to mouse movement, even after the mouse has gone outside the bounds of the screen. At the moment the user can eg rotate the camera, but the camera stops when the mouse goes outside the bounds of the screen. Is this possible with Cinder somehow?

I don’t even know how to phrase the behavior I am looking for in a manner that google can help.

Apologies again if the solution lies in Windows™.

Thanks


#2

Good question.

I believe the Windows operating system does not send events to the application window once the mouse leaves its bounds. So this is a limitation of the OS, if I am not mistaken.

One way to work around this, is by hiding the cursor pointer, then keeping it inside the window bounds by resetting its position if it gets too close to the edge. Only do this while the mouse button is down. You don’t want to constrain a user too much.

You can use OS-specific code for this. An example implementation can be found in my old Stars sample. The link will take you to the relevant lines of code, but see also this part of the code.

-Paul

Note: you may not need forceShowCursor() and forceHideCursor() because I believe Cinder’s implementation of showCursor() and hideCursor() has changed since I wrote the sample and now makes sure the cursor is shown or hidden.


#3

That’s awesome. Thanks Paul.

#ashamed_and_must_study_the_examples_more :slight_smile:


#4

Hi,

Just to follow this up, I implemented (copy/pasted :),thanks Paul) the code to do the mouse manipulation and it worked very well 99.999% of the time. However I was experiencing stutters in the mouse movement, noticing massive jumps in the MouseEvent x,y position as received by my mouse event handlers, from cinder. This seemed to occur when the code performed the win32 ::SetMousePos() method, but only sometimes. I can only assume this means that Windows 10 may have a similar issue to the one described in the commented MacOS version in constrainCursor(), and maybe the mouse coordinates are not delivered seamlessly to the application after the ::SetMousePos() call. It may be my application logic, however when I swapped out this method with another I got good results. Who knows.

So I then discovered that I could use the raw input mode of Windows which has 2 benefits. It allows the mouse to report relative movements of the mouse indefinitely, and then the application can track the virtual cursor based on that info. It also allows the application to respond to subpixel mouse movements generated by higher-DPI mice, which is nice. It also removes any ballistics on the position reporting, which can be an advantage or disadvantage depending on the application. In this case, for camera movement, I think its a pro. I can add my own mouse ballistics code driving my virtual cursor if I need to later.

By some giggery pokery (adding a mMouseRelativeX and mMouseRelativeY field to the MouseEvent class and adding a new MouseDragRel application event etc) and by adding code to manage the WM_INPUT message in the cinder message loop:

        // AppImplMsw.cpp
        // LAJ mod - raw mouse input
        case WM_INPUT:
        {
            UINT dwSize;

            GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER));
            LPBYTE lpb = new BYTE[dwSize];
            if (lpb == NULL)
            {
                return 0;
            }

            if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize)
                console() << "ERROR: GetRawInputData does not return correct size" << std::endl;

            RAWINPUT* raw = (RAWINPUT*)lpb;

            TCHAR szTempOutput[300];
            if (raw->header.dwType == RIM_TYPEKEYBOARD)
            {
               /* Keyboard data - dont need this but it is available */
                    raw->data.keyboard.MakeCode,
                    raw->data.keyboard.Flags,
                    raw->data.keyboard.Reserved,
                    raw->data.keyboard.ExtraInformation,
                    raw->data.keyboard.Message,
                    raw->data.keyboard.VKey);*/
            }
            else if (raw->header.dwType == RIM_TYPEMOUSE)
            {
               /* Mouse data */
                impl->mMouseRelativeX = raw->data.mouse.lLastX;
                impl->mMouseRelativeY = raw->data.mouse.lLastY;

                if (raw->data.mouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_DOWN)
                    impl->leftDown = true;
                if (raw->data.mouse.usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_DOWN)
                    impl->middleDown = true;
                if (raw->data.mouse.usButtonFlags & RI_MOUSE_RIGHT_BUTTON_DOWN)
                    impl->rightDown = true;
                if (raw->data.mouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_UP)
                    impl->leftDown = false;
                if (raw->data.mouse.usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_UP)
                    impl->middleDown = false;
                if (raw->data.mouse.usButtonFlags & RI_MOUSE_RIGHT_BUTTON_UP)
                    impl->rightDown = false;
                
		// TODO: pass on control/alt etc keyflags also
                unsigned int cinderEventFlags = 0;
                if (impl->leftDown)
                    cinderEventFlags |= MouseEvent::LEFT_DOWN;
                if (impl->middleDown)
                    cinderEventFlags |= MouseEvent::MIDDLE_DOWN;
                if (impl->rightDown)
                    cinderEventFlags |= MouseEvent::RIGHT_DOWN;
		
                if (impl->mIsDragging && 
                    cinderEventFlags != 0)  // and a button is pressed
                {
                    MouseEvent event(impl->getWindow(), 0, 0, 0,
                        impl->mMouseRelativeX, impl->mMouseRelativeY,
                        cinderEventFlags, 0.0f, static_cast<unsigned int>(wParam));
                    impl->getWindow()->emitMouseDragRel(&event);
                }
            }

            delete[] lpb;
            return 0;
        }

and the following code to register the applications interest in the WM_INPUT message (in the WindowImplMsw class constructor):

    // AppImplMsw.cpp
    // LAJ mod - Raw mouse input
    RAWINPUTDEVICE mouseDev;
    mouseDev.usUsagePage = 1; // 1 = HID_USAGE_PAGE_GENERIC         
    mouseDev.usUsage = 2; // 2 = HID_USAGE_GENERIC_MOUSE        
    mouseDev.dwFlags = RIDEV_INPUTSINK;
    mouseDev.hwndTarget = mWnd;
    RegisterRawInputDevices(&mouseDev, 1, sizeof(mouseDev));

Now, all I do is I handlke camera rotation and panning in the MouseDragRel event handler, where I can also maintain a virtually infinite mouse cursor, and this results in very smooth unlimited camera movements.

I wondered by the by, why Cinder does not offer a relative mouse input independent of operating system? I would have thought it a common problem for camera movements etc?

Cheers!
Laythe