packages/miew/src/gfx/geometries/VolumeSurfaceGeometry.js

Summary

Maintainability
A
1 hr
Test Coverage
import IsoSurfaceGeometry from './IsoSurfaceGeometry'
import IsoSurface from './IsoSurface'
import utils from '../../utils'
import { Box3, BufferAttribute, Vector3 } from 'three'

/**
 * This is a base class for volumetric maps based isosurface algorithms.
 * @param spheresCount - number of atoms/spheres
 * @param opts - geometry specific options
 * @constructor
 */

class VolumeSurfaceGeometry extends IsoSurfaceGeometry {
  _build() {
    const params = this._opts
    this.numVoxels = [128, 128, 128]
    this.xAxis = new Vector3(1.0, 0.0, 0.0)
    this.yAxis = new Vector3(0.0, 1.0, 0.0)
    this.zAxis = new Vector3(0.0, 0.0, 1.0)

    this.origin = new Vector3(0.0, 0.0, 0.0)
    this._visibilitySelector = params.visibilitySelector

    this._calcSurface(params)
  }

  _findMinMax(posRadArray) {
    const itemSize = 4
    const itemsCount = posRadArray.length / itemSize
    const maxPosRad = [
      posRadArray[0],
      posRadArray[1],
      posRadArray[2],
      posRadArray[3]
    ]
    const minPosRad = [
      posRadArray[0],
      posRadArray[1],
      posRadArray[2],
      posRadArray[3]
    ]
    for (let i = 1; i < itemsCount; ++i) {
      const ind = i * itemSize

      for (let itemIdx = 0; itemIdx < itemSize; ++itemIdx) {
        const tmpVal = posRadArray[ind + itemIdx]
        maxPosRad[itemIdx] = Math.max(tmpVal, maxPosRad[itemIdx])
        minPosRad[itemIdx] = Math.min(tmpVal, minPosRad[itemIdx])
      }
    }
    return { maxPosRad, minPosRad }
  }

  _findNumVoxels(posRadArray, params) {
    const { numVoxels } = this
    const minMaxValues = this._findMinMax(posRadArray)
    const minCoordRad = minMaxValues.minPosRad
    const maxCoordRad = minMaxValues.maxPosRad

    // minrad
    if (minCoordRad[3] > 4.0) {
      params.gridSpacing *= minCoordRad[3]
    }

    let gridPadding = params.radScale * maxCoordRad[3] * 1.7
    let padRad = gridPadding
    padRad = 0.65 * Math.sqrt((4.0 / 3.0) * Math.PI * padRad * padRad * padRad)
    gridPadding = Math.max(gridPadding, padRad)

    let i = 0
    for (; i < 3; ++i) {
      minCoordRad[i] -= gridPadding
      maxCoordRad[i] += gridPadding
    }

    for (i = 0; i < 3; ++i) {
      numVoxels[i] = Math.ceil(
        (maxCoordRad[i] - minCoordRad[i]) / params.gridSpacing
      )
    }
    this.xAxis.x = (numVoxels[0] - 1) * params.gridSpacing
    this.yAxis.y = (numVoxels[1] - 1) * params.gridSpacing
    this.zAxis.z = (numVoxels[2] - 1) * params.gridSpacing
    ;[this.origin.x, this.origin.y, this.origin.z] = minCoordRad

    return { bbox: minMaxValues, dim: numVoxels }
  }

  _makeSurface(surface, params) {
    const isoSurf = new IsoSurface()
    isoSurf.compute(surface.volMap, this.origin, params.isoValue, 1)
    isoSurf.vertexFusion(9, 9) // normalization is included

    if (isoSurf._numTriangles > 0) {
      isoSurf.setColorVolTex(
        surface.volTexMap,
        surface.atomMap,
        surface.atomWeightMap,
        this._visibilitySelector
      )
      this.setIndex(new BufferAttribute(isoSurf._indices, 1))
      this.setAttribute('position', new BufferAttribute(isoSurf._position, 3))
      this.setAttribute('normal', new BufferAttribute(isoSurf._normals, 3))
      this.setAttribute('color', new BufferAttribute(isoSurf._colors, 3))
    } else {
      // geometry should have at least empty position attributes to be processed in wireframe mode by three.js
      this.setAttribute(
        'position',
        new BufferAttribute(utils.allocateTyped(Float32Array, 0), 3)
      )
    }
  }

  _calcSurface(params) {
    const packedArrays = {
      posRad: this._posRad,
      colors: this._colors,
      atoms: this._opts.atoms
    }

    if (packedArrays.posRad.length === 0) {
      return
    }
    const boundaries = this._findNumVoxels(packedArrays.posRad, params)

    const box = new Box3(
      this.origin,
      new Vector3(this.xAxis.x, this.yAxis.y, this.zAxis.z).add(this.origin)
    )
    const surface = this._computeSurface(packedArrays, box, boundaries, params)

    this._makeSurface(surface, params)
  }
}

export default VolumeSurfaceGeometry