Home > Back-end >  How to achieve smooth camera movement from mouse input
How to achieve smooth camera movement from mouse input

Time:12-25

Alright, so I've been trying to figure this out for several years now (no joke, i'll work on it until I can't anymore and move onto another task telling myself i'll come back to it later and it's been a never ending loop), but I think I've finally collected enough information about what's going on that I can explain with enough detail that someone might be able to point me in the right direction.

The issue is that I cannot seem to achieve a smooth camera rotation based off mouse input in this 3d framework/engine I've been working on (hobby project) with c /glfw/glad/opengl. I've gone through and made sure that within windows mouse acceleration is disabled, within glfw cursor is disabled and GLFW_RAW_MOUSE_MOTION is enabled. There is nothing (that i'm aware of) that should be skewing the mouse input values that are being obtained.

The below is debug output I generated which shows: frame_time|current_mouse_x_position|mouse_delta_for_tick

0.010011: 5803.000000: 9.000000
0.010001: 5810.000000: 7.000000
0.010011: 5819.000000: 9.000000
0.010002: 5836.000000: 17.000000
0.010011: 5845.000000: 9.000000
0.010001: 5854.000000: 9.000000
0.010001: 5861.000000: 7.000000
0.010015: 5876.000000: 15.000000
0.010002: 5884.000000: 8.000000
0.010002: 5894.000000: 10.000000
0.010264: 5902.000000: 8.000000
0.010009: 5919.000000: 17.000000
0.010010: 5928.000000: 9.000000
0.010011: 5935.000000: 7.000000
0.010006: 5943.000000: 8.000000
0.010010: 5958.000000: 15.000000
0.010090: 5965.000000: 7.000000
0.010005: 5971.000000: 6.000000
0.010013: 5979.000000: 8.000000
0.010042: 5994.000000: 15.000000
0.010012: 6001.000000: 7.000000
0.010004: 6009.000000: 8.000000
0.010009: 6016.000000: 7.000000
0.010007: 6033.000000: 17.000000

This data was collected while moving the mouse along the x-axis as consistently as my hand would allow, and you can see in the mouse_delta_for_tick column that every so often the values appear to be double what they should be.

My first thought was that this could be due to the amount of time between when the positions are sampled, but you can see in the first column that the frame_time stays relatively consistent. So I'm uncertain what could be causing these values to be so off.

You can imagine that when using these values to calculate the camera rotation, it becomes stuttery/clicky. Below is how I'm doing the update every tick:

void Client::updateMouseDelta()
{
    // this->input updated elsewhere as fast as engine can update it, so in this function it is
    // the current mouse position for this tick
    this->currentMousePosition.x = this->input.mouseXPos;
    this->currentMousePosition.y = this->input.mouseYPos;

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

    //TODO mousedelta calculation and framerate or something causing noisy deltas??????idk????
    this->mouseDelta.x = this->currentMousePosition.x - this->lastMousePosition.x;
    this->mouseDelta.y = this->lastMousePosition.y - this->currentMousePosition.y;

    this->lastMousePosition = this->currentMousePosition;
}

void Client::updatePitchYaw()
{
    this->yaw  = this->mouseDelta.x * this->mouseSensitivity;
    this->pitch  = this->mouseDelta.y * this->mouseSensitivity;

    // make sure that when pitch is out of bounds, screen doesn't get flipped
    if (this->pitch > 89.0f)
        this->pitch = 89.0f;
    if (this->pitch < -89.0f)
        this->pitch = -89.0f;
}

void Client::updateRotation(float frameTime, float renderLerpInterval)
{
    this->updateMouseDelta();
    this->updatePitchYaw();

    this->lookDirection.x = cos(glm::radians(this->yaw)) * cos(glm::radians(this->pitch));
    this->lookDirection.y = sin(glm::radians(this->pitch));
    this->lookDirection.z = sin(glm::radians(this->yaw)) * cos(glm::radians(this->pitch));
    this->lookDirection = glm::normalize(this->lookDirection);

    auto cameraPosition = this->ccc->getCapsuleActor()->getInterpolatedTranslation(renderLerpInterval);

    this->worldCamera->setPosition(cameraPosition);
    this->worldCamera->setLookAt(cameraPosition   this->lookDirection);
}

I've done various things throughout the years to attempt to mask this stutter by smoothing the input, but it's never perfect and always introduces a noticeable latency in mouse movement (moving average filter, converting into a quaternion and using slerp between previous and current rotations, a low pass filter (although that might have been a different unrelated issue now that I think about it)).

Also just to double confirm that this wasn't unrelated to mouse input I implemented a simple rotation test using arrow keys as such (which is buttery smooth):

void Client::updatePitchYaw()
{
    if (this->input.keyUp)
        this->pitch  = 0.75f;
    if (this->input.keyDown)
        this->pitch -= 0.75f;

    if (this->input.keyRight)
        this->yaw  = 0.75f;
    if (this->input.keyLeft)
        this->yaw -= 0.75f;

    // make sure that when pitch is out of bounds, screen doesn't get flipped
    if (this->pitch > 89.0f)
        this->pitch = 89.0f;
    if (this->pitch < -89.0f)
        this->pitch = -89.0f;
}

If you've made it this far THANK YOU, and I suppose my main question is: How can I achieve the sort of buttery smooth responsive camera rotation via mouse input that every other game I've ever played seems to be able to achieve (counter-strike, quake3, half-life, etc)? There MUST be something that I'm missing.

EDIT

Adding main gameloop code and mouse input update code (stripped down version of loop, but all relevant parts should be there) which should make it clear how I am getting the mouse position values:

/*
App::execute() invoked via main(), once invoked, this method controls the
application until it is terminated.
*/
void App::execute()
{
    this->fixedLogicTime = 1 / this->config.LOGIC_TICK; // LOGIC_TICK = 120.0
    this->currentTime = this->time();

    while (true)
    {

        if (this->shouldClose || this->window->shouldClose())
            break;

        this->newTime = this->time();
        this->frameTime = this->newTime - this->currentTime;
        
        // UPDATE MOUSE INPUT HERE. THIS METHOD CALLS THE Window::setMouse() method
        // which is included below this one
        this->window->updateInputState();
        

        if (this->frameTime >= (1 / this->config.MAX_RENDER_FPS)) // cap max fps
        {
            this->currentTime = this->newTime;              

            if (this->frameTime > 0.25)
                this->frameTime = 0.25;

            this->accumulator  = this->frameTime;

            while (this->accumulator >= this->fixedLogicTime)
            {
                this->activeScene->applyTransformations();
                this->activeScene->processSensors();
                this->activeScene->innerLoop((float)this->fixedLogicTime); // fixed logic updates
                this->activeScene->updateAnimations(this->fixedLogicTime);
                this->activeScene->stepPhysics((float)this->fixedLogicTime);
                this->activeScene->postPhysics((float)this->fixedLogicTime);
                this->accumulator -= this->fixedLogicTime;
            }
            
            float renderLerpInterval = (float)(this->accumulator / this->fixedLogicTime);
            
            // THIS METHOD WOULD BE CALLING THE METHODS WHERE THE MOUSE INPUT VALUES ARE USED
            // IE, MY PREVIOUSLY POSTED Client::updateRotation() METHOD
            this->activeScene->outerLoop((float)this->frameTime, renderLerpInterval); // immediate logic updates
            
            this->gpu->clearBuffers(0.0f, 0.0f, 0.0f, 1.0f);
            this->activeScene->draw(renderLerpInterval);
        }
    }
}

void Window::setMouse() 
{
    double mXPos;
    double mYPos;

    glfwGetCursorPos(this->glfwWindow, &mXPos, &mYPos);

    this->inputState.mouseXPos = (float)mXPos;
    this->inputState.mouseYPos = (float)mYPos;
}

CodePudding user response:

Your mouse input is incorrect. Maybe you want to doublecheck that you get more serious values with another program like

<html>
<body>
<form name="Form1">
<textarea  type="text" name="posx" />
</textarea>
</form>
<script type="text/javascript">
var start = Date.now();
document.onmousemove = function (event)
{       
    if (Date.now() - start<10) return;
    start = Date.now();    
    document.Form1.posx.value  = start   "\t"  event.clientX  "\n" ;    
}
</script>
</body>
</html>

CodePudding user response:

You get smooth rotation with key press, because you add (subtract) a fixed value from the previous/actual one and increase (decrease) the rotation by a fixed amount. Every frame shows you a rotation by a fixed angle (smooth).

If you want to achieve the same result as key presses, use the same method.

Divide the screen into cells (the cell size is determined by your scaling factor, e.g. how many key strokes it needs for 180 degrees, which is equal to the screen width or a mouse motion from left to right). Based on your mouse position or moved distance, calculate the cell your pointer resides within or how many cells it moved, which gives you in return the desired angle for the rotation (by a fixed amount, just like key strokes).

Therefore, small changes of the mouse pointer will have no effect, only if the moved distance is greater than a cell size, then a rotation will occur (moved cells multiplied by a scaling factor).

  • Related