Source: ui/world/terrain-editor.js

import { VRSPACEUI } from '../vrspace-ui.js';
import { World } from '../../world/world.js';
import { TextureSelector } from './texture-selector.js';

export class TerrainEditor {
  /**
   * @param {World} world 
   */
  constructor(world) {
    this.world = world;
    this.scene = world.scene;
    this.terrain = world.terrain;
    this.heightIncrement = 1;
    this.editing = false;
    this.textureSelector = new TextureSelector(this.scene, (img) => this.publishTexture(img));
    // add own selection predicate to the world
    this.selectionPredicate = (mesh) => this.isSelectableMesh(mesh);
    world.addSelectionPredicate(this.selectionPredicate);
    this.observer = null;
    this.groundPickable = null;
    this.movementMode = null;
  }

  createSharedTerrain() {
    if (!World.lastInstance.sharedTerrain) {
      var object = {
        permanent: true,
        active: true,
        specularColor: this.terrain.terrainMaterial.specularColor,
        diffuseColor: this.terrain.terrainMaterial.diffuseColor,
        emissiveColor: this.terrain.terrainMaterial.emissiveColor
      };
      this.world.worldManager.VRSPACE.createSharedObject(object, "Terrain").then(obj => {
        console.log("Created new Terrain", obj);
        World.lastInstance.sharedTerrain = obj;
      });
    }
  }

  edit() {
    this.createSharedTerrain();
    if (!this.observer) {
      this.observer = this.scene.onPointerObservable.add((pointerInfo) => {
        switch (pointerInfo.type) {
          case BABYLON.PointerEventTypes.POINTERDOWN:
            if (pointerInfo.pickInfo.hit && pointerInfo.pickInfo.pickedMesh == this.terrain.mesh()) {
              this.lastIndex = this.updatePicked(pointerInfo.pickInfo);
              this.terrain.mesh().enablePointerMoveEvents = true;
            }
            break;
          case BABYLON.PointerEventTypes.POINTERUP:
            this.lastIndex = -1;
            this.terrain.mesh().enablePointerMoveEvents = false;
            break;
          case BABYLON.PointerEventTypes.POINTERMOVE:
            if (this.lastIndex >= 0 && pointerInfo.pickInfo.pickedMesh == this.terrain.mesh()) {
              var newIndex = this.terrain.findIndex(pointerInfo.pickInfo.pickedPoint.x, pointerInfo.pickInfo.pickedPoint.z);
              if (newIndex != this.lastIndex) {
                this.lastIndex = newIndex;
                this.updatePicked(pointerInfo.pickInfo);
              }
            }
            break;
        }
      });
    }

    if (World.lastInstance.ground) {
      this.groundPickable = World.lastInstance.ground.isPickable;
      World.lastInstance.ground.isPickable = false;
    }

    this.raiseButton = VRSPACEUI.hud.addButton("Raise", VRSPACEUI.contentBase + "/content/icons/upload.png");
    this.digButton = VRSPACEUI.hud.addButton("Dig", VRSPACEUI.contentBase + "/content/icons/download.png");
    this.textureButton = VRSPACEUI.hud.addButton("Texture", VRSPACEUI.contentBase + "/content/icons/terrain-texture.png");
    this.raiseSlider = VRSPACEUI.hud.addSlider("Height", 0, 2, this.heightIncrement)

    this.raiseButton.onPointerDownObservable.add(() => {
      this.direction = 1;
      VRSPACEUI.hud.showButtons(this.editing, this.raiseButton, this.raiseSlider);
      this.world.enableFloorSelection(this.editing);
      this.enableMovement(this.editing);
      this.editing = !this.editing;
      this.terrain.terrainMaterial.wireframe = this.editing;
    });
    this.digButton.onPointerDownObservable.add(() => {
      this.direction = -1;
      VRSPACEUI.hud.showButtons(this.editing, this.digButton, this.raiseSlider);
      this.world.enableFloorSelection(this.editing);
      this.enableMovement(this.editing);
      this.editing = !this.editing;
      this.terrain.terrainMaterial.wireframe = this.editing;
    });
    this.textureButton.onPointerDownObservable.add(() => {
      this.editing = !this.editing;
      if (this.editing) {
        this.textureSelector.show();
      } else {
        this.textureSelector.hide();
      }
      VRSPACEUI.hud.showButtons(!this.editing, this.textureButton);
    });
    this.raiseSlider.onValueChangedObservable.add(value => { this.heightIncrement = value });

    this.diffusePicker = VRSPACEUI.hud.addColorPicker("Diffuse", this.terrain.terrainMaterial.diffuseColor);
    this.specularPicker = VRSPACEUI.hud.addColorPicker("Specular", this.terrain.terrainMaterial.specularColor);
    this.emissivePicker = VRSPACEUI.hud.addColorPicker("Emissive", this.terrain.terrainMaterial.emissiveColor);
    this.specularPicker.onValueChangedObservable.add((val) => {
      //this.terrain.terrainMaterial.specularColor.copyFrom(val);
      this.world.worldManager.VRSPACE.sendEvent(World.lastInstance.sharedTerrain, { specularColor: val });
    });
    this.diffusePicker.onValueChangedObservable.add((val) => {
      //this.terrain.terrainMaterial.diffuseColor.copyFrom(val);
      this.world.worldManager.VRSPACE.sendEvent(World.lastInstance.sharedTerrain, { diffuseColor: val });
    });
    this.emissivePicker.onValueChangedObservable.add((val) => {
      //this.terrain.terrainMaterial.emissiveColor.copyFrom(val);
      this.world.worldManager.VRSPACE.sendEvent(World.lastInstance.sharedTerrain, { emissiveColor: val });
    });

    VRSPACEUI.hud.enableSpeech(true);
  }

  enableMovement(enable) {
    if (!enable) {
      this.movementMode = this.world.xrHelper.disableMovement();
    } else if (this.movementMode) {
      this.world.xrHelper.enableMovement(this.movementMode);
    }
  }

  updatePicked(pickInfo) {
    var index = -1;
    var x = pickInfo.pickedPoint.x;
    var z = pickInfo.pickedPoint.z;
    if (this.editing) {
      var online = this.world.isOnline() && World.lastInstance.sharedTerrain;
      if (online) {
        // if online, terrain is not refreshed until the server responds with updated height
        index = this.terrain.findIndex(x, z);
        if (index) {
          var point = this.terrain.point(index);
          point.y += this.heightIncrement * this.direction;
          // publish updates
          var change = { change: { index: index, point: point } };
          this.world.worldManager.VRSPACE.sendEvent(World.lastInstance.sharedTerrain, change);
        } else {
          console.log("ERROR: index " + index + " for " + x + "," + z);
        }
      } else {
        index = this.terrain.raise(x, z, this.heightIncrement * this.direction, online);
      }
    }
    return index;
  }

  publishTexture(imgUrl) {
    console.log("Publishing texture: " + imgUrl);
    this.world.worldManager.VRSPACE.sendEvent(World.lastInstance.sharedTerrain, { diffuseTexture: imgUrl });
  }

  dispose() {
    this.world.removeSelectionPredicate(this.selectionPredicate);
    this.world.removeListener(this);
    this.terrain.terrainMaterial.wireframe = false;
    this.scene.onPointerObservable.remove(this.observer);
    this.raiseButton.dispose();
    this.digButton.dispose();
    this.textureButton.dispose();
    this.raiseSlider.dispose();
    if (World.lastInstance.ground) {
      World.lastInstance.ground.isPickable = this.groundPickable;
    }
    this.enableMovement(true);
  }

  isSelectableMesh(mesh) {
    // terrain is selectable only while editing
    return this.editing && this.terrain && mesh == this.terrain.mesh();
  }
}