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

src/proxy/atom-proxy.js

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

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

import {
    SecStrucHelix, SecStrucSheet, SecStrucTurn,
    ProteinType, RnaType, DnaType, WaterType, IonType, SaccharideType,
    CgProteinBackboneType, CgRnaBackboneType, CgDnaBackboneType
} from '../structure/structure-constants.js'

/**
 * Atom proxy
 */
class AtomProxy {
  /**
   * @param {Structure} structure - the structure
   * @param {Integer} index - the index
   */
  constructor (structure, index) {
    /**
     * The structure the atom belongs to.
     * @type {Structure}
     */
    this.structure = structure

    /**
     * @type {ChainStore}
     */
    this.chainStore = structure.chainStore
    /**
     * @type {ResidueStore}
     */
    this.residueStore = structure.residueStore
    /**
     * @type {AtomStore}
     */
    this.atomStore = structure.atomStore

    /**
     * @type {ResidueMap}
     */
    this.residueMap = structure.residueMap
    /**
     * @type {AtomMap}
     */
    this.atomMap = structure.atomMap

    /**
     * The index of the atom, pointing to the data in the corresponding {@link AtomStore}
     * @type {Integer}
     */
    this.index = index
  }

  /**
   * @type {BondHash}
   */
  get bondHash () { return this.structure.bondHash }

  /**
   * Molecular enity
   * @type {Entity}
   */
  get entity () {
    return this.structure.entityList[ this.entityIndex ]
  }
  get entityIndex () {
    return this.chainStore.entityIndex[ this.chainIndex ]
  }
  get modelIndex () {
    return this.chainStore.modelIndex[ this.chainIndex ]
  }
  get chainIndex () {
    return this.residueStore.chainIndex[ this.residueIndex ]
  }
  /**
   * @type {ResidueProxy}
   */
  get residue () {
    console.warn('residue - might be expensive')
    return this.structure.getResidueProxy(this.residueIndex)
  }

  get residueIndex () {
    return this.atomStore.residueIndex[ this.index ]
  }
  set residueIndex (value) {
    this.atomStore.residueIndex[ this.index ] = value
  }

  //

  /**
   * Secondary structure code
   * @type {String}
   */
  get sstruc () {
    return this.residueStore.getSstruc(this.residueIndex)
  }
  /**
   * Insertion code
   * @type {String}
   */
  get inscode () {
    return this.residueStore.getInscode(this.residueIndex)
  }
  /**
   * Residue number/label
   * @type {Integer}
   */
  get resno () {
    return this.residueStore.resno[ this.residueIndex ]
  }
  /**
   * Chain name
   * @type {String}
   */
  get chainname () {
    return this.chainStore.getChainname(this.chainIndex)
  }
  /**
   * Chain id
   * @type {String}
   */
  get chainid () {
    return this.chainStore.getChainid(this.chainIndex)
  }

  //

  /**
   * @type {ResidueType}
   */
  get residueType () {
    return this.residueMap.get(this.residueStore.residueTypeId[ this.residueIndex ])
  }
  /**
   * @type {AtomType}
   */
  get atomType () {
    return this.atomMap.get(this.atomStore.atomTypeId[ this.index ])
  }
  get residueAtomOffset () {
    return this.residueStore.atomOffset[ this.residueIndex ]
  }

  //

  /**
   * Residue name
   * @type {String}
   */
  get resname () {
    return this.residueType.resname
  }
  /**
   * Hetero flag
   * @type {Boolean}
   */
  get hetero () {
    return this.residueType.hetero
  }

  //

  /**
   * Atom name
   * @type {String}
   */
  get atomname () {
    return this.atomType.atomname
  }
  /**
   * Element
   * @type {String}
   */
  get element () {
    return this.atomType.element
  }
  /**
   * Van-der-Waals radius
   * @type {Float}
   */
  get vdw () {
    return this.atomType.vdw
  }
  /**
   * Covalent radius
   * @type {Float}
   */
  get covalent () {
    return this.atomType.covalent
  }

  //

  /**
   * X coordinate
   * @type {Float}
   */
  get x () {
    return this.atomStore.x[ this.index ]
  }
  set x (value) {
    this.atomStore.x[ this.index ] = value
  }

  /**
   * Y coordinate
   * @type {Float}
   */
  get y () {
    return this.atomStore.y[ this.index ]
  }
  set y (value) {
    this.atomStore.y[ this.index ] = value
  }

  /**
   * Z coordinate
   * @type {Float}
   */
  get z () {
    return this.atomStore.z[ this.index ]
  }
  set z (value) {
    this.atomStore.z[ this.index ] = value
  }

  /**
   * Serial number
   * @type {Integer}
   */
  get serial () {
    return this.atomStore.serial[ this.index ]
  }
  set serial (value) {
    this.atomStore.serial[ this.index ] = value
  }

  /**
   * B-factor value
   * @type {Float}
   */
  get bfactor () {
    return this.atomStore.bfactor[ this.index ]
  }
  set bfactor (value) {
    this.atomStore.bfactor[ this.index ] = value
  }

  /**
   * Occupancy value
   * @type {Float}
   */
  get occupancy () {
    return this.atomStore.occupancy[ this.index ]
  }
  set occupancy (value) {
    this.atomStore.occupancy[ this.index ] = value
  }

  /**
   * Alternate location identifier
   * @type {String}
   */
  get altloc () {
    return this.atomStore.getAltloc(this.index)
  }
  set altloc (value) {
    this.atomStore.setAltloc(this.index, value)
  }

  /**
   * Partial charge
   * @type {Float|null}
   */
  get partialCharge () {
    return this.atomStore.partialCharge ? this.atomStore.partialCharge[ this.index ] : null
  }
  set partialCharge (value) {
    if (this.atomStore.partialCharge) {
      this.atomStore.partialCharge[ this.index ] = value
    }
  }

  /**
   * Formal charge
   * @type {Integer|null}
   */
  get formalCharge () {
    return this.atomStore.formalCharge ? this.atomStore.formalCharge[ this.index ] : null
  }
  set formalCharge (value) {
    if (this.atomStore.formalCharge) {
      this.atomStore.formalCharge[ this.index ] = value
    }
  }

  //

  /**
   * Iterate over each bond
   * @param  {function(bond: BondProxy)} callback - iterator callback function
   * @param  {BondProxy} [bp] - optional target bond proxy for use in the callback
   * @return {undefined}
   */
  eachBond (callback, bp) {
    bp = bp || this.structure._bp
    const idx = this.index
    const bondHash = this.bondHash
    const indexArray = bondHash.indexArray
    const n = bondHash.countArray[ idx ]
    const offset = bondHash.offsetArray[ idx ]

    for (let i = 0; i < n; ++i) {
      bp.index = indexArray[ offset + i ]
      callback(bp)
    }
  }

  /**
   * Iterate over each bonded atom
   * @param  {function(atom: AtomProxy)} callback - iterator callback function
   * @param  {AtomProxy} [ap] - optional target atom proxy for use in the callback
   * @return {undefined}
   */
  eachBondedAtom (callback, ap) {
    ap = ap || this.structure._ap
    var idx = this.index

    this.eachBond(function (bp) {
      if (idx !== bp.atomIndex1) {
        ap.index = bp.atomIndex1
      } else {
        ap.index = bp.atomIndex2
      }
      callback(ap)
    })
  }

  /**
   * Check if this atom is bonded to the given atom,
   * assumes both atoms are from the same structure
   * @param  {AtomProxy} ap - the given atom
   * @return {Boolean} whether a bond exists or not
   */
  hasBondTo (ap) {
    let hasBond = false
    this.eachBondedAtom(function (bap) {
      if (ap.index === bap.index) hasBond = true
    })
    return hasBond
  }

  //

  /**
   * If atom is part of a backbone
   * @return {Boolean} flag
   */
  isBackbone () {
    var backboneIndexList = this.residueType.backboneIndexList
    if (backboneIndexList.length > 0) {
      var atomOffset = this.residueStore.atomOffset[ this.residueIndex ]
      return backboneIndexList.includes(this.index - atomOffset)
    } else {
      return false
    }
  }

  /**
   * If atom is part of a polymer
   * @return {Boolean} flag
   */
  isPolymer () {
    if (this.structure.entityList.length > 0) {
      return this.entity.isPolymer()
    } else {
      var moleculeType = this.residueType.moleculeType
      return (
                moleculeType === ProteinType ||
                moleculeType === RnaType ||
                moleculeType === DnaType
      )
    }
  }

  /**
   * If atom is part of a sidechin
   * @return {Boolean} flag
   */
  isSidechain () {
    return this.isPolymer() && !this.isBackbone()
  }

  /**
   * If atom is part of a coarse-grain group
   * @return {Boolean} flag
   */
  isCg () {
    var backboneType = this.residueType.backboneType
    return (
            backboneType === CgProteinBackboneType ||
            backboneType === CgRnaBackboneType ||
            backboneType === CgDnaBackboneType
    )
  }

  /**
   * If atom is part of a hetero group
   * @return {Boolean} flag
   */
  isHetero () {
    return this.residueType.hetero === 1
  }

  /**
   * If atom is part of a protein molecule
   * @return {Boolean} flag
   */
  isProtein () {
    return this.residueType.moleculeType === ProteinType
  }

  /**
   * If atom is part of a nucleic molecule
   * @return {Boolean} flag
   */
  isNucleic () {
    const moleculeType = this.residueType.moleculeType
    return (
            moleculeType === RnaType ||
            moleculeType === DnaType
    )
  }

  /**
   * If atom is part of a rna
   * @return {Boolean} flag
   */
  isRna () {
    return this.residueType.moleculeType === RnaType
  }

  /**
   * If atom is part of a dna
   * @return {Boolean} flag
   */
  isDna () {
    return this.residueType.moleculeType === DnaType
  }

  /**
   * If atom is part of a water molecule
   * @return {Boolean} flag
   */
  isWater () {
    return this.residueType.moleculeType === WaterType
  }

  /**
   * If atom is part of an ion
   * @return {Boolean} flag
   */
  isIon () {
    return this.residueType.moleculeType === IonType
  }

  /**
   * If atom is part of a saccharide
   * @return {Boolean} flag
   */
  isSaccharide () {
    return this.residueType.moleculeType === SaccharideType
  }

  /**
   * If atom is part of a helix
   * @return {Boolean} flag
   */
  isHelix () {
    return SecStrucHelix.includes(this.sstruc)
  }

  /**
   * If atom is part of a sheet
   * @return {Boolean} flag
   */
  isSheet () {
    return SecStrucSheet.includes(this.sstruc)
  }

  /**
   * If atom is part of a turn
   * @return {Boolean} flag
   */
  isTurn () {
    return SecStrucTurn.includes(this.sstruc) && this.isProtein()
  }

  isBonded () {
    return this.bondHash.countArray[ this.index ] !== 0
  }

  /**
   * If atom is part of a ring
   * @return {Boolean} flag
   */
  isRing () {
    var ringFlags = this.residueType.getRings().flags
    return ringFlags[ this.index - this.residueAtomOffset ] === 1
  }

  /**
   * Distance to another atom
   * @param  {AtomProxy} atom - the other atom
   * @return {Number} the distance
   */
  distanceTo (atom) {
    var taa = this.atomStore
    var aaa = atom.atomStore
    var ti = this.index
    var ai = atom.index
    var x = taa.x[ ti ] - aaa.x[ ai ]
    var y = taa.y[ ti ] - aaa.y[ ai ]
    var z = taa.z[ ti ] - aaa.z[ ai ]
    var distSquared = x * x + y * y + z * z
    return Math.sqrt(distSquared)
  }

  /**
   * If connected to another atom
   * @param  {AtomProxy} atom - the other atom
   * @return {Boolean} flag
   */
  connectedTo (atom) {
    var taa = this.atomStore
    var aaa = atom.atomStore
    var ti = this.index
    var ai = atom.index

    if (taa.altloc && aaa.altloc) {
      var ta = taa.altloc[ ti ]  // use Uint8 value to compare
      var aa = aaa.altloc[ ai ]  // no need to convert to char
      // 0 is the Null character, 32 is the space character
      if (!(ta === 0 || aa === 0 || ta === 32 || aa === 32 || (ta === aa))) return false
    }

    var x = taa.x[ ti ] - aaa.x[ ai ]
    var y = taa.y[ ti ] - aaa.y[ ai ]
    var z = taa.z[ ti ] - aaa.z[ ai ]

    var distSquared = x * x + y * y + z * z

    // if( this.residue.isCg() ) console.log( this.qualifiedName(), Math.sqrt( distSquared ), distSquared )
    if (distSquared < 64.0 && this.isCg()) return true

    if (isNaN(distSquared)) return false

    var d = this.covalent + atom.covalent
    var d1 = d + 0.3
    var d2 = d - 0.5

    return distSquared < (d1 * d1) && distSquared > (d2 * d2)
  }

  /**
   * Set atom position from array
   * @param  {Array|TypedArray} array - input array
   * @param  {Integer} [offset] - the offset
   * @return {AtomProxy} this object
   */
  positionFromArray (array, offset) {
    if (offset === undefined) offset = 0

    this.x = array[ offset + 0 ]
    this.y = array[ offset + 1 ]
    this.z = array[ offset + 2 ]

    return this
  }

  /**
   * Write atom position to array
   * @param  {Array|TypedArray} [array] - target array
   * @param  {Integer} [offset] - the offset
   * @return {Array|TypedArray} target array
   */
  positionToArray (array, offset) {
    if (array === undefined) array = []
    if (offset === undefined) offset = 0

    var index = this.index
    var atomStore = this.atomStore

    array[ offset + 0 ] = atomStore.x[ index ]
    array[ offset + 1 ] = atomStore.y[ index ]
    array[ offset + 2 ] = atomStore.z[ index ]

    return array
  }

  /**
   * Write atom position to vector
   * @param  {Vector3} [v] - target vector
   * @return {Vector3} target vector
   */
  positionToVector3 (v) {
    if (v === undefined) v = new Vector3()

    v.x = this.x
    v.y = this.y
    v.z = this.z

    return v
  }

  /**
   * Set atom position from vector
   * @param  {Vector3} v - input vector
   * @return {AtomProxy} this object
   */
  positionFromVector3 (v) {
    this.x = v.x
    this.y = v.y
    this.z = v.z

    return this
  }

  /**
   * Add vector to atom position
   * @param  {Vector3} v - input vector
   * @return {AtomProxy} this object
   */
  positionAdd (v) {
    this.x += v.x
    this.y += v.y
    this.z += v.z

    return this
  }

  /**
   * Subtract vector from atom position
   * @param  {Vector3} v - input vector
   * @return {AtomProxy} this object
   */
  positionSub (v) {
    this.x -= v.x
    this.y -= v.y
    this.z -= v.z

    return this
  }

  /**
   * Get intra group/residue bonds
   * @param  {Boolean} firstOnly - immediately return the first connected atomIndex
   * @return {Integer[]|Integer|undefined} connected atomIndices
   */
  getResidueBonds (firstOnly) {
    var residueAtomOffset = this.residueAtomOffset
    var relativeIndex = this.index - this.residueAtomOffset
    var bonds = this.residueType.getBonds()
    var atomIndices1 = bonds.atomIndices1
    var atomIndices2 = bonds.atomIndices2
    var idx1, idx2, connectedAtomIndex, connectedAtomIndices

    if (!firstOnly) connectedAtomIndices = []

    idx1 = atomIndices1.indexOf(relativeIndex)
    while (idx1 !== -1) {
      connectedAtomIndex = atomIndices2[ idx1 ] + residueAtomOffset
      if (firstOnly) return connectedAtomIndex
      connectedAtomIndices.push(connectedAtomIndex)
      idx1 = atomIndices1.indexOf(relativeIndex, idx1 + 1)
    }

    idx2 = atomIndices2.indexOf(relativeIndex)
    while (idx2 !== -1) {
      connectedAtomIndex = atomIndices1[ idx2 ] + residueAtomOffset
      if (firstOnly) return connectedAtomIndex
      connectedAtomIndices.push(connectedAtomIndex)
      idx2 = atomIndices2.indexOf(relativeIndex, idx2 + 1)
    }

    return connectedAtomIndices
  }

  //

  qualifiedName (noResname) {
    var name = ''
    if (this.resname && !noResname) name += '[' + this.resname + ']'
    if (this.resno !== undefined) name += this.resno
    if (this.inscode) name += '^' + this.inscode
    if (this.chainname) name += ':' + this.chainname
    if (this.atomname) name += '.' + this.atomname
    if (this.altloc) name += '%' + this.altloc
    if (this.structure.modelStore.count > 1) name += '/' + this.modelIndex
    return name
  }

  /**
   * Clone object
   * @return {AtomProxy} cloned atom
   */
  clone () {
    return new this.constructor(this.structure, this.index)
  }

  toObject () {
    return {
      index: this.index,
      residueIndex: this.residueIndex,

      atomno: this.atomno,
      resname: this.resname,
      x: this.x,
      y: this.y,
      z: this.z,
      element: this.element,
      chainname: this.chainname,
      resno: this.resno,
      serial: this.serial,
      vdw: this.vdw,
      covalent: this.covalent,
      hetero: this.hetero,
      bfactor: this.bfactor,
      altloc: this.altloc,
      atomname: this.atomname,
      modelindex: this.modelindex
    }
  }
}

export default AtomProxy