2
\$\begingroup\$

I'm working on a class responsible for arm sway in an fps engine, and am having an issue with a stutter that appears to be related to fluctuating framerate. However I have also noticed it when locking the framerate, but to a much lesser almost unnoticeable degree (so maybe NOT related to framerate variance). As far as I can tell I have everything setup the way it should be to account for framerate fluctuations, but apparently I've missed something?? Curious if I've made a glaring oversight that might jump out at someone. I have uploaded a video where you can see exactly what it is I'm talking about.

Any ideas? update() is where the magic happens.

#include "glm/gtx/string_cast.hpp"
#include "vel/App.h"
#include "vel/helpers/functions.h"

#include "ArmSwayController.h"


ArmSwayController::ArmSwayController() :
    input(vel::App::get().getInputState()),
    swayXYPos(glm::vec2(0.0f, 0.0f)),
    maxSwayDistance(0.1f),
    swaySpeed(0.0f),
    mouseBufferSize(44),
    firstMouseInput(true),
    currentMousePosition(glm::vec2(0.0f, 0.0f)),
    lastMousePosition(glm::vec2(0.0f, 0.0f)),
    mouseDelta(glm::vec2(0.0f, 0.0f)),
{};

glm::vec2 ArmSwayController::getAverageMouse()
{
    glm::vec2 out;
    
    for (auto& x : this->mouseXBuffer)
    {
        out.x += x;
    }
    out.x = out.x / this->mouseXBuffer.size();

    for (auto& y : this->mouseYBuffer)
    {
        out.y += y;
    }
    out.y = out.y / this->mouseYBuffer.size();

    return out;
}

void ArmSwayController::bufferMouse(glm::vec2 in)
{
    if (this->mouseXBuffer.size() == this->mouseBufferSize)
        this->mouseXBuffer.pop_front();
    if (this->mouseYBuffer.size() == this->mouseBufferSize)
        this->mouseYBuffer.pop_front();

    this->mouseXBuffer.push_back(in.x);
    this->mouseYBuffer.push_back(in.y);
}

void ArmSwayController::bufferMouseXSpeed(float in)
{
    if (this->mouseXSpeedBuffer.size() == this->mouseBufferSize)
        this->mouseXSpeedBuffer.pop_front();

    this->mouseXSpeedBuffer.push_back(in);
}

void ArmSwayController::bufferMouseYSpeed(float in)
{
    if (this->mouseYSpeedBuffer.size() == this->mouseBufferSize)
        this->mouseYSpeedBuffer.pop_front();

    this->mouseYSpeedBuffer.push_back(in);
}

void ArmSwayController::updateMouseDelta()
{
    this->currentMousePosition.x = this->input.mouseXPos;
    this->currentMousePosition.y = this->input.mouseYPos;

    if (this->firstMouseInput)
    {
        this->lastMousePosition = this->currentMousePosition;
        this->firstMouseInput = false;
    }

    this->mouseDelta.x = this->currentMousePosition.x - this->lastMousePosition.x;
    this->mouseDelta.y = this->lastMousePosition.y - this->currentMousePosition.y;

    this->lastMousePosition = this->currentMousePosition;
}

glm::vec2 ArmSwayController::update(float dt) // called once per tick
{
    this->updateMouseDelta();

    this->bufferMouse(this->mouseDelta);
    this->swaySpeed = 0.00000625f / dt;


    glm::vec2 vecToAdd = vel::helpers::functions::invertVec2(this->getAverageMouse());

    // X
    this->bufferMouseXSpeed(vecToAdd.x);

    float pullBackAmountX = this->mouseXSpeedBuffer.size() > 1 ? this->mouseXSpeedBuffer[this->mouseXSpeedBuffer.size() - 2] : this->mouseXSpeedBuffer[0];
    pullBackAmountX = pullBackAmountX < 0 ? fabs(pullBackAmountX) : (-1.0f * pullBackAmountX);

    this->swayXYPos.x += ((vecToAdd.x + pullBackAmountX) * this->swaySpeed);


    // Y
    this->bufferMouseYSpeed(vecToAdd.y);

    float pullBackAmountY = this->mouseYSpeedBuffer.size() > 1 ? this->mouseYSpeedBuffer[this->mouseYSpeedBuffer.size() - 2] : this->mouseYSpeedBuffer[0];
    pullBackAmountY = pullBackAmountY < 0 ? fabs(pullBackAmountY) : (-1.0f * pullBackAmountY);

    this->swayXYPos.y += ((vecToAdd.y + pullBackAmountY) * this->swaySpeed);


    return this->swayXYPos;
}
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

You're averaging the total travel of the mouse on a frame.

The travel of the mouse is proportional to the speed the player is moving the mouse and the duration over which you measure the movement.

Assuming the player has been moving the mouse at approximately the same speed for several frames in a row, if some of those frames were shorter (less time between calls to updateMouseDelta), then you'll store a smaller number in your buffer for those frames. If some of those frames were longer, you'll store a larger number.

So these are not exactly comparable measurements. Let's say the player moves the mouse right at 1000 screen pixels per second. Then at a given moment, your buffer might contain records of...

Mouse delta +16 in 16 ms
Mouse delta +17 in 17 ms
Mouse delta +15 in 15 ms
Mouse delta +20 in 20 ms

(Average delta +17)

If that +20 in 20 ms gets evicted for a new sample of +15 in 15 ms, that will substantially shift the average of the buffer (to 15.75). But it shouldn't, because the player's movement didn't actually change.

By weighing all these values equally in your average, you're claiming they all measure the same thing, but they don't, they represent observations over different durations.

So this is why you get a slight vibration here, from the happenstance of a stray short or long frame (or a random run of a few) dropping into your buffer and biasing the results one way or another.

To correct for this, you should divide your mouse delta by the time elapsed since you last set lastMousePosition. This will give you an estimate for how fast the mouse is moving, and that's something you can directly compare between frames. In that case our buffer might look more like...

Mouse delta +16 in 16 ms => speed +1 px/ms
Mouse delta +17 in 17 ms => speed +1 px/ms
Mouse delta +15 in 15 ms => speed +1 px/ms
Mouse delta +20 in 20 ms => speed +1 px/ms

Now our average speed stays stable.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ This might be the best answer I've ever seen written on a stackexchange site. It's extremely well written, descriptive, and explains why I was having the issue I was having AND includes a solution. Thank you for this! It did indeed resolve the stutter, and now it will no longer be haunting my dreams :) \$\endgroup\$
    – whitwhoa
    Commented Dec 2, 2020 at 16:53

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .