Home Reference Source Repository

js/SceneKit/SCNTube.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 tube or pipe geometry—a right circular cylinder with a circular hole along its central axis.
 * @access public
 * @extends {SCNGeometry}
 * @see https://developer.apple.com/documentation/scenekit/scntube
 */
export default class SCNTube extends SCNGeometry {
  // Creating a Tube

  /**
   * Creates a tube geometry with the specified inner radius, outer radius, and height.
   * @access public
   * @constructor
   * @param {number} innerRadius - The radius of the tube’s circular central hole in the x- and z-axes of its local coordinate space.
   * @param {number} outerRadius - The radius of the tube’s circular cross section in the x- and z-axes of its local coordinate space.
   * @param {number} height - The height of the tube along the y-axis of its local coordinate space.
   * @desc The tube is centered in its local coordinate system. For example, if you create a tube whose outer radius is 5.0, inner radius is 1.0, and height is 10.0, its circular cross section extends from -5.0 to 5.0 along the x- and z-axes, the y-coordinates of its base and top are -5.0 and 5.0, and the hole through its center extends from -0.5 to 0.5 along the x- and z-axes.
   * @see https://developer.apple.com/documentation/scenekit/scntube/1522843-init
   */
  constructor(innerRadius = 0.25, outerRadius = 0.5, height = 1.0) {
    super()

    // Adjusting a Tube’s Dimensions

    /**
     * The radius of the tube’s outer circular cross section. Animatable.
     * @type {number}
     * @see https://developer.apple.com/documentation/scenekit/scntube/1523270-outerradius
     */
    this.outerRadius = outerRadius

    /**
     * The radius of the circular hole through the tube. Animatable.
     * @type {number}
     * @see https://developer.apple.com/documentation/scenekit/scntube/1524070-innerradius
     */
    this.innerRadius = innerRadius

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


    // Adjusting Geometric Detail

    /**
     * The number of subdivisions around the circumference of the tube. Animatable.
     * @type {number}
     * @see https://developer.apple.com/documentation/scenekit/scntube/1523619-radialsegmentcount
     */
    this.radialSegmentCount = 48

    /**
     * The number of subdivisions in the inner and outer surfaces of the tube along its y-axis. Animatable.
     * @type {number}
     * @see https://developer.apple.com/documentation/scenekit/scntube/1523080-heightsegmentcount
     */
    this.heightSegmentCount = 1

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

  _createGeometry() {
    const sourceData = []

    const top = this.height * 0.5
    const bottom = -this.height * 0.5

    const outerData = []
    const innerData = []
    const topData = []
    const bottomData = []

    const rStep = 2.0 * Math.PI / this.radialSegmentCount
    const tStep = 1.0 / this.radialSegmentCount
    for(let i=0; i<=this.radialSegmentCount; i++){
      const x = -Math.sin(rStep * i)
      const z = -Math.cos(rStep * i)
      const ovx = x * this.outerRadius
      const ovz = z * this.outerRadius
      const ivx = x * this.innerRadius
      const ivz = z * this.innerRadius

      // vertex
      outerData.push(ovx, bottom, ovz)
      innerData.push(ivx, top, ivz)
      topData.push(ovx, top, ovz)
      bottomData.push(-ovx, bottom, ovz)

      // normal
      outerData.push(x, 0, z)
      innerData.push(-x, 0, -z)
      topData.push(0, 1, 0)
      bottomData.push(0, -1, 0)

      // texcoord
      const tx = tStep * i
      outerData.push(tx, 1.0)
      innerData.push(1.0 - tx, 0.0)

      const ttx = 0.5 + Math.cos(rStep * i) * 0.5
      const tty = 0.5 + Math.sin(rStep * i) * 0.5
      topData.push(ttx, tty)
      bottomData.push(ttx, tty)

      // vertex
      outerData.push(ovx, top, ovz)
      innerData.push(ivx, bottom, ivz)
      topData.push(ivx, top, ivz)
      bottomData.push(-ivx, bottom, ivz)

      // normal
      outerData.push(x, 0, z)
      innerData.push(-x, 0, -z)
      topData.push(0, 1, 0)
      bottomData.push(0, -1, 0)

      // texcoord
      outerData.push(tx, 0.0)
      innerData.push(1.0 - tx, 1.0)

      const ttx2 = 0.5 + Math.cos(rStep * i) * 0.25
      const tty2 = 0.5 + Math.sin(rStep * i) * 0.25
      topData.push(ttx2, tty2)
      bottomData.push(ttx2, tty2)
    }
    sourceData.push(...outerData, ...innerData, ...topData, ...bottomData)

    const vectorCount = (this.radialSegmentCount + 1) * 8
    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 elements = []
    for(let i=0; i<4; i++){
      const indexData = []
      const offset = (this.radialSegmentCount + 1) * i * 2
      for(let j=0; j<this.radialSegmentCount; j++){
        const base = offset + j * 2
        indexData.push(base, base + 3, base + 1)
        indexData.push(base, base + 2, base + 3)
      }
      elements.push(new SCNGeometryElement(indexData, SCNGeometryPrimitiveType.triangles))
    }

    this._geometryElements = elements
    this._geometrySources = [vertexSource, normalSource, texcoordSource]
    this.boundingBox = {
      min: new SCNVector3(-this.outerRadius, bottom, -this.outerRadius),
      max: new SCNVector3(this.outerRadius, top, this.outerRadius)
    }
  }

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

}