NGL@1.0.0-beta.7 Home Manual Reference Source Gallery

src/component/annotation.js

/**
 * @file Annotation
 * @author Alexander Rose <alexander.rose@weirdbyte.de>
 * @private
 */

import { Vector3 } from '../../lib/three.es6.js'

import { defaults } from '../utils.js'
import { smoothstep } from '../math/math-utils.js'

/**
 * Annotation HTML element floating on top of a position rendered in 3d
 */
class Annotation {
    /**
     * @param {Component} component - the associated component
     * @param {Vector3} position - position in 3d
     * @param {String|Element} content - HTML content
     * @param {Object} [params] - parameters
     * @param {Integer} params.offsetX - 2d offset in x direction
     * @param {Integer} params.offsetY - 2d offset in y direction
     * @param {Boolean} params.visible - visibility flag
     */
  constructor (component, position, content, params) {
    const p = params || {}

    this.offsetX = defaults(p.offsetX, 0)
    this.offsetY = defaults(p.offsetY, 0)
    this.visible = defaults(p.visible, true)

    this.component = component
    this.stage = component.stage
    this.viewer = this.stage.viewer
    this.position = position

    this._viewerPosition = new Vector3()
    this._updateViewerPosition()
    this._canvasPosition = new Vector3()
    this._cameraPosition = new Vector3()

    this.element = document.createElement('div')
    Object.assign(this.element.style, {
      display: 'block',
      position: 'fixed',
      zIndex: 1 + (parseInt(this.viewer.container.style.zIndex) || 0),
      pointerEvents: 'none',
      backgroundColor: 'rgba( 0, 0, 0, 0.6 )',
      color: 'lightgrey',
      padding: '8px',
      fontFamily: 'sans-serif',
      left: '-10000px'
    })

    this.viewer.container.appendChild(this.element)
    this.setContent(content)
    this.updateVisibility()
    this.viewer.signals.ticked.add(this._update, this)
    this.component.signals.matrixChanged.add(this._updateViewerPosition, this)
  }

    /**
     * Set HTML content of the annotation
     * @param {String|Element} value - HTML content
     * @return {undefined}
     */
  setContent (value) {
    const displayValue = this.element.style.display
    if (displayValue === 'none') {
      this.element.style.left = '-10000px'
      this.element.style.display = 'block'
    }

    if (value instanceof window.Element) {
      this.element.innerHTML = ''
      this.element.appendChild(value)
    } else {
      this.element.innerHTML = value
    }

    this._clientRect = this.element.getBoundingClientRect()

    if (displayValue === 'none') {
      this.element.style.display = displayValue
    }
  }

    /**
     * Set visibility of the annotation
     * @param {Boolean} value - visibility flag
     * @return {undefined}
     */
  setVisibility (value) {
    this.visible = value
    this.updateVisibility()
  }

  getVisibility () {
    return this.visible && this.component.visible
  }

  updateVisibility () {
    this.element.style.display = this.getVisibility() ? 'block' : 'none'
  }

  _updateViewerPosition () {
    this._viewerPosition
            .copy(this.position)
            .applyMatrix4(this.component.matrix)
  }

  _update () {
    if (!this.getVisibility()) return

    const s = this.element.style
    const cp = this._canvasPosition
    const vp = this._viewerPosition
    const cr = this._clientRect

    this._cameraPosition.copy(vp)
            .add(this.viewer.translationGroup.position)
            .applyMatrix4(this.viewer.rotationGroup.matrix)
            .sub(this.viewer.camera.position)

    if (this._cameraPosition.z < 0) {
      s.display = 'none'
      return
    } else {
      s.display = 'block'
    }

    s.opacity = 1 - smoothstep(
            this.viewer.scene.fog.near,
            this.viewer.scene.fog.far,
            this._cameraPosition.length()
        )

    this.stage.viewerControls.getPositionOnCanvas(vp, cp)

    s.bottom = (this.offsetX + cp.y + cr.height / 2) + 'px'
    s.left = (this.offsetY + cp.x - cr.width / 2) + 'px'
  }

    /**
     * Safely remove the annotation
     * @return {undefined}
     */
  dispose () {
    this.viewer.container.removeChild(this.element)
    this.viewer.signals.ticked.remove(this._update, this)
    this.component.signals.matrixChanged.remove(this._updateViewerPosition, this)
  }
}

export default Annotation