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

src/controls/trackball-controls.js

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

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

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

const tmpRotateXMatrix = new Matrix4()
const tmpRotateYMatrix = new Matrix4()
const tmpRotateMatrix = new Matrix4()
const tmpRotateVector = new Vector3()
const tmpRotateQuaternion = new Quaternion()
const tmpPanMatrix = new Matrix4()
const tmpPanVector = new Vector3()
const tmpAtomVector = new Vector3()

/**
 * Trackball controls
 */
class TrackballControls {
  constructor (stage, params) {
    const p = params || {}

    this.rotateSpeed = defaults(p.rotateSpeed, 2.0)
    this.zoomSpeed = defaults(p.zoomSpeed, 1.2)
    this.panSpeed = defaults(p.panSpeed, 1.0)

    this.stage = stage
    this.viewer = stage.viewer
    this.mouse = stage.mouseObserver
    this.controls = stage.viewerControls
  }

  get component () {
    return this.stage.transformComponent
  }

  get atom () {
    return this.stage.transformAtom
  }

  _setPanVector (x, y, z) {
    let scaleFactor
    const camera = this.viewer.camera

    z = -z || 0
    z += camera.position.z

    if (camera.type === 'OrthographicCamera') {
      scaleFactor = 1 / camera.zoom
    } else {
      const fov = degToRad(camera.fov)
      const unitHeight = -2.0 * z * Math.tan(fov / 2)
      scaleFactor = unitHeight / this.viewer.height
    }

    tmpPanVector.set(x, y, 0)
    tmpPanVector.multiplyScalar(this.panSpeed * scaleFactor)
  }

  _getRotateXY (x, y) {
    return [
      this.rotateSpeed * -x * 0.01,
      this.rotateSpeed * y * 0.01
    ]
  }

  _transformPanVector () {
    tmpPanMatrix.extractRotation(this.component.transform)
    tmpPanMatrix.premultiply(this.viewer.rotationGroup.matrix)
    tmpPanMatrix.getInverse(tmpPanMatrix)
    tmpPanVector.applyMatrix4(tmpPanMatrix)
  }

  zoom (delta) {
    this.controls.zoom(this.zoomSpeed * delta * 0.02)
  }

  pan (x, y) {
    this._setPanVector(x, y)

    tmpPanMatrix.getInverse(this.viewer.rotationGroup.matrix)
    tmpPanVector.applyMatrix4(tmpPanMatrix)
    this.controls.translate(tmpPanVector)
  }

  panComponent (x, y) {
    if (!this.component) return

    this._setPanVector(x, y)
    this._transformPanVector()

    this.component.position.add(tmpPanVector)
    this.component.updateMatrix()
  }

  panAtom (x, y) {
    if (!this.atom || !this.component) return

    this.atom.positionToVector3(tmpAtomVector)
    tmpAtomVector.add(this.viewer.translationGroup.position)
    tmpAtomVector.applyMatrix4(this.viewer.rotationGroup.matrix)

    this._setPanVector(x, y, tmpAtomVector.z)
    this._transformPanVector()

    this.atom.positionAdd(tmpPanVector)
    this.component.updateRepresentations({ 'position': true })
  }

  rotate (x, y) {
    const [ dx, dy ] = this._getRotateXY(x, y)

    tmpRotateXMatrix.makeRotationX(dy)
    tmpRotateYMatrix.makeRotationY(dx)
    tmpRotateXMatrix.multiply(tmpRotateYMatrix)
    this.controls.applyMatrix(tmpRotateXMatrix)
  }

  rotateComponent (x, y) {
    if (!this.component) return

    const [ dx, dy ] = this._getRotateXY(x, y)

    tmpRotateMatrix.extractRotation(this.component.transform)
    tmpRotateMatrix.premultiply(this.viewer.rotationGroup.matrix)
    tmpRotateMatrix.getInverse(tmpRotateMatrix)
    tmpRotateVector.set(1, 0, 0)
    tmpRotateVector.applyMatrix4(tmpRotateMatrix)
    tmpRotateXMatrix.makeRotationAxis(tmpRotateVector, dy)
    tmpRotateVector.set(0, 1, 0)
    tmpRotateVector.applyMatrix4(tmpRotateMatrix)
    tmpRotateYMatrix.makeRotationAxis(tmpRotateVector, dx)
    tmpRotateXMatrix.multiply(tmpRotateYMatrix)
    tmpRotateQuaternion.setFromRotationMatrix(tmpRotateXMatrix)
    this.component.quaternion.premultiply(tmpRotateQuaternion)
    this.component.updateMatrix()
  }
}

export default TrackballControls