Source: ui/widget/base-area.js

/**
 * Base class common for TextArea and ImageArea, possibly more to come.
 * Provides methods to attach the area to HUD or camera, and common variables.
 */
export class BaseArea {
  constructor(scene, name) {
    this.scene = scene;
    this.name = name;
    this.size = .2;
    this.position = BABYLON.Vector3.ZERO; // OVERRIDE!
    this.addHandles = true;
    this.canMinimize = true;
    this.group = new BABYLON.TransformNode(name, this.scene);
    this.attachedToHud = false;
    this.attachedToCamera = false;
    this.areaPlane = null;
    this.handles = null;
    this.texture = null;
    this.material = null;
    this.billboardMode = BABYLON.Mesh.BILLBOARDMODE_NONE;
  }
  
  /**
   * Attach it to the HUD. It does not resize automatically, just sets the parent.
   * Optionally also attaches handles to the hud, if they are active.
   */
  attachToHud() {
    this.group.parent = VRSPACEUI.hud.root;
    this.group.position = this.position;
    this.attachedToCamera = false;
    this.attachedToHud = true;
    VRSPACEUI.hud.addAttachment(this.areaPlane);
    if ( this.handles ) {
      this.handles.handles.forEach( h => VRSPACEUI.hud.addAttachment(h));
      VRSPACEUI.hud.addAttachment(this.handles.box);
    }
  }
 
  /**
   * Attach it to the camera. It does not resize automatically, just sets the parent.
   * It does not automatically switch to another camera if active camera changes.
   * @param camera currently active camera
   */
  attachToCamera(camera = this.scene.activeCamera) {
    this.group.parent = camera;
    this.group.position = this.position;
    this.attachedToCamera = true;
    this.attachedToHud = false;
    VRSPACEUI.hud.removeAttachment(this.areaPlane);
    if ( this.handles ) {
      this.handles.handles.forEach( h => VRSPACEUI.hud.addAttachment(h));
      VRSPACEUI.hud.removeAttachment(this.handles.box);
    }
  }
  
  /**
   * Detach from whatever attached to, i.e. drop it where you stand.
   */
  detach() {
    this.group.parent = null;
    let camera = this.scene.activeCamera;
    // just in front of camera:
    this.group.position = camera.position.add(camera.getForwardRay(1).direction.scale(this.position.z));
    this.attachedToCamera = false;
    this.attachedToHud = false;
    VRSPACEUI.hud.removeAttachment(this.areaPlane);
    if ( this.handles ) {
      this.handles.handles.forEach( h => VRSPACEUI.hud.addAttachment(h));
      VRSPACEUI.hud.removeAttachment(this.handles.box);
    }
  }
 
  /**
  * Removes manipulation handles.
  */
  removeHandles() {
    if ( this.handles ) {
      this.handles.dispose();
      this.handles = null;
    }
  }
 
  /**
  * XR pointer support
  */
  isSelectableMesh(mesh) {
    return mesh == this.areaPlane && this.areaPlane.isEnabled() || (this.handles && !this.handles.minimized && this.handles.handles.includes(mesh)); 
  }

  /** Clean up allocated resources */
  dispose() {
    this.detach();
    this.removeHandles();
    if ( this.areaPlane ) {
      this.areaPlane.dispose();
    }
    if ( this.material ) {
      this.material.dispose();
    }
  }
}