Source: ui/world/terrain-editor.js

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

export class TerrainEditor extends WorldListener {
  /**
   * @param {World} world 
   */
  constructor(world) {
    super();
    this.world = world;
    this.scene = world.scene;
    this.terrain = world.terrain;
    this.heightIncrement=1;
    this.sharedTerrain = null;
    this.editing = false;
    world.addListener(this);
    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);
  }
  /** Called by WorldManager when user enters the world */
  entered(welcome) {
    //console.log(welcome);
    if ( welcome.permanents ) {
      welcome.permanents.forEach( obj => {
        if (obj.Terrain) {
          this.sharedTerrain = obj.Terrain;
          if ( obj.Terrain.points ) {
            obj.Terrain.points.forEach( p => {
              this.terrain.update(p.index, p.x, p.y, p.z);
            });
            this.terrain.refresh();
          }
          if ( obj.Terrain.specularColor ) {
            this.terrain.terrainMaterial.specularColor = new BABYLON.Color3(obj.Terrain.specularColor.r, obj.Terrain.specularColor.g, obj.Terrain.specularColor.b)
          }
          if ( obj.Terrain.diffuseColor ) {
            this.terrain.terrainMaterial.diffuseColor = new BABYLON.Color3(obj.Terrain.diffuseColor.r, obj.Terrain.diffuseColor.g, obj.Terrain.diffuseColor.b)
          }
          if ( obj.Terrain.emissiveColor ) {
            this.terrain.terrainMaterial.emissiveColor = new BABYLON.Color3(obj.Terrain.emissiveColor.r, obj.Terrain.emissiveColor.g, obj.Terrain.emissiveColor.b)
          }
          if ( obj.Terrain.diffuseTexture ) {
            this.setTexture(obj.Terrain.diffuseTexture);
          }
        };
      });
    } 
  }
  
  added(added) {
    if ( added && added.className == "Terrain") {
      console.log("Terrain added", added);
      this.sharedTerrain = added;
      added.addListener((obj,change)=>this.terrainChanged(change));
      this.terrain.mesh().setEnabled(true);
    }
  }
  
  terrainChanged(e) {
    console.log("Terrain changed", e);
    if ( e.change ) {
      this.terrain.update(e.change.index, e.change.point.x, e.change.point.y, e.change.point.z);
      this.terrain.refresh();
    } else {
      for ( const field in e ) {
        if ( field.indexOf('Color') > 0 ) {
          // e.g. emissiveColor, diffuseColor, specularColor
          console.log(field + "="+e[field]);
          this.terrain.terrainMaterial[field] = new BABYLON.Color3(e[field].r, e[field].g, e[field].b);
        } else if (field.indexOf('Texture') > 0) {
          this.setTexture(e[field]);
        }
      }
    }
  }
  
  createSharedTerrain() {
    if ( ! this.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);
        this.sharedTerrain = obj;
      });
    }
  }
  
  edit() {
    this.createSharedTerrain();
    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;
        }
    });
    
    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.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.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);
    });
    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(this.sharedTerrain, {specularColor:val});      
    });
    this.diffusePicker.onValueChangedObservable.add( (val) => {
      //this.terrain.terrainMaterial.diffuseColor.copyFrom(val);
      this.world.worldManager.VRSPACE.sendEvent(this.sharedTerrain, {diffuseColor:val});      
    });
    this.emissivePicker.onValueChangedObservable.add( (val) => {
      //this.terrain.terrainMaterial.emissiveColor.copyFrom(val);
      this.world.worldManager.VRSPACE.sendEvent(this.sharedTerrain, {emissiveColor:val});      
    });
    
    VRSPACEUI.hud.enableSpeech(true);
  }
  
  updatePicked( pickInfo ) {
    var index = -1;
    var x = pickInfo.pickedPoint.x;
    var z = pickInfo.pickedPoint.z;
    if ( this.editing ) {
      var online = this.world.isOnline() && this.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(this.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(this.sharedTerrain, {diffuseTexture:imgUrl});
  }
  setTexture(imgUrl) {
    console.log("New texture: "+imgUrl);
    if ( this.terrainTexture ) {
      this.terrainTexture.dispose();
    }
    this.terrainTexture = new BABYLON.Texture(imgUrl, this.scene);
    this.terrain.mesh().material.diffuseTexture = this.terrainTexture;
  }
  dispose() {
    this.world.removeSelectionPredicate(this.selectionPredicate);
    this.world.removeListener(this);
    // ..etc, CHECKME
  }
  isSelectableMesh(mesh) {
    // terrain is selectable only while editing
    return this.editing && this.terrain && mesh == this.terrain.mesh();
  }
}