Home Reference Source Repository

js/SceneKit/SCNSphere.js

'use strict'

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

/**
 * A sphere (or ball or globe) geometry.
 * @access public
 * @extends {SCNGeometry}
 * @see https://developer.apple.com/documentation/scenekit/scnsphere
 */
export default class SCNSphere extends SCNGeometry {
  static get _propTypes() {
    return {
      $constructor: (propNames, propValues) => {
        const sphere = new SCNSphere(propValues.sphereradius)
        sphere.isGeodesic = propValues.spheregeodesic
        sphere.segmentCount = propValues.spheresegmentCount
        sphere.name = propValues.name
        sphere._sphereRadialSpan = propValues.sphereRadialSpan
        sphere._isHemispheric = propValues.spherehemispheric
        sphere._spherePrimitiveType = propValues.sphereprimitiveType
        sphere.materials = propValues.materials
        sphere.tessellator = propValues.tessellator
        sphere.subdivisionLevel = propValues.subdivisionLevel
        sphere.wantsAdaptiveSubdivision = propValues.wantsAdaptiveSubdivision
        sphere._createGeometry()

        return sphere
      },
      sphereradius: ['float', null],
      spheregeodesic: ['boolean', null],
      spheresegmentCount: ['integer', null],
      materials: ['NSArray', null],
      tessellator: ['SCNGeometryTessellator', null],
      wantsAdaptiveSubdivision: ['boolean', null],

      adaptiveSubdivision: ['boolean', null],
      name: ['string', null],
      sphereradialSpan: ['float', null],
      spherehemispheric: ['boolean', null],
      sphereprimitiveType: ['integer', null],
      subdivisionLevel: ['integer', null],
      subdivisionSettings: ['bytes', null]
    }
  }

  // Creating a Sphere

  /**
   * Creates a sphere geometry with the specified radius.
   * @access public
   * @constructor
   * @param {number} radius - The radius of the sphere in its local coordinate space.
   * @desc The sphere is centered in its local coordinate system. For example, if you create a sphere whose radius is 5.0, it extends from -5.0 to 5.0 along each of the the x, y, and z-axes.
   * @see https://developer.apple.com/documentation/scenekit/scnsphere/1522601-init
   */
  constructor(radius = 0.5) {
    super([], [])

    // Adjusting a Sphere’s Dimensions

    /**
     * The radius of the sphere. Animatable.
     * @type {number}
     * @see https://developer.apple.com/documentation/scenekit/scnsphere/1523787-radius
     */
    this.radius = radius

    // Adjusting Geometric Detail

    /**
     * A Boolean value specifying whether SceneKit uses a geodesic polygon mesh to render the sphere.
     * @type {boolean}
     * @see https://developer.apple.com/documentation/scenekit/scnsphere/1523268-isgeodesic
     */
    this.isGeodesic = false

    /**
     * A number determining the detail of the polygon mesh SceneKit uses to render the sphere. Animatable.
     * @type {number}
     * @see https://developer.apple.com/documentation/scenekit/scnsphere/1523912-segmentcount
     */
    this.segmentCount = 24

    this._sphereRadialSpan = 0
    this._spherePrimitiveType = 0
    this._isHemispheric = false

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

  _createGeometry() {
    const sourceData = []
    const indexData = []
    const vectorCount = (this.segmentCount + 1) * (this.segmentCount + 1)
    const primitiveCount = (this.segmentCount - 1) * this.segmentCount * 2

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

    for(let lng=0; lng<=this.segmentCount; lng++){
      const x = -Math.sin(2.0 * Math.PI * lng / this.segmentCount)
      const z = -Math.cos(2.0 * Math.PI * lng / this.segmentCount)
      for(let lat=0; lat<=this.segmentCount; lat++){
        const xNom = x * ySin[lat]
        const zNom = z * ySin[lat]

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

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

        // texcoord
        sourceData.push(lng / this.segmentCount, 1.0 - lat / this.segmentCount)
      }
    }

    for(let i=0; i<this.segmentCount; i++){
      let index1 = i * (this.segmentCount + 1)
      let index2 = index1 + this.segmentCount + 2

      indexData.push(index1, index2, index1 + 1)
      index1 += 1
      for(let j=0; j<this.segmentCount-2; j++){
        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.radius, -this.radius, -this.radius),
      max: new SCNVector3(this.radius, this.radius, this.radius)
    }
  }

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