0

Alright, so I am trying to use the analog stick on a gamepad to move the desktop mouse cursor around. The problem is that I need to be able to get the same smoothness as Attempt 2, but without using an absolute mouse position. The cursor needs to be moved relative to its current position. The reason for this is that many applications (mainly video games) also set the mouse to an absolute position. This causes the application and attempt 2 to fight one another for control of the mouse.

Attempt 1 (relative)

// keep updating the controller state and mouse position
while (true)
{
    // getState returns a 2d float vector with normalized values from [-1, 1]
    // The cursor is being set relative to its current position here.
    SetCursorPosition(GetCursorPosition() + analogStick->getState());
}

This solution works, but suffers from a rounding issue because GetCursorPosition and SetCursorPosition are based on integers. As a result, small movements are not registered because smaller analog movements will always get truncated. Visually speaking, small movements on the analog stick will only move the mouse along the X or Y axis even if you are try to make a diagonal movement.

Attempt 2 (absolute)

vec2 mouseTargetPosition = GetCursorPosition(); // global cursor position
while (true)
{
    mouseTargetPosition += leftStick->getState();
    vec2 newPosition = lerp(GetCursorPos(), mouseTargetPosition, 0.8f);
    SetCursorPos(round(newPosition.x), round(newPosition.y));
}

This solution works great, the mouse responds to the smallest of movements and moves very naturally as a result of interpolating the accumulated analog movements. But, it sets the mouse to an absolute position (mouseTargetPosition), making this solution a deal breaker.

Watercycle
  • 497
  • 1
  • 7
  • 12

1 Answers1

2

This is an awfully specific question in the first place I suppose. After fooling around with several configurations this is the one that feels smoothest and works well. It's basically magic considering because it can add native feeling analog support for games and model viewers that don't have it :)

vec2 mouseTargetPos, mouseCurrentPos, change;
while (true)
{
    // Their actual position doesn't matter so much as how the 'current' vector approaches
    // the 'target vector'
    mouseTargetPos += primary->state;
    mouseCurrentPos = util::lerp(mouseCurrentPos, mouseTargetPos, 0.75f);
    change = mouseTargetPos - mouseCurrentPos;

    // movement was too small to be recognized, so we accumulate it
    if (fabs(change.x) < 0.5f) accumulator.x += change.x;
    if (fabs(change.y) < 0.5f) accumulator.y += change.y;

    // If the value is too small to be recognized ( < 0.5 ) then the position will remain the same
    SetCursorPos(GetCursorPos() + change);
    SetCursorPos(GetCursorPos() + accumulator);

    // once the accumulator has been used, reset it for the next accumulation.
    if (fabs(accumulator.x) >= 0.5f) accumulator.x = 0;
    if (fabs(accumulator.y) >= 0.5f) accumulator.y = 0;

}
Watercycle
  • 497
  • 1
  • 7
  • 12
  • I'm having trouble understanding this. So every frame currPos covers 75% of the distance to actualTarget. The remaining 25% assigned to `change`. `change` is added to `accumulator` if there's < 0.5f distance remaining. But then both `change` and `accumulator` are added to OS cursor position no matter what. Do I just need to get some sleep? – danneu Jul 15 '22 at 20:24
  • Yikes, yeah, that's confusing. If I recall correctly, `GetCursorPos` returned integers. `SetCursorPos` took doubles and would round them into integers (pixels). So, values below 0.5 wouldn't actually change the mouse position despite what the call to `SetCursorPos` suggests. Essentially, the first `SetCursorPos` accounts for large movements and the second one for smaller (it's okay for both to get applied on the same frame). Also... you could write `mouseCurrentPos = mouseTargetPos`. All the lerp does is add a super small amount of drag to the mouse every frame. – Watercycle Jul 16 '22 at 02:08
  • I see, that seems obvious now. Thanks for coming back after seven years to clarify, haha. – danneu Jul 18 '22 at 23:04