import { BufferGeometry } from '../core/BufferGeometry.js'; import { Float32BufferAttribute } from '../core/BufferAttribute.js'; import * as Curves from '../extras/curves/Curves.js'; import { Vector2 } from '../math/Vector2.js'; import { Vector3 } from '../math/Vector3.js'; class TubeGeometry extends BufferGeometry { constructor( path = new Curves[ 'QuadraticBezierCurve3' ]( new Vector3( - 1, - 1, 0 ), new Vector3( - 1, 1, 0 ), new Vector3( 1, 1, 0 ) ), tubularSegments = 64, radius = 1, radialSegments = 8, closed = false ) { super(); this.type = 'TubeGeometry'; this.parameters = { path: path, tubularSegments: tubularSegments, radius: radius, radialSegments: radialSegments, closed: closed }; const frames = path.computeFrenetFrames( tubularSegments, closed ); // expose internals this.tangents = frames.tangents; this.normals = frames.normals; this.binormals = frames.binormals; // helper variables const vertex = new Vector3(); const normal = new Vector3(); const uv = new Vector2(); let P = new Vector3(); // buffer const vertices = []; const normals = []; const uvs = []; const indices = []; // create buffer data generateBufferData(); // 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 ) ); // functions function generateBufferData() { for ( let i = 0; i < tubularSegments; i ++ ) { generateSegment( i ); } // if the geometry is not closed, generate the last row of vertices and normals // at the regular position on the given path // // if the geometry is closed, duplicate the first row of vertices and normals (uvs will differ) generateSegment( ( closed === false ) ? tubularSegments : 0 ); // uvs are generated in a separate function. // this makes it easy compute correct values for closed geometries generateUVs(); // finally create faces generateIndices(); } function generateSegment( i ) { // we use getPointAt to sample evenly distributed points from the given path P = path.getPointAt( i / tubularSegments, P ); // retrieve corresponding normal and binormal const N = frames.normals[ i ]; const B = frames.binormals[ i ]; // generate normals and vertices for the current segment for ( let j = 0; j <= radialSegments; j ++ ) { const v = j / radialSegments * Math.PI * 2; const sin = Math.sin( v ); const cos = - Math.cos( v ); // normal normal.x = ( cos * N.x + sin * B.x ); normal.y = ( cos * N.y + sin * B.y ); normal.z = ( cos * N.z + sin * B.z ); normal.normalize(); normals.push( normal.x, normal.y, normal.z ); // vertex vertex.x = P.x + radius * normal.x; vertex.y = P.y + radius * normal.y; vertex.z = P.z + radius * normal.z; vertices.push( vertex.x, vertex.y, vertex.z ); } } function generateIndices() { for ( let j = 1; j <= tubularSegments; j ++ ) { for ( let i = 1; i <= radialSegments; i ++ ) { const a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 ); const b = ( radialSegments + 1 ) * j + ( i - 1 ); const c = ( radialSegments + 1 ) * j + i; const d = ( radialSegments + 1 ) * ( j - 1 ) + i; // faces indices.push( a, b, d ); indices.push( b, c, d ); } } } function generateUVs() { for ( let i = 0; i <= tubularSegments; i ++ ) { for ( let j = 0; j <= radialSegments; j ++ ) { uv.x = i / tubularSegments; uv.y = j / radialSegments; uvs.push( uv.x, uv.y ); } } } } copy( source ) { super.copy( source ); this.parameters = Object.assign( {}, source.parameters ); return this; } toJSON() { const data = super.toJSON(); data.path = this.parameters.path.toJSON(); return data; } static fromJSON( data ) { // This only works for built-in curves (e.g. CatmullRomCurve3). // User defined curves or instances of CurvePath will not be deserialized. return new TubeGeometry( new Curves[ data.path.type ]().fromJSON( data.path ), data.tubularSegments, data.radius, data.radialSegments, data.closed ); } } export { TubeGeometry };