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

Summary

Maintainability
C
1 day
Test Coverage
import utils from '../../utils'
import {
  BufferAttribute,
  BufferGeometry,
  Uint16BufferAttribute,
  Vector3
} from 'three'

const MAX_POINTS_COUNT_16BIT = 65536
const PTS_PER_TRIANGLE = 3

class CylinderBufferGeometry extends BufferGeometry {
  constructor(
    radiusTop,
    radiusBottom,
    height,
    radialSegments,
    heightSegments,
    openEnded
  ) {
    super()

    const thetaStart = 0
    const thetaLength = 2 * Math.PI

    this.type = 'CylinderBufferGeometry'

    this.parameters = {
      radiusTop,
      radiusBottom,
      height,
      radialSegments,
      heightSegments,
      openEnded
    }

    const hasTop = openEnded === false && radiusTop > 0
    const hasBottom = openEnded === false && radiusBottom > 0
    const vertexCount =
      (heightSegments + 1) * radialSegments +
      hasTop * (radialSegments + 1) +
      hasBottom * (radialSegments + 1)
    const facesCount =
      (2 * heightSegments + hasTop + hasBottom) * radialSegments

    const heightHalf = height / 2

    /* eslint-disable no-magic-numbers */
    const positions = new BufferAttribute(
      utils.allocateTyped(Float32Array, vertexCount * 3),
      3
    )
    const normals = new BufferAttribute(
      utils.allocateTyped(Float32Array, vertexCount * 3),
      3
    )
    const indices = new Uint16BufferAttribute(
      utils.allocateTyped(Uint16Array, facesCount * PTS_PER_TRIANGLE),
      1
    )
    /* eslint-enable no-magic-numbers */

    const uvs = new BufferAttribute(
      utils.allocateTyped(Float32Array, vertexCount * 2),
      2
    )
    console.assert(
      vertexCount < MAX_POINTS_COUNT_16BIT,
      'DEBUG: Cylinder Geometry has too many vertices (65536 max).'
    )

    let currVtxIdx = 0
    let currFaceIdx = 0
    const tanTheta = -(radiusBottom - radiusTop) / height

    // setup cylinder data
    for (let y = 0; y <= heightSegments; y++) {
      // faces
      if (y !== heightSegments) {
        for (let i = 0; i < radialSegments; i++) {
          const v1 = currVtxIdx + i
          const v2 = currVtxIdx + radialSegments + i
          const v3 = currVtxIdx + radialSegments + ((i + 1) % radialSegments)
          const v4 = currVtxIdx + ((i + 1) % radialSegments)

          indices.setXYZ(currFaceIdx * PTS_PER_TRIANGLE, v1, v4, v2)
          currFaceIdx++
          indices.setXYZ(currFaceIdx * PTS_PER_TRIANGLE, v2, v4, v3)
          currFaceIdx++
        }
      }

      // vertices
      const v = y / heightSegments
      const radius = v * (radiusBottom - radiusTop) + radiusTop

      for (let x = 0; x < radialSegments; x++) {
        const u = x / radialSegments

        const vx = radius * Math.sin(u * thetaLength + thetaStart)
        const vy = v * height - heightHalf
        const vz = radius * Math.cos(u * thetaLength + thetaStart)

        const normal = new Vector3(
          vx,
          Math.sqrt(vx * vx + vz * vz) * tanTheta,
          vz
        ).normalize()

        positions.setXYZ(currVtxIdx, vx, vy, vz)
        normals.setXYZ(currVtxIdx, normal.x, normal.y, normal.z)
        uvs.setXY(currVtxIdx, u, v)
        ++currVtxIdx
      }
    }

    // top cap
    if (hasTop) {
      const startTIdx = currVtxIdx
      const lastIdx = currVtxIdx + radialSegments
      for (let fTIdx = 0; fTIdx < radialSegments; ++fTIdx) {
        const currSrcIdx = currVtxIdx - radialSegments
        positions.setXYZ(
          currVtxIdx,
          positions.getX(currSrcIdx),
          positions.getY(currSrcIdx),
          positions.getZ(currSrcIdx)
        )
        normals.setXYZ(currVtxIdx, 0, 1, 0)
        uvs.setXY(currVtxIdx, 1, 1)

        const nextTVtx = startTIdx + ((fTIdx + 1) % radialSegments)
        indices.setXYZ(
          currFaceIdx * PTS_PER_TRIANGLE,
          currVtxIdx,
          nextTVtx,
          lastIdx
        )
        currFaceIdx++
        currVtxIdx++
      }

      positions.setXYZ(currVtxIdx, 0, heightHalf, 0)
      normals.setXYZ(currVtxIdx, 0, 1, 0)
      uvs.setXY(currVtxIdx, 1, 1)
      ++currVtxIdx
    }

    // bottom cap
    if (hasBottom) {
      const startBIdx = currVtxIdx
      const lastBIdx = currVtxIdx + radialSegments
      for (let fBIdx = 0; fBIdx < radialSegments; ++fBIdx) {
        const currSrcBIdx = fBIdx
        positions.setXYZ(
          currVtxIdx,
          positions.getX(currSrcBIdx),
          positions.getY(currSrcBIdx),
          positions.getZ(currSrcBIdx)
        )
        normals.setXYZ(currVtxIdx, 0, -1, 0)
        uvs.setXY(currVtxIdx, 0, 0)

        const nextBVtx = startBIdx + ((fBIdx + 1) % radialSegments)
        indices.setXYZ(
          currFaceIdx * PTS_PER_TRIANGLE,
          nextBVtx,
          currVtxIdx,
          lastBIdx
        )
        currFaceIdx++
        currVtxIdx++
      }

      positions.setXYZ(currVtxIdx, 0, -heightHalf, 0)
      normals.setXYZ(currVtxIdx, 0, -1, 0)
      uvs.setXY(currVtxIdx, 0, 0)
    }

    this.setIndex(indices)
    this.setAttribute('position', positions)
    this.setAttribute('normal', normals)
    this.setAttribute('uv', uvs)
  }

  clone() {
    const { parameters } = this

    return new CylinderBufferGeometry(
      parameters.radiusTop,
      parameters.radiusBottom,
      parameters.height,
      parameters.radialSegments,
      parameters.heightSegments,
      parameters.openEnded
    )
  }
}

export default CylinderBufferGeometry