Help on smoothing out jittering in custom controls based on PointerLockControls

Hi guys, I would ask some help to optimize a custom controls I’ve made that use the official PointerLockControls. The controls implements a basic functionality to translate on the X and Z axis. The problem here is that I got a good amount of jittering in the movements on a mid-range laptop, even when the fps are not so bad (between 50 and 60). I guess there is something not well implemented in the translation logic, here is the code:

const startPoint = new T.Vector3(0, 0, 0)
const moveSpeed = 300 * this.scale
const offsetY = 2.5
let direction = new T.Vector3()
let velocity = new T.Vector3()

// reset camera position and rotation
this.camera.position.set(0, 0, 0)
this.camera.rotation.set(0, 0, 0)

this.controls = new T.PointerLockControls(this.camera, this.domElement)
const yawObject = this.controls.getObject()
yawObject.position.set(startPoint.x, offsetY, startPoint.z)
yawObject.rotation.y = Math.PI
this.scene.add(yawObject)

this.controls.forward =  false
this.controls.backward =  false
this.controls.left =  false
this.controls.right =  false

const onKeyDown = (e) => {
    switch (e.keyCode) {
        case 38: // up
        case 87: // w
            this.controls.forward = true
            break

        case 37: // left
        case 65: // a
            this.controls.left = true
            break

        case 40: // down
        case 83: // s
            this.controls.backward = true
            break

        case 39: // right
        case 68: // d
            this.controls.right = true
            break

        default:
            break
    }
}

const onKeyUp = (e) => {
    switch (e.keyCode) {
        case 38: // up
        case 87: // w
            this.controls.forward = false
            break

        case 37: // left
        case 65: // a
            this.controls.left = false
            break

        case 40: // down
        case 83: // s
            this.controls.backward = false
            break

        case 39: // right
        case 68: // d
            this.controls.right = false
            break

        default:
            break
    }
}

const onPointerLock = (e) => {
    
	// [...] start rendering
	this.renderingEnabled = true

}

const onPointerUnlock = (e) => {
    
	// [...] stop rendering
	this.renderingEnabled = false

}

document.addEventListener("keydown", onKeyDown, false)
document.addEventListener("keyup", onKeyUp, false)
this.controls.addEventListener("lock", onPointerLock, false)
this.controls.addEventListener("unlock", onPointerUnlock, false)


const X_axis = new T.Vector3(1, 0, 0)
const Z_axis = new T.Vector3(0, 0, 1)

let delta_1
let newX_1
let newZ_1

let delta_2
let newX_2
let newZ_2


const updateControls = (elapsedTime, deltaTime) => {    

    direction.z = Number(this.controls.forward) - Number(this.controls.backward);
    direction.x = Number(this.controls.left) - Number(this.controls.right);
    direction.normalize();

    if (this.controls.forward || this.controls.backward) {
        velocity.z = -direction.z * moveSpeed * deltaTime * deltaTime;
        delta_1 = Z_axis.clone().applyQuaternion(yawObject.quaternion).multiplyScalar(velocity.z)
        newX_1 = yawObject.position.x + delta_1.x
        newZ_1 = yawObject.position.z + delta_1.z        

        yawObject.position.set(newX_1, offsetY, newZ_1)

    }

    if (this.controls.left || this.controls.right) {
        velocity.x = -direction.x * moveSpeed * deltaTime * deltaTime;
        delta_2 = X_axis.clone().applyQuaternion(yawObject.quaternion).multiplyScalar(velocity.x)
        newX_2 = yawObject.position.x + delta_2.x
        newZ_2 = yawObject.position.z + delta_2.z        

        yawObject.position.set(newX_2, offsetY, newZ_2)

    }

}


const animate = () => {

    if (this.renderingEnabled) {
        
        this.deltaTime = this.clock.getDelta()
        this.elapsedTime = this.clock.elapsedTime

        updateControls(this.elapsedTime, this.deltaTime)

        this.renderer.render(this.scene, this.camera)
        
        requestAnimationFrame(animate)
        
    }

}


this.controls.lock()
animate()

Any idea for improvements?

Can you please try it with a different mouse and/or a mouse pad? I remember an issue some time ago similar to this one. It was not possible to fix this in code since the event data were already bad. Using a different mouse solved the issue…

Hi @Mugen87, the jittering is not related to the look around part (coming from the official PointerLockControls), that works just fine. The problem is with the translation part on X and Z axis done with the keyboard (wasd or arrows), it does not work smooth in my tests.

Okay. Do you experience the same with the official example?

https://threejs.org/examples/misc_controls_pointerlock

Or does it only happen in your application?

I got no jitter in that example.
I’ve tried to adapt the code using the example and I see a slight improvement (less jitter). Still it is not very smooth. These are some stats I’ve collected while testing:

Memory footprint: 150 Mb
GPU memory footprint: 220 ;b
Max draw calls: 77
Geometries: 58
Textures: 73
Traingles: 31725
FPS: 53-60

This is the code adapted from the example:

let offsetY = 2.5

let controls;
let moveForward = false;
let moveBackward = false;
let moveLeft = false;
let moveRight = false;

let prevTime = performance.now();
let time
let delta
let velocity = new THREE.Vector3();
let direction = new THREE.Vector3();

const startPoint = new T.Vector3(0, 0, 0)

this.camera.position.set(0, 0, 0)
this.camera.rotation.set(0, 0, 0)
this.controls = new T.PointerLockControls( this.camera, this.domElement);
this.controls.getObject().position.set(startPoint.x, offsetY, startPoint.z)
this.scene.add( this.controls.getObject() );

let overlay = document.createElement("div")
overlay.setAttribute("id", "pointerLockOverlay")
overlay.style.position = "absolute"
overlay.style.top = "0px"
overlay.style.left = "0px"
overlay.style.width = "100vw"
overlay.style.height = "100vh"
overlay.style.zIndex = "1"
overlay.style.background = "#000"
overlay.style.opacity = "0.5"
overlay.style.color = "#fff"
overlay.style.textAlign = "center"
overlay.style.verticalAlign = "middle"
overlay.style.lineHeight = "100vh"
overlay.textContent = "Click to enable controls!"
overlay.style.display = "none"
this.domElement.appendChild(overlay)

const overlayOnClik = () => {
    if (!this.controls.isLocked) {
        this.controls.lock()
        overlay.style.display = "none"
    }
}

overlay.addEventListener("click", overlayOnClik, false)

this.controls.addEventListener( 'lock', () => {

    overlay.style.display = "none"
    cameraTargetDiv.style.display = "block"
    this.startWebGL()

}, false );

this.controls.addEventListener( 'unlock', () => {

    overlay.style.display = "block"
    cameraTargetDiv.style.display = "none"
    this.stopWebGL()

}, false );

const onKeyDown = function ( event ) {

    switch ( event.keyCode ) {

        case 38: // up
        case 87: // w
            moveForward = true;
            break;

        case 37: // left
        case 65: // a
            moveLeft = true;
            break;

        case 40: // down
        case 83: // s
            moveBackward = true;
            break;

        case 39: // right
        case 68: // d
            moveRight = true;
            break;

        case 32: // space
            if ( canJump === true ) velocity.y += 350;
            canJump = false;
            break;

    }

};

const onKeyUp = function ( event ) {

    switch ( event.keyCode ) {

        case 38: // up
        case 87: // w
            moveForward = false;
            break;

        case 37: // left
        case 65: // a
            moveLeft = false;
            break;

        case 40: // down
        case 83: // s
            moveBackward = false;
            break;

        case 39: // right
        case 68: // d
            moveRight = false;
            break;

    }

};

document.addEventListener( 'keydown', onKeyDown, false );
document.addEventListener( 'keyup', onKeyUp, false );

const animate = () => {

    if ( this.controls.isLocked === true ) {

        time = performance.now();
        delta = ( time - prevTime ) / 1000;

        velocity.x -= velocity.x * 10.0 * delta;
        velocity.z -= velocity.z * 10.0 * delta;        

        direction.z = Number( moveForward ) - Number( moveBackward );
        direction.x = Number( moveLeft ) - Number( moveRight );
        direction.normalize(); // this ensures consistent movements in all directions

        if ( moveForward || moveBackward ) velocity.z -= direction.z * 50.0 * delta;
        if ( moveLeft || moveRight ) velocity.x -= direction.x * 50.0 * delta;

        this.controls.getObject().translateX( velocity.x * delta );        
        this.controls.getObject().translateZ( velocity.z * delta );        

        prevTime = time;

    }

}

What else can I do?

Um, do you see a difference when testing with Chrome and Firefox?

On firefox even worse than chrome :sweat_smile:

Sry, I have no idea how to solve this device specific issue. I would file a bug at a browser bug tracker and hope for the best^^

Ok! Thanks anyway for the help