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

src/representation/line-representation.js

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

import { defaults } from '../utils.js'
import { RepresentationRegistry } from '../globals.js'
import StructureRepresentation from './structure-representation.js'
import WideLineBuffer from '../buffer/wideline-buffer.js'
import { AtomPicker } from '../utils/picker.js'

/**
 * Determine which atoms in  a Structure[View] form no bonds to any other atoms
 * in that Structure.
 *
 * This differs from setting the selection to "nonbonded" as it finds atoms
 * that have no bonds within the current selection.
 * @param  {Structure} structure - The Structure or StructureView object
 * @return {AtomSet} AtomSet of lone atoms
 */
function getLoneAtomSet (structure) {
  const atomSet = structure.getAtomSet()
  const bondSet = structure.getBondSet()
  const bp = structure.getBondProxy()
  bondSet.forEach(function (idx) {
    bp.index = idx
    atomSet.clear(bp.atomIndex1)
    atomSet.clear(bp.atomIndex2)
  })
  return atomSet
}

/**
 * Line representation
 */
class LineRepresentation extends StructureRepresentation {
  /**
   * Create Line representation object
   * @param {Structure} structure - the structure to be represented
   * @param {Viewer} viewer - a viewer object
   * @param {RepresentationParameters} params - representation parameters, plus the properties listed below
   * @property {String} multipleBond - one off "off", "symmetric", "offset"
   * @param {Float} params.bondSpacing - spacing for multiple bond rendering
   * @param {Integer} params.linewidth - width of lines
   * @param {Boolean} params.lines - render bonds as lines
   * @param {String} params.crosses - render atoms as crosses: "off", "all" or "lone" (default)
   * @param {Float} params.crossSize - size of cross
   * @param {null} params.flatShaded - not available
   * @param {null} params.side - not available
   * @param {null} params.wireframe - not available
   * @param {null} params.roughness - not available
   * @param {null} params.metalness - not available
   * @param {null} params.diffuse - not available
   */
  constructor (structure, viewer, params) {
    super(structure, viewer, params)

    this.type = 'line'

    this.parameters = Object.assign({

      multipleBond: {
        type: 'select',
        rebuild: true,
        options: {
          'off': 'off',
          'symmetric': 'symmetric',
          'offset': 'offset'
        }
      },
      bondSpacing: {
        type: 'number', precision: 2, max: 2.0, min: 0.5
      },
      linewidth: {
        type: 'integer', max: 50, min: 1, buffer: true
      },
      lines: {
        type: 'boolean', rebuild: true
      },
      crosses: {
        type: 'select',
        rebuild: true,
        options: {
          'off': 'off',
          'lone': 'lone',
          'all': 'all'
        }
      },
      crossSize: {
        type: 'number', precision: 2, max: 2.0, min: 0.1
      }

    }, this.parameters, {

      flatShaded: null,
      side: null,
      wireframe: null,

      roughness: null,
      metalness: null

    })

    this.init(params)
  }

  init (params) {
    var p = params || {}

    this.multipleBond = defaults(p.multipleBond, 'off')
    this.bondSpacing = defaults(p.bondSpacing, 1.0)
    this.linewidth = defaults(p.linewidth, 2)
    this.lines = defaults(p.lines, true)
    this.crosses = defaults(p.crosses, 'lone')
    this.crossSize = defaults(p.crossSize, 0.4)

    super.init(p)
  }

  getBondParams (what, params) {
    params = Object.assign({
      multipleBond: this.multipleBond,
      bondSpacing: this.bondSpacing,
      radiusParams: { 'radius': 0.1, 'scale': 1 }
    }, params)

    return super.getBondParams(what, params)
  }

  _crossData (what, sview) {
    if (what) {
      if (!what.position && !what.color) return
    }

    const p = {}
    if (this.crosses === 'lone') {
      p.atomSet = getLoneAtomSet(sview)
    }

    const atomData = sview.getAtomData(this.getAtomParams(what, p))
    const crossData = {}
    const position = atomData.position
    const color = atomData.color
    const picking = atomData.picking

    const size = (position || color).length
    const attrSize = size * 3

    let cPosition1
    let cPosition2
    let cColor
    let cColor2
    let cOffset

    let pickingArray

    if (!what || what.position) {
      cPosition1 = crossData.position1 = new Float32Array(attrSize)
      cPosition2 = crossData.position2 = new Float32Array(attrSize)
      cOffset = this.crossSize / 2
    }
    if (!what || what.color) {
      cColor = crossData.color = new Float32Array(attrSize)
      cColor2 = crossData.color2 = new Float32Array(attrSize)
    }
    if (!what || what.picking) {
      pickingArray = new Float32Array(atomData.picking.array.length * 3) // Needs padding??
    }

    for (let v = 0; v < size; v++) {
      const j = v * 3
      const i = j * 3

      if (!what || what.position) {
        const x = position[ j ]
        const y = position[ j + 1 ]
        const z = position[ j + 2 ]

        cPosition1[ i ] = x - cOffset
        cPosition1[ i + 1 ] = y
        cPosition1[ i + 2 ] = z
        cPosition2[ i ] = x + cOffset
        cPosition2[ i + 1 ] = y
        cPosition2[ i + 2 ] = z

        cPosition1[ i + 3 ] = x
        cPosition1[ i + 4 ] = y - cOffset
        cPosition1[ i + 5 ] = z
        cPosition2[ i + 3 ] = x
        cPosition2[ i + 4 ] = y + cOffset
        cPosition2[ i + 5 ] = z

        cPosition1[ i + 6 ] = x
        cPosition1[ i + 7 ] = y
        cPosition1[ i + 8 ] = z - cOffset
        cPosition2[ i + 6 ] = x
        cPosition2[ i + 7 ] = y
        cPosition2[ i + 8 ] = z + cOffset
      }

      if (!what || what.color) {
        const cimax = i + 9
        for (let ci = i; ci < cimax; ci += 3) {
          cColor[ ci ] = cColor2[ ci ] = color[ j ]
          cColor[ ci + 1 ] = cColor2[ ci + 1 ] = color[ j + 1 ]
          cColor[ ci + 2 ] = cColor2[ ci + 2 ] = color[ j + 2 ]
        }
      }

      if (!what || what.picking) {
        pickingArray[ j ] =
        pickingArray[ j + 1 ] =
        pickingArray[ j + 2 ] = picking.array[ v ]
      }
    }

    if (!what || what.picking) {
      crossData.picking = new AtomPicker(
        pickingArray, atomData.picking.structure
      )
    }

    return crossData
  }

  createData (sview) {
    const what = { position: true, color: true, picking: true }

    const bufferList = []

    if (this.lines) {
      const bondData = sview.getBondData(this.getBondParams(what))

      const lineBuffer = new WideLineBuffer(
        bondData, this.getBufferParams({ linewidth: this.linewidth })
      )

      bufferList.push(lineBuffer)
    }

    if (this.crosses !== 'off') {
      const crossBuffer = new WideLineBuffer(
        this._crossData(what, sview),
        this.getBufferParams({linewidth: this.linewidth})
      )
      bufferList.push(crossBuffer)
    }

    return {
      bufferList: bufferList
    }
  }

  updateData (what, data) {
    let bufferIdx = 0

    if (this.lines) {
      const bondData = data.sview.getBondData(this.getBondParams(what))
      const lineAttributes = {}

      if (!what || what.position) {
        lineAttributes.position1 = bondData.position1
        lineAttributes.position2 = bondData.position2
      }

      if (!what || what.color) {
        lineAttributes.color = bondData.color
        lineAttributes.color2 = bondData.color2
      }

      data.bufferList[ bufferIdx++ ].setAttributes(lineAttributes)
    }

    if (this.crosses !== 'off') {
      const crossData = this._crossData(what, data.sview)
      const crossAttributes = {}

      if (!what || what.position) {
        crossAttributes.position1 = crossData.position1
        crossAttributes.position2 = crossData.position2
      }
      if (!what || what.color) {
        crossAttributes.color = crossData.color
        crossAttributes.color2 = crossData.color2
      }

      data.bufferList[ bufferIdx++ ].setAttributes(crossAttributes)
    }
  }

  setParameters (params) {
    var rebuild = false
    var what = {}

    if (params && (params.bondSpacing || params.crossSize)) {
      what.position = true
    }

    super.setParameters(params, what, rebuild)

    return this
  }
}

RepresentationRegistry.add('line', LineRepresentation)

export default LineRepresentation