FreeCamera's keyboard input not working if render loop not triggered from engine

Hi all, I created a FreeCamera and registered WASD events.

var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);
camera.keysUp = [87]; 
camera.keysDown = [83]; 
camera.keysLeft = [65]; 
camera.keysRight = [68]; 
camera.keysRotateLeft = []; 
camera.keysRotateRight = []; 
camera.keysUpward = [81]; 
camera.keysDownward = [69]; 

If the render loop is triggered from the engine.runRenderLoop(), then the rotation and WASD of this camera work well.

engine.runRenderLoop(() => {
    scene.render();
});

But if I use requestAnimationFrame to trigger this, then only the rotation works and the WASD has no response.

const renderFrame = () => {
    scene.render();
    requestAnimationFrame(renderFrame);
}
requestAnimationFrame(renderFrame);

Is this behavior expected or a bug?

1 Like

Hello :slight_smile:

I don’t know if it’s expected, or a bug.
My guess is that engine.runRenderLoop(callback) adds a listener to the keyboard, in addition to the adding the callback to the active render loops.

If you want to to trigger render like you did, and still preserve the keyboard listener, maybe you could try to still run

engine.runRenderLoop(() => {});

Without explicitly triggering the scene render inside

++
Tricotou

3 Likes

Thank you Tricotou. It works!

1 Like

@Devs : I had a look at source code, and indeed as I can see HERE :

public runRenderLoop(renderFunction: () => void): void {
    if (this._activeRenderLoops.indexOf(renderFunction) !== -1) {
        return;
    }

    this._activeRenderLoops.push(renderFunction);

    // On the first added function, start the render loop.
    if (this._activeRenderLoops.length === 1 && this._frameHandler === 0) {
        this._frameHandler = this._queueNewFrame(this._boundRenderFunction, this.getHostWindow());
    }
}

The function runRenderLoop starts the render loop on the first call. But it appears that if you call engine.runRenderLoop(); it triggers an error, undefined is pushed to activeRenderLoops
and then a call it triggered on this undefined function : TypeError: (0 , this._activeRenderLoops[e]) is not a function. The only way to call the runRenderLoop is to actually send an empty function. Maybe this could be changed (Ex : ignoring the callback if undefined, but still launch the render loop)

2 Likes

I have found out the reason for this behavior. The FreeCamera (as well as other cameras) calculates the move speed from the engine’s delta time and FPS. If no render loop has ever been registered to the engine, then the engine’s delta time and FPS will be invalid, so the speed for the camera will always be zero.

// targetCamera.ts

// Methods
/** @internal */
public _computeLocalCameraSpeed(): number {
    const engine = this.getEngine();
    return this.speed * Math.sqrt(engine.getDeltaTime() / (engine.getFps() * 100.0));
}

So the exact behavior is, for all cameras, if engine.runRenderLoop() has never been called, then the movement input will be inactive.

As @Tricotou said, we can register an empty function to bypass this problem.

engine.runRenderLoop(() => {});

I personally think this behavior is a little bit confusing. Delta time and FPS should also be available if the user chooses to trigger scene.render() or camera.update() on their own. (e.g. the render on demand thread)

Let’s see if other experts have any suggestions for this.

1 Like

can you try to call engine.startFrame(); and engine.endFrame(); around your code so you would do the same as what engine does in runRenderLoop :slight_smile:

2 Likes

Using beginFrame() alone does not work :rofl:

Seems like beginFrame() will not start the actual render loop _boundRenderFunction()

1 Like

yes it is not meant to call it.

beginFrame computes some timing thinggies then you can call your scene.render and finally endFrame.

This is what is done internally in runRenderLoop

2 Likes