UNPKG

three

Version:

JavaScript 3D library

333 lines (210 loc) 7.55 kB
import { BufferGeometry } from '../core/BufferGeometry.js'; import { Float32BufferAttribute } from '../core/BufferAttribute.js'; import { Vector3 } from '../math/Vector3.js'; import { Vector2 } from '../math/Vector2.js'; /** * A geometry class for representing a cylinder. * * ```js * const geometry = new THREE.CylinderGeometry( 5, 5, 20, 32 ); * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); * const cylinder = new THREE.Mesh( geometry, material ); * scene.add( cylinder ); * ``` * * @augments BufferGeometry */ class CylinderGeometry extends BufferGeometry { /** * Constructs a new cylinder geometry. * * @param {number} [radiusTop=1] - Radius of the cylinder at the top. * @param {number} [radiusBottom=1] - Radius of the cylinder at the bottom. * @param {number} [height=1] - Height of the cylinder. * @param {number} [radialSegments=32] - Number of segmented faces around the circumference of the cylinder. * @param {number} [heightSegments=1] - Number of rows of faces along the height of the cylinder. * @param {boolean} [openEnded=false] - Whether the base of the cylinder is open or capped. * @param {number} [thetaStart=0] - Start angle for first segment, in radians. * @param {number} [thetaLength=Math.PI*2] - The central angle, often called theta, of the circular sector, in radians. * The default value results in a complete cylinder. */ constructor( radiusTop = 1, radiusBottom = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2 ) { super(); this.type = 'CylinderGeometry'; /** * Holds the constructor parameters that have been * used to generate the geometry. Any modification * after instantiation does not change the geometry. * * @type {Object} */ this.parameters = { radiusTop: radiusTop, radiusBottom: radiusBottom, height: height, radialSegments: radialSegments, heightSegments: heightSegments, openEnded: openEnded, thetaStart: thetaStart, thetaLength: thetaLength }; const scope = this; radialSegments = Math.floor( radialSegments ); heightSegments = Math.floor( heightSegments ); // buffers const indices = []; const vertices = []; const normals = []; const uvs = []; // helper variables let index = 0; const indexArray = []; const halfHeight = height / 2; let groupStart = 0; // generate geometry generateTorso(); if ( openEnded === false ) { if ( radiusTop > 0 ) generateCap( true ); if ( radiusBottom > 0 ) generateCap( false ); } // build geometry this.setIndex( indices ); this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); function generateTorso() { const normal = new Vector3(); const vertex = new Vector3(); let groupCount = 0; // this will be used to calculate the normal const slope = ( radiusBottom - radiusTop ) / height; // generate vertices, normals and uvs for ( let y = 0; y <= heightSegments; y ++ ) { const indexRow = []; const v = y / heightSegments; // calculate the radius of the current row const radius = v * ( radiusBottom - radiusTop ) + radiusTop; for ( let x = 0; x <= radialSegments; x ++ ) { const u = x / radialSegments; const theta = u * thetaLength + thetaStart; const sinTheta = Math.sin( theta ); const cosTheta = Math.cos( theta ); // vertex vertex.x = radius * sinTheta; vertex.y = - v * height + halfHeight; vertex.z = radius * cosTheta; vertices.push( vertex.x, vertex.y, vertex.z ); // normal normal.set( sinTheta, slope, cosTheta ).normalize(); normals.push( normal.x, normal.y, normal.z ); // uv uvs.push( u, 1 - v ); // save index of vertex in respective row indexRow.push( index ++ ); } // now save vertices of the row in our index array indexArray.push( indexRow ); } // generate indices for ( let x = 0; x < radialSegments; x ++ ) { for ( let y = 0; y < heightSegments; y ++ ) { // we use the index array to access the correct indices const a = indexArray[ y ][ x ]; const b = indexArray[ y + 1 ][ x ]; const c = indexArray[ y + 1 ][ x + 1 ]; const d = indexArray[ y ][ x + 1 ]; // faces if ( radiusTop > 0 || y !== 0 ) { indices.push( a, b, d ); groupCount += 3; } if ( radiusBottom > 0 || y !== heightSegments - 1 ) { indices.push( b, c, d ); groupCount += 3; } } } // add a group to the geometry. this will ensure multi material support scope.addGroup( groupStart, groupCount, 0 ); // calculate new start value for groups groupStart += groupCount; } function generateCap( top ) { // save the index of the first center vertex const centerIndexStart = index; const uv = new Vector2(); const vertex = new Vector3(); let groupCount = 0; const radius = ( top === true ) ? radiusTop : radiusBottom; const sign = ( top === true ) ? 1 : - 1; // first we generate the center vertex data of the cap. // because the geometry needs one set of uvs per face, // we must generate a center vertex per face/segment for ( let x = 1; x <= radialSegments; x ++ ) { // vertex vertices.push( 0, halfHeight * sign, 0 ); // normal normals.push( 0, sign, 0 ); // uv uvs.push( 0.5, 0.5 ); // increase index index ++; } // save the index of the last center vertex const centerIndexEnd = index; // now we generate the surrounding vertices, normals and uvs for ( let x = 0; x <= radialSegments; x ++ ) { const u = x / radialSegments; const theta = u * thetaLength + thetaStart; const cosTheta = Math.cos( theta ); const sinTheta = Math.sin( theta ); // vertex vertex.x = radius * sinTheta; vertex.y = halfHeight * sign; vertex.z = radius * cosTheta; vertices.push( vertex.x, vertex.y, vertex.z ); // normal normals.push( 0, sign, 0 ); // uv uv.x = ( cosTheta * 0.5 ) + 0.5; uv.y = ( sinTheta * 0.5 * sign ) + 0.5; uvs.push( uv.x, uv.y ); // increase index index ++; } // generate indices for ( let x = 0; x < radialSegments; x ++ ) { const c = centerIndexStart + x; const i = centerIndexEnd + x; if ( top === true ) { // face top indices.push( i, i + 1, c ); } else { // face bottom indices.push( i + 1, i, c ); } groupCount += 3; } // add a group to the geometry. this will ensure multi material support scope.addGroup( groupStart, groupCount, top === true ? 1 : 2 ); // calculate new start value for groups groupStart += groupCount; } } copy( source ) { super.copy( source ); this.parameters = Object.assign( {}, source.parameters ); return this; } /** * Factory method for creating an instance of this class from the given * JSON object. * * @param {Object} data - A JSON object representing the serialized geometry. * @return {CylinderGeometry} A new instance. */ static fromJSON( data ) { return new CylinderGeometry( data.radiusTop, data.radiusBottom, data.height, data.radialSegments, data.heightSegments, data.openEnded, data.thetaStart, data.thetaLength ); } } export { CylinderGeometry };