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

src/controls/viewer-controls.js

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

import { Vector3, Matrix4, Quaternion } from '../../lib/three.es6.js'
import Signal from '../../lib/signals.es6.js'

import {
  ensureVector2, ensureVector3, ensureMatrix4, ensureQuaternion
} from '../utils.js'

/**
 * Orientation matrix, a 4x4 transformation matrix with rotation part
 * used for scene rotation, scale part for scene camera distance and
 * position part for scene translation
 * @typedef {Matrix4} OrientationMatrix - orientation matrix
 */

const tmpQ = new Quaternion()
const tmpP = new Vector3()
const tmpS = new Vector3()

const tmpCanvasVector = new Vector3()
const tmpScaleVector = new Vector3()
const tmpRotateMatrix = new Matrix4()
const tmpRotateVector = new Vector3()
const tmpAlignMatrix = new Matrix4()

/**
 * Viewer controls
 */
class ViewerControls {
  /**
   * @param  {Stage} stage - the stage object
   */
  constructor (stage) {
    this.stage = stage
    this.viewer = stage.viewer

    /**
     * @type {{changed: Signal}}
     */
    this.signals = {
      changed: new Signal()
    }
  }

  /**
   * scene center position
   * @type {Vector3}
   */
  get position () {
    return this.viewer.translationGroup.position
  }

  /**
   * scene rotation
   * @type {Quaternion}
   */
  get rotation () {
    return this.viewer.rotationGroup.quaternion
  }

  /**
   * Trigger render and emit changed event
   * @emits {ViewerControls.signals.changed}
   * @return {undefined}
   */
  changed () {
    this.viewer.requestRender()
    this.signals.changed.dispatch()
  }

  getPositionOnCanvas (position, optionalTarget) {
    const canvasPosition = ensureVector2(optionalTarget)
    const viewer = this.viewer

    tmpCanvasVector.copy(position)
      .add(viewer.translationGroup.position)
      .applyMatrix4(viewer.rotationGroup.matrix)
      .project(viewer.camera)

    return canvasPosition.set(
      (tmpCanvasVector.x + 1) * viewer.width / 2,
      (tmpCanvasVector.y + 1) * viewer.height / 2
    )
  }

  /**
   * get scene orientation
   * @param {Matrix4} optionalTarget - pre-allocated target matrix
   * @return {OrientationMatrix} scene orientation
   */
  getOrientation (optionalTarget) {
    const m = ensureMatrix4(optionalTarget)

    m.copy(this.viewer.rotationGroup.matrix)
    const z = -this.viewer.camera.position.z
    m.scale(tmpScaleVector.set(z, z, z))
    m.setPosition(this.viewer.translationGroup.position)

    return m
  }

  /**
   * set scene orientation
   * @param {OrientationMatrix|Array} orientation - scene orientation
   * @return {undefined}
   */
  orient (orientation) {
    ensureMatrix4(orientation).decompose(tmpP, tmpQ, tmpS)

    const v = this.viewer
    v.rotationGroup.setRotationFromQuaternion(tmpQ)
    v.translationGroup.position.copy(tmpP)
    v.camera.position.z = -tmpS.z
    v.updateZoom()
    this.changed()
  }

  /**
   * translate scene
   * @param  {Vector3|Array} vector - translation vector
   * @return {undefined}
   */
  translate (vector) {
    this.viewer.translationGroup.position
      .add(ensureVector3(vector))
    this.changed()
  }

  /**
   * center scene
   * @param  {Vector3|Array} position - center position
   * @return {undefined}
   */
  center (position) {
    this.viewer.translationGroup.position
      .copy(ensureVector3(position)).negate()
    this.changed()
  }

  /**
   * zoom scene
   * @param  {Number} delta - zoom change
   * @return {undefined}
   */
  zoom (delta) {
    this.distance(this.viewer.camera.position.z * (1 - delta))
  }

  /**
   * camera distance
   * @param  {Number} z - distance
   * @return {undefined}
   */
  distance (z) {
    this.viewer.camera.position.z = z
    this.viewer.updateZoom()
    this.changed()
  }

  /**
   * spin scene on axis
   * @param  {Vector3|Array} axis - rotation axis
   * @param  {Number} angle - amount to spin
   * @return {undefined}
   */
  spin (axis, angle) {
    tmpRotateMatrix.getInverse(this.viewer.rotationGroup.matrix)
    tmpRotateVector
      .copy(ensureVector3(axis)).applyMatrix4(tmpRotateMatrix)

    this.viewer.rotationGroup.rotateOnAxis(tmpRotateVector, angle)
    this.changed()
  }

  /**
   * rotate scene
   * @param  {Quaternion|Array} quaternion - rotation quaternion
   * @return {undefined}
   */
  rotate (quaternion) {
    this.viewer.rotationGroup
      .setRotationFromQuaternion(ensureQuaternion(quaternion))
    this.changed()
  }

  /**
   * align scene to basis matrix
   * @param  {Matrix4|Array} basis - basis matrix
   * @return {undefined}
   */
  align (basis) {
    tmpAlignMatrix.getInverse(ensureMatrix4(basis))

    this.viewer.rotationGroup.setRotationFromMatrix(tmpAlignMatrix)
    this.changed()
  }

  /**
   * apply rotation matrix to scene
   * @param  {Matrix4|Array} matrix - rotation matrix
   * @return {undefined}
   */
  applyMatrix (matrix) {
    this.viewer.rotationGroup.applyMatrix(ensureMatrix4(matrix))
    this.changed()
  }
}

export default ViewerControls