Home Reference Source Repository

js/SceneKit/SCNCone.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 cone or frustum geometry.
 * @access public
 * @extends {SCNGeometry}
 * @see https://developer.apple.com/documentation/scenekit/scncone
 */
export default class SCNCone extends SCNGeometry {

  // Creating a Cone

  /**
   * Creates a cone geometry with the given top radius, bottom radius, and height.
   * @constructor
   * @access public
   * @param {number} topRadius - The radius of the cone’s top, forming a circle in the x- and z-axis dimensions of its local coordinate space.
   * @param {number} bottomRadius - The radius of the cone’s base, forming a circle in the x- and z-axis dimensions of its local coordinate space.
   * @param {number} height - The height of the cone along the y-axis of its local coordinate space.
   * @desc The cone is centered in its local coordinate system. For example, if you create a cone whose bottom radius is 5.0, top radius is 0.0, and height is 10.0, its apex is at the point {0, 5.0, 0}, and its base lies in the plane whose y-coordinate is -5.0, extending from -5.0 to 5.0 along both the x- and z-axes.Pass zero for topRadius or bottomRadius or parameter to create a cone whose sides taper to a single point, or a different value to create a frustum with a circular top.
   * @see https://developer.apple.com/documentation/scenekit/scncone/1522863-init
   */
  constructor(topRadius = 0.0, bottomRadius = 0.5, height = 1.0) {
    super([], [])

    // Adjusting a Cone’s Dimensions

    /**
     * The radius of the cone’s circular top. Animatable.
     * @type {number}
     * @see https://developer.apple.com/documentation/scenekit/scncone/1524240-topradius
     */
    this.topRadius = topRadius

    /**
     * The radius of the cone’s circular base. Animatable.
     * @type {number}
     * @see https://developer.apple.com/documentation/scenekit/scncone/1523198-bottomradius
     */
    this.bottomRadius = bottomRadius

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


    // Adjusting Geometric Detail

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

    /**
     * The number of subdivisions in the sides of the cone along its y-axis. Animatable.
     * @type {number}
     * @see https://developer.apple.com/documentation/scenekit/scncone/1524113-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 sideData = []
    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 tvx = x * this.topRadius
      const tvz = z * this.topRadius
      const bvx = x * this.bottomRadius
      const bvz = z * this.bottomRadius

      // vertex
      sideData.push(bvx, bottom, bvz)
      bottomData.push(-bvx, bottom, bvz)

      // normal
      sideData.push(x, 0, z)
      bottomData.push(0, -1, 0)

      // texcoord
      const tx = tStep * i
      sideData.push(tx, 1.0)

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

      // vertex
      sideData.push(tvx, top, tvz)
      bottomData.push(0, bottom, 0)

      // normal
      sideData.push(x, 0, z)
      bottomData.push(0, -1, 0)

      // texcoord
      sideData.push(tx, 0.0)
      bottomData.push(0.5, 0.5)
    }
    sourceData.push(...sideData, ...bottomData)

    const vectorCount = (this.radialSegmentCount + 1) * 4 // TODO: use heightSegmentCount
    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 = []
    const indexData0 = []
    const indexData1 = []
    const offset1 = (this.radialSegmentCount + 1) * 2
    for(let i=0; i<this.radialSegmentCount; i++){
      const base0 = i * 2
      indexData0.push(base0, base0 + 2, base0 + 3)
      const base1 = offset1 + base0
      indexData1.push(base1, base1 + 2, base1 + 3)
    }
    elements.push(new SCNGeometryElement(indexData0, SCNGeometryPrimitiveType.triangles))
    elements.push(new SCNGeometryElement(indexData1, SCNGeometryPrimitiveType.triangles))

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