How to properly save and load a scene?

I’m trying to implement a saving and loading feature on a scene made with imported GLB models, a dynamic skybox and post-processing effects (also contains a GizmoManager) by following this guide, but I’m getting an error when loading the saved scene. What I want is to save the scene completely to a .babylon file. On loading a scene from a .babylon file, I want the new scene to completely replace the current scene.

PLAYGROUND LINK (Press 3 to save and 4 to load)

When I try to load the saved scene, I’m getting this error:

The loading part work for simple scenes like basicScene.zip (52.0 KB) (but not in the playground), so I’m guessing the problem is in the saving part?

I got the same error when I exported the scene with the inspector, but it seems to work fine when exported as a GLB file instead of .babylon file. I can use the GLTF exporter, but it seems like some things are not supported yet.

Relevant code:

/**
* Serializes the scene and saves it to a file.
*/
saveScene() {
    if (!this.scene) {
        throw new Error("No scene");
    }

    if (this.savedSceneURL) {
        window.URL.revokeObjectURL(this.savedSceneURL);
    }

    const serializedScene = BABYLON.SceneSerializer.Serialize(this.scene);

    const strMesh = JSON.stringify(serializedScene);

    if (
        this.savedSceneFilename.toLowerCase().lastIndexOf(".babylon") !==
        this.savedSceneFilename.length - 8 ||
        this.savedSceneFilename.length < 9
    ) {
        this.savedSceneFilename += ".babylon";
    }

    const blob = new Blob([strMesh], { type: "octet/stream" });

    // turn blob into an object URL; saved as a member, so can be cleaned out later
    this.savedSceneURL = (window.webkitURL || window.URL).createObjectURL(blob);

    const link = window.document.createElement("a");
    link.href = this.savedSceneURL;
    link.download = this.savedSceneFilename;
    const clickEvent = new MouseEvent("click", {
        view: window,
        bubbles: true,
        cancelable: false,
    });
    link.dispatchEvent(clickEvent);
}

/**
 * Loads a scene from a file.
 */
loadScene() {
    const fileInput = document.createElement("input");
    fileInput.type = "file";
    fileInput.accept = ".babylon";

    fileInput.onchange = (event) => {
        const file = (event.target as HTMLInputElement).files?.[0];

        if (file) {
            const fileURL = URL.createObjectURL(file);
            console.log(fileURL);
            BABYLON.SceneLoader.LoadAsync("", fileURL, this.engine, null, ".babylon").then(
                (scene: BABYLON.Scene) => {
                    if (scene) {
                        this.scene?.dispose();
                        this.scene = scene;
                        this.scene.activeCamera = this._createController();
                    }
                }
            );
        }
    };

    fileInput.click();
}

Hmm I did some debugging for you;

  1. First I added BABYLON.SceneLoader.loggingLevel = BABYLON.SceneLoader.DETAILED_LOGGING; to display more information;

  2. I checked the generated .babylon file and tried to see what was causing the issue;

  3. if you treat the .babylon file like a JSON and remove this one single line:
    image
    your scene will load fine.
    Not too sure why, either it’s a bug on babylon’s side or it’s because the environment texture is badly referenced
    4.Therefore I added delete serializedScene.environmentTexture before stringifying it.

I also changed the way you recorded key record because it kept spamming the window…

Anyway, hope this helps. Of course what I did in step 4 may affect how the environment looks after loading the scene, maybe you can set scene.environmentTexture = ... after loading the scene (since the texture/material should still exist)

2 Likes

Thanks!