Source: ui/world/world-persistence.js

import { VRSPACE } from '../../client/vrspace.js';
import { VRSPACEUI } from '../vrspace-ui.js';
import { World } from '../../world/world.js';

export class WorldPersistence {
  /**
   * @param {World} world 
   * @param {HTMLInputElement} [fileInput] 
   */
  constructor(world, fileInput) {
    /** @type {World} */
    this.world = world;
    this.autoCreateFileInput = true;
    /** @type {HTMLInputElement} */
    this.fileInput = null;
    if (fileInput) {
      this.setFileInput(fileInput);
      this.autoCreateFileInput = false;
    }
  }
  /**
   * Save current scene: dumps everything using AssetLoader.dump(), and calls VRSPACEUI.saveFile(). 
   */
  save() {
    VRSPACEUI.progressStart("saveFile");
    let scene = {
      terrain:null,
      skybox:null,
      objects:VRSPACEUI.assetLoader.dump()
    }
    if (this.world.sharedSkybox) {
      scene.skybox = {
        texture: this.world.sharedSkybox.texture,
        amibentIntensity: this.world.sharedSkybox.amibentIntensity
      }
    }
    if (this.world.sharedTerrain) {
      console.log(this.world.sharedTerrain);
      scene.terrain = {
        diffuseColor:this.world.sharedTerrain.diffuseColor,
        diffuseTexture:this.world.sharedTerrain.diffuseTexture,
        emissiveColor:this.world.sharedTerrain.emissiveColor,
        specularColor:this.world.sharedTerrain.specularColor,
        points:this.world.sharedTerrain.points
      }
    }
    if (Object.keys(scene.objects).length > 0) {
      VRSPACEUI.saveFile(this.world.name + ".json", JSON.stringify(scene));
    }
    VRSPACEUI.progressEnd("saveFile");
  }

  /**
   * Implements load by adding change listener to file input html element. Called from constructor.
   * @param fileInput html file input element
   */
  setFileInput(fileInput) {
    this.fileInput = fileInput;
    fileInput.addEventListener('change', () => {
      const selectedFile = fileInput.files[0];
      if (selectedFile) {
        VRSPACEUI.progressStart("loadFile");
        console.log("Loading from ", selectedFile);
        const reader = new FileReader();
        reader.onload = async e => {
          const scene = JSON.parse(e.target.result);
          console.log("Loaded scene", scene);
          await this.publish(scene);
          VRSPACEUI.progressEnd("loadFile");
        }
        reader.readAsText(selectedFile);
      }
    }, false);
  }

  /**
   * Create a hidden file input.
   */
  createFileInput() {
    const input = document.createElement('input');
    input.type = 'file';
    input.id = 'VRSpace-fileInput';
    input.style = 'display:none';
    input.accept = '.json';
    this.setFileInput(input);
  }

  /**
   * Load saved scene, requires file input html element
   */
  load() {
    if (!this.fileInput && this.autoCreateFileInput) {
      console.log("WARNING no file input element, creating one");
      this.createFileInput();
    }
    this.fileInput.click();
  }

  /**
   * Publish all loaded object to the server
   * @param scene scene object, containing world objects, terrain, skybox
   */
  async publish(scene) {
    for (let url in scene.objects) {
      let instances = scene.objects[url].instances;
      if (!url.startsWith("/")) {
        // relative url, make it relative to world script path
        url = this.baseUrl + url;
      }
      instances.forEach(async instance => {
        let mesh = {
          mesh: url,
          active: true,
          position: instance.position,
          rotation: instance.rotation,
          scale: instance.scale
        };
        let obj = await VRSPACE.createSharedObject(mesh);
        console.log("Created new VRObject", obj);
      });
    }
    if (scene.skybox) {
      // CHECKME: this hangs, why?
      //this.world.skyBox.setTexture(scene.skybox.texture);
      await this.world.createSharedSkybox();
      this.world.worldManager.VRSPACE.sendEvent(this.world.sharedSkybox, { texture: scene.skybox.texture });
    }
    if (scene.terrain) {
      if ( !this.world.terrain ) {
        this.world.terrain = new Terrain(this.world);
        this.world.terrain.terrainMaterial = new BABYLON.StandardMaterial("terrainMaterial", this.world.scene);
        this.world.terrain.init(this.world.scene);
      }
      this.world.terrain.material().specularColor = new BABYLON.Color3(scene.terrain.specularColor.r, scene.terrain.specularColor.g, scene.terrain.specularColor.b);
      this.world.terrain.material().diffuseColor = new BABYLON.Color3(scene.terrain.diffuseColor.r, scene.terrain.diffuseColor.g, scene.terrain.diffuseColor.b);
      this.world.terrain.material().emissiveColor = new BABYLON.Color3(scene.terrain.emissiveColor.r, scene.terrain.emissiveColor.g, scene.terrain.emissiveColor.b);
      this.world.terrain.setTexture(scene.terrain.diffuseTexture);

      await this.world.createSharedTerrain();
      this.terrain?.points?.forEach(point=>{
        this.worldManager.VRSPACE.sendEvent(World.lastInstance.sharedTerrain, { change: { index: point.index, point: {x:point.x, y:point.y, z:point.z} } });
      });
    }
  }
  
  
}