2

I have been playing around with webGL and I have reached a point I can make small three dimensional games with very pitiful graphics (it is more of a proof of concept/functionality as of now). For the three dimensional experience, it is nice to move the mouse in any direction infinitely and seamlessly to rotate the first person camera. Pointerlock allows me to lock and hide the cursor position, which is very helpful, but then I need to find another method of tracking the mouse's movements. In my research, event.movementX and event.movementY seemed to be the standard, but I often get large blips (usually between 500 and 583) of movement in the opposite direction of the mouse's movement. I tested this with numerous mice and trackpads and experience the same phenomenon.

Here are my relavent event listeners:

document.addEventListener("mousemove", function(event) {
   xMovement += event.movementX; 
   yMovement += event.movementY; 
   console.log(event.movementX)
}, false);

document.addEventListener("pointerlockchange", function(event) {
   if(pointerLockEnabled) pointerLockEnabled = false; 
   else pointerLockEnabled = true; 
   xMovement = 0; yMovement = 0; 
} , false);

And relevant render loop code:

function render() {
   if(pointerLockEnabled) {
       camera.rotation.y = -xMovement / 1000;
       camera.rotation.x = -yMovement / 1000;
       if(rightKey && !leftKey) {
          camera.position.x += 10 * Math.cos(camera.rotation.y);
          camera.position.z -= 10 * Math.sin(camera.rotation.y);
       }
       else if(leftKey && !rightKey) {
          camera.position.x -= 10 * Math.cos(camera.rotation.y);
          camera.position.z += 10 * Math.sin(camera.rotation.y);
       }
       if(upKey&& !downKey) {
          camera.position.z -= 10 * Math.cos(camera.rotation.y);
          camera.position.x -= 10 * Math.sin(camera.rotation.y);
       }
       else if(downKey && !upKey) {
          camera.position.z += 10 * Math.cos(camera.rotation.y);
          camera.position.x += 10 * Math.sin(camera.rotation.y);
       }
   }
}

But my console has occurrences such as this:

console capture

I added conditions to changing xMovement to prevent massive turns in camera angle, but I am still left with very annoying movement. Any ideas to patch or replace to a more seamless interface movement?

  • 1
    need to see more context, such as what other code is related to the mouse movement or functionality etc (like the camera locking as you mention) – mad.meesh Jan 10 '18 at 03:44
  • I'm experiencing the same, if not a very similar problem. Did you manage to find the cause and/or a solution? – T. J. Evers Jul 27 '21 at 10:32

3 Answers3

2

I know I'm super late to the party here, but I have an explanation for the first problem mentioned in the OP, and a work-around for the second problem brought up by @KiranKota.

The first problem is actually a bug in Chromium versions prior to 64. It was dormant until something happened in the Windows 10 Fall Creator's update that ended up exposing it. Even though you would be in pointer-lock, your "cursor", though invisible, would essentially wrap around to the other side of the window, causing spikes in the opposite direction of movement.

The fix for this is to simply ignore the first mouse move event that moves in the opposite direction; that's if you still care about supporting Chromium < 67.

The second problem, where the spikes move in the same direction, is entirely unrelated, and still a problem as of Chromium 94. The issue has to do with mice with high polling rates, as is the case with many gaming mice. Through my experiments, I've discovered that a polling rate of 1000 is quite bad, 500 less so, and 250 appears to make the issue disappear. I've also discovered that the spikes are consistent with the width of the current window. They are always window.innerWidth (or innerHeight) / ~2.3... , plus what I can only assume is the "real" distance of the current mouse movement. Why 2.3-ish...? I have no idea. The factor is the same whether I'm running a rate of 1000 or 500.

I'm going to experiment with this some more and see if I can't reliably filter out these anomalies. In the mean time, perhaps this info will be useful.

UPDATE: I've settled on solving the second issue by simply ignoring any mouse movements that are greater than window.innerWidth / 3 and window.innerHeight / 3. I've provided a toggle for users to turn this solution on or off, but it doesn't seem to interfere with normal use, regardless of polling rate.

Josh Langley
  • 351
  • 2
  • 6
  • Have you come back to this at all? I had the same findings on polling rate, but we can't ask users to change that so I'm still interested in a function that can reliably ignore/smooth the bad values – Kiran Kota Nov 27 '22 at 15:33
  • 1
    I have! See the update in my OP. – Josh Langley Nov 30 '22 at 20:00
1

It could be helpful if you would throttle your mousemove event in some way. For example the lodash throttle version:

function handleMouseMove(event) {
   xMovement += event.movementX; 
   yMovement += event.movementY; 
   console.log(event.movementX)
}
var throttledHandleMouseMove = _.throttle(handleMouseMove, 75);
document.addEventListener("mousemove", throttledHandleMouseMove, false);

With this approach handleMouseMove would not be executed more that 1 time per 75ms.

dhilt
  • 18,707
  • 8
  • 70
  • 85
  • So, this essentially prevents noise by throttling the frequency that the event is checked? – Benjamin Brownlee Jan 10 '18 at 04:17
  • @BenjaminBrownlee Exactly. See [this demo](https://patrickhlauke.github.io/touch/touch-limit/), it is based on `limit.js` but the implementation does not matter. So I'd recommend [lodash.throttle](https://www.npmjs.com/package/lodash.throttle)... – dhilt Jan 10 '18 at 11:58
  • 1
    This doesn't work, for anyone who reads this and is considering trying it. Throttling the handler function doesn't throttle event propagation. I had my suspicion but tried it anyway by limiting the handler function to framerate and then 75ms. It didn't fix the erroneous values – Kiran Kota Nov 26 '20 at 17:19
0

I had the same issue but, for me, the bad values are always in the same direction as movement. I found that if I replace any values above 50 with the last value, I get very good accuracy. The only issues are when the bad values are in the 30-49 range but I don't want to cancel those in case the user is actually moving their mouse that fast or their mouse has a bad polling rate. Some trendline comparison would work for smoothing those, but if you don't need too much precision, this is good:

const movement = {X: 0, Y: 0};
const lastMovement = {X: 0, Y: 0};
function onMouseMove(evt) {
  if (checkPointerLock()) {
    ['X', 'Y'].forEach(axis => {
      const checkValue = evt['movement' + axis] || evt['mozMovement' + axis]|| 0;
      //ignore >=50. probably erroneous. smooth to last value
      if (Math.abs(checkValue) < 50) {
        lastMovement[axis] = checkValue;
      }
      movement[axis] = lastMovement[axis];
    });

    yaw += movement.X * -0.001 * data.sensitivityX;
    pitch += movement.Y * (data.invertY ? .001 : -.001) * data.sensitivityY;
    pitch = Math.max(-Math.PI/2, Math.min(Math.PI/2, pitch));
  }
}
Kiran Kota
  • 103
  • 1
  • 12