Home Reference Source Repository

js/SceneKit/SCNCapsule.js

'use strict'

import SCNGeometry from './SCNGeometry'
import SCNGeometryElement from './SCNGeometryElement'
import SCNGeometryPrimitiveType from './SCNGeometryPrimitiveType'
import SCNGeometrySource from './SCNGeometrySource'
import SCNMaterial from './SCNMaterial'
import SCNVector3 from './SCNVector3'

/**
 * A right circular cylinder geometry whose ends are capped with hemispheres.
 * @access public
 * @extends {SCNGeometry}
 * @see https://developer.apple.com/documentation/scenekit/scncapsule
 */
export default class SCNCapsule extends SCNGeometry {
  // Creating a Capsule

  /**
   * Creates a capsule geometry with the specified radius and height.
   * @access public
   * @constructor
   * @param {number} capRadius - The radius both of the capsule’s cylindrical body and of its hemispherical ends.
   * @param {number} height - The height of the capsule along the y-axis of its local coordinate space.
   * @desc The capsule is centered in its local coordinate system. For example, if you create a capsule whose cap radius is 5.0 and height is 20.0, it extends from -10.0 to 10.0 in the y-axis, and the circular cross section at the center of its body extends from -5.0 to 5.0 along the x- and z-axes.
   * @see https://developer.apple.com/documentation/scenekit/scncapsule/1523790-init
   */
  constructor(capRadius = 0.5, height = 2.0) {
    super([], [])

    // Adjusting a Capsule’s Dimensions

    /**
     * The radius both of the capsule’s circular center cross section and of its hemispherical ends. Animatable.
     * @type {number}
     * @see https://developer.apple.com/documentation/scenekit/scncapsule/1523926-capradius
     */
    this.capRadius = capRadius

    /**
     * The extent of the capsule along its y-axis. Animatable.
     * @type {number}
     * @see https://developer.apple.com/documentation/scenekit/scncapsule/1522789-height
     */
    this.height = height


    // Adjusting Geometric Detail

    /**
     * The number of subdivisions around the lateral circumference of the capsule. Animatable.
     * @type {number}
     * @see https://developer.apple.com/documentation/scenekit/scncapsule/1522735-radialsegmentcount
     */
    this.radialSegmentCount = 24

    /**
     * The number of subdivisions in the height of each hemispherical end of the capsule. Animatable.
     * @type {number}
     * @see https://developer.apple.com/documentation/scenekit/scncapsule/1523561-capsegmentcount
     */
    this.capSegmentCount = 48

    /**
     * The number of subdivisions in the sides of the capsule along its y-axis. Animatable.
     * @type {number}
     * @see https://developer.apple.com/documentation/scenekit/scncapsule/1523697-heightsegmentcount
     */
    this.heightSegmentCount = 1

    this._createGeometry()
    this.materials.push(new SCNMaterial())
  }

  _createGeometry() {
    const sourceData = []
    const indexData = []
    const vectorCount = (this.radialSegmentCount * 2 + 1) * (this.capSegmentCount + 4)
    //const primitiveCount = this.radialSegmentCount * this.capSegmentCount * 4

    const yNom = []
    const ySin = []
    for(let lat=0; lat<=this.capSegmentCount; lat++){
      yNom.push(-Math.cos(Math.PI * lat / this.capSegmentCount))
      ySin.push(Math.sin(Math.PI * lat / this.capSegmentCount))
    }

    const cylinderHeight = this.height - this.capRadius * 2
    const hemiLen = this.capSegmentCount / 2
    const rad2 = this.radialSegmentCount * 2
    for(let lng=0; lng<=rad2; lng++){
      const x = -Math.sin(2.0 * Math.PI * lng / rad2)
      const z = -Math.cos(2.0 * Math.PI * lng / rad2)
      const tx = lng / rad2
      let y = -cylinderHeight * 0.5
      for(let lat=0; lat<=hemiLen; lat++){
        const xNom = x * ySin[lat]
        const zNom = z * ySin[lat]

        // vertex
        sourceData.push(xNom * this.capRadius, y + yNom[lat] * this.capRadius, zNom * this.capRadius)

        // normal
        sourceData.push(xNom, yNom[lat], zNom)

        // texcoord
        sourceData.push(tx, 1.0 - 0.25 * lat / hemiLen)

        if(lat === hemiLen){
          // put the same data again
          sourceData.push(xNom * this.capRadius, y + yNom[lat] * this.capRadius, zNom * this.capRadius)
          sourceData.push(xNom, yNom[lat], zNom)
          sourceData.push(tx, 1.0 - 0.25 * lat / hemiLen)
        }
      }

      y = cylinderHeight * 0.5
      for(let lat=hemiLen; lat<=this.capSegmentCount; lat++){
        const xNom = x * ySin[lat]
        const zNom = z * ySin[lat]

        // vertex
        sourceData.push(xNom * this.capRadius, y + yNom[lat] * this.capRadius, zNom * this.capRadius)

        // normal
        sourceData.push(xNom, yNom[lat], zNom)

        // texcoord
        sourceData.push(tx, 0.50 - 0.25 * lat / hemiLen)

        if(lat === hemiLen){
          // put the same data again
          sourceData.push(xNom * this.capRadius, y + yNom[lat] * this.capRadius, zNom * this.capRadius)
          sourceData.push(xNom, yNom[lat], zNom)
          sourceData.push(tx, 0.50 - 0.25 * lat / hemiLen)
        }
      }
    }

    // index
    const capLen = this.capSegmentCount
    const radLen = this.radialSegmentCount * 2 + 1
    for(let i=0; i<capLen; i++){
      let index1 = i * (this.capSegmentCount + 4)
      let index2 = index1 + this.capSegmentCount + 5

      indexData.push(index1, index2, index1 + 1)
      index1 += 1
      for(let j=0; j<radLen; j++){
        if(Math.abs(j - this.radialSegmentCount) !== 1){
          indexData.push(index1, index2 + 1, index1 + 1)
          indexData.push(index1, index2, index2 + 1)
        }
        index1 += 1
        index2 += 1
      }
      indexData.push(index1, index2, index2 + 1)
    }

    const vertexSource = new SCNGeometrySource(
      sourceData, // data
      SCNGeometrySource.Semantic.vertex, // semantic
      vectorCount, // vectorCount
      true, // floatComponents
      3, // componentsPerVector
      4, // bytesPerComponent
      0, // offset
      32 // sride
    )

    const normalSource = new SCNGeometrySource(
      sourceData, // data
      SCNGeometrySource.Semantic.normal, // semantic
      vectorCount, // vectorCount
      true, // floatComponents
      3, // componentsPerVector
      4, // bytesPerComponent
      12, // offset
      32 // stride
    )

    const texcoordSource = new SCNGeometrySource(
      sourceData, // data
      SCNGeometrySource.Semantic.texcoord, // semantic
      vectorCount, // vectorCount
      true, // floatComponents
      2, // componentsPerVector
      4, // bytesPerComponent
      24, // offset
      32 // stride
    )

    const element = new SCNGeometryElement(indexData, SCNGeometryPrimitiveType.triangles)

    this._geometryElements = [element]
    this._geometrySources = [vertexSource, normalSource, texcoordSource]

    this.boundingBox = {
      min: new SCNVector3(-this.capRadius, -this.height * 0.5, -this.capRadius),
      max: new SCNVector3(this.capRadius, this.height * 0.5, this.capRadius)
    }
  }

  /**
   * @access private
   * @returns {Ammo.btCollisionShape} -
   * @desc call Ammo.destroy(shape) after using it.
   */
  _createBtCollisionShape() {
    //const height = (this.height - this.capRadius) * 0.5
    //const shape = new Ammo.btCapsuleShape(this.capRadius, height)
    //return shape
  }

  /**
   * The center point and radius of the object’s bounding sphere.
   * @type {Object}
   * @parameter {SCNVector3} _boundingSphere.center -
   * @parameter {number} _boundingSphere.radius -
   * @returns {Object} -
   * @desc Scene Kit defines a bounding sphere in the local coordinate space using a center point and a radius. For example, if a node’s bounding sphere has the center point {3, 1, 4} and radius 2.0, all points in the vertex data of node’s geometry (and any geometry attached to its child nodes) lie within 2.0 units of the center point.The coordinates provided when reading this property are valid only if the object has a volume to be measured. For a geometry containing no vertex data or a node containing no geometry (and whose child nodes, if any, contain no geometry), the values center and radius are both zero.
   * @see https://developer.apple.com/documentation/scenekit/scnboundingvolume/2034707-boundingsphere
   */
  getBoundingSphere() {
    const c = new SCNVector3(0, 0, 0)
    const r = this.height * 0.5

    return { center: c, radius: r }
  }

  _updateBoundingBoxForSkinner(skinner = null){
    if(skinner === null){
      return this.boundingBox
    }
    return super._updateBoundingBoxForSkinner(skinner)
  }
}