scene.unregisterBeforeRender not stopping ArcRotateCamera motion previously registered

Hi everyone!

I’m currently working on creating a camera (“Museum”) mode where, when the mode is toggled with a button click, the one ArcRotateCamera in the scene slowly, continuously rotates around its centerpoint. I have the button toggling the Redux state correctly, my handler is correctly printing the state, and the rotation motion is functioning as expected once turned on, but I can’t get it to turn off.

I’m using registerBeforeRender successfully whenever the Museum mode is toggled on, but unable to use unregisterBeforeRender when Museum mode is toggled off. Am I misunderstanding how to use it? (Note that I’ve tried to use unregisterBeforeRender in a conditional part of my registerBeforeRender function, a style which I’ve seen a couple places online, and I’ve tried to have registerBeforeRender and unregisterBeforeRender get called separately depending on my mode boolean:

/**
 * Used to store the previous state received by Redux.
 */
let previousState: StateProps = {
    museumModeOn: false
};

/**
 * Handler for dispatching messages to the CState Manager.
 * This is used for synchronizing state changes from the React Components
 * to CState.
 * @param state - The latest state update from the Global Redux State.
 * @param manager - The Cstate manager that should handle the state change.
 * @param dispatcher - Props object of dispatch methods.
 * @param scene - A reference to the BabylonJS Scene populated in onSceneReady.
 */
function stateChangeHandler(
    state: StateProps,
    manager: C.StateManager,
    dispatcher: DispatcherProps,
    scene: BABYLON.Scene
): void {

    if (previousState.museumModeOn !== state.museumModeOn) {
        // Register a function that rotates the ArcRotateCamera on each render when museumModeOn gets toggled to true
        scene.registerBeforeRender(function museumRotateCamera() {
            if (state.museumModeOn) {
                if (state.camera != null) {
                    state.camera.useAutoRotationBehavior = true;
                    state.camera.autoRotationBehavior!.idleRotationSpeed = 0.07;
                    state.camera.autoRotationBehavior!.idleRotationSpinupTime = 10;
                    state.camera.autoRotationBehavior!.idleRotationWaitTime = 10;
                }
            }
            // Unregister the function when museumModeOn gets toggled off
            else {
                if (state.camera != null) {
                    scene.unregisterBeforeRender(museumRotateCamera);
                }
            }
        });
    }
}

Here’s my render function:

/**
 * A Render Update Callback for Updating the state of the Scene.
 * @param scene - A reference to the BabylonJS Scene populated in onSceneReady.
 * @param options - Additional optional parameters.
 */
function onRender(scene: BABYLON.Scene, options: GraphRenderOptions, state: Props): void {
    const { dispatcher } = options;
    const fps = scene.getEngine().getFps();
    dispatcher.updateFps(fps);
}

A relevant wrapper:

// Wraps the onSceneReady callback to allow for additional parameters.
    const onSceneReadyWrapper = (scene: BABYLON.Scene): void =>
        onSceneReady(scene, { dispatcher: props, setOnStateChange });

And the OnSceneReady function that calls the StateChangeHandler above:


/**
 * Generates a new BabylonJS Scene.
 * @param scene - The initial scene created by the BabylonJS Scene Component.
 * @param options - Additional optional parameters.
 * @param graph - Additional optional parameters.
 */
function onSceneReady(scene: BABYLON.Scene, options: GraphSceneReadyOptions): void {
    // Set the scene to use a right handed coordinate system.
    scene.useRightHandedSystem = true;

    // Get the canvas HTMLElement from the scene engine.
    const canvas = scene.getEngine()!.getRenderingCanvas()!;

    // This creates and positions a rotating camera focused at position cameraFocus.
    const camera = new BABYLON.ArcRotateCamera(
        'camera1',
        Math.PI / 2,
        Math.PI / 2,
        5000,
        BABYLON.Vector3.Zero(),
        scene
    );

    // This attaches the camera to the canvas.
    // This feature is not allowed when using NullEngine since it does not contain getRenderingCanvas.
    if (process.env.NODE_ENV !== 'test') {
        camera.attachControl(canvas, true);
    }

    // Set various camera properties
    camera.lowerRadiusLimit = 1;
    camera.upperRadiusLimit = 10000;
    // Set the upVector of the camera. 
    camera.upVector = new BABYLON.Vector3(0, 0, 1);
    camera.beta = Math.PI / 2;
    camera.alpha = 0;

    // Setting up faster zooming and clipping changes to prevent
    // some undesired clipping when going too far.
    camera.wheelPrecision = 0.2;
    camera.minZ = 1;
    camera.maxZ = 50000;

    // Create a C Message Handler which will manage the state of the C Graph.
    const cachedSpriteManager = new CachedSpriteManager(scene);
    const graph = new C.Graph(scene, cachedSpriteManager);
    const hud = new C.HUD(scene);
    const stateManager = new C.StateManager(graph, hud, cachedSpriteManager);
    // Get dispatcher for reporting input and register onStateChange function.
    const { dispatcher, setOnStateChange } = options;

    setOnStateChange(
        () =>
            (props: StateProps): void =>
                stateChangeHandler(props, stateManager, dispatcher, scene)
    );
}

Either put your function to outer scope, like as class-function:

Or use it as variable:

2 Likes

when your camera is ArcRotateCamera
and target set

camera.lockedTarget = targetObject;

try to this

camera.useAutoRotationBehavior = true;

when you want stop try this

camera.useAutoRotationBehavior = false;

Hey there, thanks for the response! Can I ask how your suggestion is different than what I’m currently doing in the StateChangeHander, here? Maybe I’m missing something?

Hi, thanks so much for your reply and the playground. I recreated the formatting you’ve used, but I’m not seeing any change in the behavior:

    function museumRotateCamera() {
        if (state.camera != null) {
            state.camera.useAutoRotationBehavior = true;
            state.camera.autoRotationBehavior!.idleRotationSpeed = 0.07;
            state.camera.autoRotationBehavior!.idleRotationSpinupTime = 10;
            state.camera.autoRotationBehavior!.idleRotationWaitTime = 10;
        }
        // Unregister when museumModeOn gets toggled off
        if (!state.museumModeOn) {
            scene.unregisterBeforeRender(museumRotateCamera);
        }
    }

    if (previousState.museumModeOn !== state.museumModeOn) {
        console.log('Museum Mode:', state.museumModeOn);
        // Register rotation every render when museumModeOn gets toggled to true
        if (state.museumModeOn) {
            scene.registerBeforeRender(museumRotateCamera);
        }
    }

Hi, I don’t know exactly if you can unregister a function inside of itself, but why not something like this

function museumRotateCamera() {
  if (state.camera != null) {
      state.camera.useAutoRotationBehavior = true;
      state.camera.autoRotationBehavior!.idleRotationSpeed = 0.07;
      state.camera.autoRotationBehavior!.idleRotationSpinupTime = 10;
      state.camera.autoRotationBehavior!.idleRotationWaitTime = 10;
  }
}
...

if (state.museumModeOn) {
    scene.registerBeforeRender(museumRotateCamera);
}
else {
   scene.unregisterBeforeRender(museumRotateCamera);
}


Later edit, you can do this without register too

if (state.museumModeOn) {
   state.camera.useAutoRotationBehavior = true;
}
else {
   state.camera.useAutoRotationBehavior = false;
}