Use GetMouseMovePointsEx to smooth mouse input.
authorFraser Waters <frassle@gmail.com>
Sun, 23 Mar 2014 16:26:24 +0000 (16:26 +0000)
committerFraser Waters <frassle@gmail.com>
Sun, 23 Mar 2014 16:26:24 +0000 (16:26 +0000)
Uses GetMouseMovePointsEx to get mouse move history so mouse events aren't dropped even with low framerates.

Fixes #76.

Source/OpenTK/Platform/Windows/API.cs
Source/OpenTK/Platform/Windows/WinGLNative.cs

index e265afd..a29c677 100644 (file)
@@ -111,6 +111,13 @@ namespace OpenTK.Platform.Windows
 
     internal static class Functions
     {
+        #region GetLastError
+
+        [DllImport("Kernel32.dll")]
+        internal static extern uint GetLastError();
+
+        #endregion
+
         #region Window functions
 
         #region SetWindowPos
@@ -371,6 +378,21 @@ namespace OpenTK.Platform.Windows
 
         #endregion
 
+        #region GetMessageTime
+
+        /// <summary>
+        /// Retrieves the message time for the last message retrieved by the 
+        /// GetMessage function. The time is a long integer that specifies the 
+        /// elapsed time, in milliseconds, from the time the system was started 
+        /// to the time the message was created (that is, placed in the thread's
+        /// message queue).
+        /// </summary>
+        /// <returns>The return value specifies the message time.</returns>
+        [DllImport("User32.dll")]
+        internal static extern int GetMessageTime();
+
+        #endregion
+
         #region SendMessage
 
         [DllImport("user32.dll", CharSet = CharSet.Auto)]
@@ -1004,6 +1026,30 @@ namespace OpenTK.Platform.Windows
         [DllImport("user32.dll")]
         public static extern bool SetCursorPos(int X, int Y);
 
+        /// <summary>
+        /// Retrieves a history of up to 64 previous coordinates of the mouse or pen.
+        /// </summary>
+        /// <param name="cbSize">The size, in bytes, of the MouseMovePoint structure.</param>
+        /// <param name="pointsIn">
+        /// A pointer to a MOUSEMOVEPOINT structure containing valid mouse 
+        /// coordinates (in screen coordinates). It may also contain a time 
+        /// stamp.
+        /// </param>
+        /// <param name="pointsBufferOut">
+        /// A pointer to a buffer that will receive the points. It should be at 
+        /// least cbSize * nBufPoints in size.
+        /// </param>
+        /// <param name="nBufPoints">The number of points to be retrieved.</param>
+        /// <param name="resolution">
+        /// The resolution desired. This parameter can GMMP_USE_DISPLAY_POINTS 
+        /// or GMMP_USE_HIGH_RESOLUTION_POINTS.
+        /// </param>
+        /// <returns></returns>
+        [DllImport("user32", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)]
+        unsafe internal static extern int GetMouseMovePointsEx(
+            uint cbSize, MouseMovePoint* pointsIn, 
+            MouseMovePoint* pointsBufferOut, int nBufPoints, uint resolution);
+
         #region Async input
 
         #region GetCursorPos
@@ -1633,6 +1679,19 @@ namespace OpenTK.Platform.Windows
             internal static readonly IntPtr MESSAGE_ONLY = new IntPtr(-3);
 
             internal static readonly IntPtr HKEY_LOCAL_MACHINE = new IntPtr(unchecked((int)0x80000002));
+
+            /// <summary>
+            /// Retrieves the points using the display resolution.
+            /// </summary>
+            internal const int GMMP_USE_DISPLAY_POINTS = 1;
+
+            /// <summary>
+            /// Retrieves high resolution points. Points can range from zero to 
+            /// 65,535 (0xFFFF) in both x and y coordinates. This is the resolution 
+            /// provided by absolute coordinate pointing devices such as drawing 
+            /// tablets.
+            /// </summary>
+            internal const int GMMP_USE_HIGH_RESOLUTION_POINTS = 2;
         }
 
         #endregion
@@ -2917,6 +2976,36 @@ namespace OpenTK.Platform.Windows
 
     #endregion
 
+    #region MouseMovePoint
+
+    /// <summary>
+    /// Contains information about the mouse's location in screen coordinates.
+    /// </summary>
+    [StructLayout(LayoutKind.Sequential)]
+    public struct MouseMovePoint
+    {
+        /// <summary>
+        /// The x-coordinate of the mouse.
+        /// </summary>
+        public int X;
+        /// <summary>
+        /// The y-coordinate of the mouse.
+        /// </summary>
+        public int Y;
+        /// <summary>
+        /// The time stamp of the mouse coordinate.
+        /// </summary>
+        public int Time;
+        /// <summary>
+        /// Additional information associated with this coordinate.
+        /// </summary>
+        public IntPtr ExtraInfo;
+
+        public static readonly int SizeInBytes = Marshal.SizeOf(default(MouseMovePoint));
+    }
+
+    #endregion
+
     #endregion
 
     #region --- Enums ---
index aa84682..5bfd209 100644 (file)
@@ -70,6 +70,7 @@ namespace OpenTK.Platform.Windows
         bool borderless_maximized_window_state = false; // Hack to get maximized mode with hidden border (not normally possible).
         bool focused;
         bool mouse_outside_window = true;
+        int mouse_last_timestamp = 0;
         bool invisible_since_creation; // Set by WindowsMessage.CREATE and consumed by Visible = true (calls BringWindowToFront).
         int suppress_resize; // Used in WindowBorder and WindowState in order to avoid rapid, consecutive resize events.
         bool is_in_modal_loop; // set to true whenever we enter the modal resize/move event loop 
@@ -403,10 +404,86 @@ namespace OpenTK.Platform.Windows
 
         void HandleMouseMove(IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam)
         {
-            Point point = new Point(
-                (short)((uint)lParam.ToInt32() & 0x0000FFFF),
-                (short)(((uint)lParam.ToInt32() & 0xFFFF0000) >> 16));
-            mouse.Position = point;
+            unsafe
+            {
+                Point point = new Point(
+                    (short)((uint)lParam.ToInt32() & 0x0000FFFF),
+                    (short)(((uint)lParam.ToInt32() & 0xFFFF0000) >> 16));
+
+                // GetMouseMovePointsEx works with screen coordinates
+                Point screenPoint = point;
+                Functions.ClientToScreen(handle, ref screenPoint);
+                int timestamp = Functions.GetMessageTime();
+
+                // & 0xFFFF to handle multiple monitors http://support.microsoft.com/kb/269743 
+                MouseMovePoint movePoint = new MouseMovePoint()
+                {
+                    X = screenPoint.X & 0xFFFF,
+                    Y = screenPoint.Y & 0xFFFF,
+                    Time = timestamp,
+                };
+
+                // Max points GetMouseMovePointsEx can return is 64.
+                int numPoints = 64;
+
+                MouseMovePoint* movePoints = stackalloc MouseMovePoint[numPoints];
+
+                // GetMouseMovePointsEx fills in movePoints so that the most 
+                // recent events are at low indices in the array.
+                int points = Functions.GetMouseMovePointsEx(
+                    (uint)MouseMovePoint.SizeInBytes,
+                    &movePoint, movePoints, numPoints,
+                    Constants.GMMP_USE_DISPLAY_POINTS);
+
+                uint lastError = Functions.GetLastError();
+
+                // No points returned or search point not found
+                if (points == 0 || (points == -1 && lastError == 1171))
+                {
+                    // Just use the mouse move position
+                    mouse.Position = point;
+                }
+                else if (points == -1)
+                {
+                    throw new System.ComponentModel.Win32Exception((int)lastError);
+                }
+                else
+                {
+                    // Exclude the current position. 
+                    Point currentScreenPosition = new Point(mouse.X, mouse.Y);
+                    Functions.ClientToScreen(handle, ref currentScreenPosition);
+
+                    // Find the first move point we've already seen.
+                    int i = 0;
+                    for (i = 0; i < points; ++i)
+                    {
+                        if (movePoints[i].Time < mouse_last_timestamp)
+                            break;
+                        if (movePoints[i].Time == mouse_last_timestamp &&
+                            movePoints[i].X == currentScreenPosition.X &&
+                            movePoints[i].Y == currentScreenPosition.Y)
+                            break;
+                    }
+
+                    // Now move the mouse to each point before the one just found.
+                    while (--i >= 0)
+                    {
+                        Point position = new Point(movePoints[i].X, movePoints[i].Y);
+                        // Handle multiple monitors http://support.microsoft.com/kb/269743 
+                        if (position.X > 32767)
+                        {
+                            position.X -= 65536;
+                        }
+                        if (position.Y > 32767)
+                        {
+                            position.Y -= 65536;
+                        }
+                        Functions.ScreenToClient(handle, ref position);
+                        mouse.Position = position;
+                    }
+                }
+                mouse_last_timestamp = timestamp;
+            }
 
             if (mouse_outside_window)
             {