Source: packages/three/geometries/TubeGeometry.js

import { Geometry, BufferGeometry, Float32BufferAttribute, Vector2, Vector3 } from 'three';

/**@class ThreeExt */
class ThreeExt {
}

/**Tube geometry providing vertex attribute 'dir'.
 *
 * Modified from three.js source.
 * @author oosmoxiecode / https://github.com/oosmoxiecode
 * @author WestLangley / https://github.com/WestLangley
 * @author zz85 / https://github.com/zz85
 * @author miningold / https://github.com/miningold
 * @author jonobr1 / https://github.com/jonobr1
 * @author Mugen87 / https://github.com/Mugen87
 *
 * @constructor DirTubeGeometry
 * @param {THREE.Curve} path
 * @param {int} tubularSegments
 * @param {number} radius
 * @param {int} radialSegments
 * @param {bool} closed
 * @memberof ThreeExt
 */
function DirTubeGeometry( path, tubularSegments, radius, radialSegments, closed ) {

	Geometry.call( this );

	this.type = 'DirTubeGeometry';

	this.parameters = {
		path: path,
		tubularSegments: tubularSegments,
		radius: radius,
		radialSegments: radialSegments,
		closed: closed
	};

	// if ( taper !== undefined ) console.warn( 'THREE.TubeGeometry: taper has been removed.' );

	var bufferGeometry = new DirTubeBufferGeometry( path, tubularSegments, radius, radialSegments, closed );

	// expose internals

	this.tangents = bufferGeometry.tangents;
	this.normals = bufferGeometry.normals;
	this.binormals = bufferGeometry.binormals;

	// create geometry

	this.fromBufferGeometry( bufferGeometry );
	this.mergeVertices();

}

DirTubeGeometry.prototype = Object.create( Geometry.prototype );
DirTubeGeometry.prototype.constructor = DirTubeGeometry;

/**Tube geometry providing vertex attribute 'a_tan', the path direction.
 *
 * Modified from three.js source.
 * @author oosmoxiecode / https://github.com/oosmoxiecode
 * @author WestLangley / https://github.com/WestLangley
 * @author zz85 / https://github.com/zz85
 * @author miningold / https://github.com/miningold
 * @author jonobr1 / https://github.com/jonobr1
 * @author Mugen87 / https://github.com/Mugen87
 *
 * @class DirTubeBufferGeometry
 * @param {THREE.Curve} path
 * @param {int} tubularSegments
 * @param {number} radius
 * @param {int} radialSegments
 * @param {bool} closed
 * @memberof ThreeExt
 */
function DirTubeBufferGeometry( path, tubularSegments, radius, radialSegments, closed ) {

	BufferGeometry.call( this );

	this.type = 'DirTubeBufferGeometry';

	this.parameters = {
		path: path,
		tubularSegments: tubularSegments,
		radius: radius,
		radialSegments: radialSegments,
		closed: closed
	};

	tubularSegments = tubularSegments || 64;
	radius = radius || 1;
	radialSegments = radialSegments || 8;
	closed = closed || false;

	// {tangents: tangents,
	//	normals: normals,
	//	binormals: binormals}
	var frames = path.computeFrenetFrames( tubularSegments, closed );

	// expose internals

	this.tangents = frames.tangents;
	this.normals = frames.normals;
	this.binormals = frames.binormals;

	// helper variables

	var vertex = new Vector3();
	var normal = new Vector3();
	var uv = new Vector2();
	var P = new Vector3();

	var i, j;

	// buffer

	var vertices = [];
	var normals = [];
	var uvs = [];
	var indices = [];
	// odys: vertices' tangents
	var vertans = [];

	// ody
	var dirs = [];

	// 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 ) );
	// odys:
	this.setAttribute( 'a_tan', new Float32BufferAttribute( vertans, 3 ) );

	// functions

	function generateBufferData() {

		for ( i = 0; i < tubularSegments; i ++ ) {
			generateSegment( i );

			// ody
			var T = frames.tangents[i];
			dirs.push ( T.x, T.y, T.z );
		}

		// 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

		var N = frames.normals[ i ];
		var B = frames.binormals[ i ];
		// odys
		var T = frames.tangents[ i ];

		// generate normals and vertices for the current segment

		for ( j = 0; j <= radialSegments; j ++ ) {

			var v = j / radialSegments * Math.PI * 2;

			var sin = Math.sin( v );
			var 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 );

			// odys: Why not do this in GPU?
			vertans.push( T.x, T.y, T.z );
		}
	}

	function generateIndices() {
		for ( j = 1; j <= tubularSegments; j ++ ) {
			for ( i = 1; i <= radialSegments; i ++ ) {
				var a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 );
				var b = ( radialSegments + 1 ) * j + ( i - 1 );
				var c = ( radialSegments + 1 ) * j + i;
				var d = ( radialSegments + 1 ) * ( j - 1 ) + i;

				// faces
				indices.push( a, b, d );
				indices.push( b, c, d );
			}
		}
	}

	function generateUVs() {
		for ( i = 0; i <= tubularSegments; i ++ ) {
			for ( j = 0; j <= radialSegments; j ++ ) {
				uv.x = i / tubularSegments;
				uv.y = j / radialSegments;

				uvs.push( uv.x, uv.y );
			}
		}
	}

}

DirTubeBufferGeometry.prototype = Object.create( BufferGeometry.prototype );
DirTubeBufferGeometry.prototype.constructor = DirTubeBufferGeometry;

DirTubeBufferGeometry.prototype.toJSON = function () {

	var data = BufferGeometry.prototype.toJSON.call( this );
	// odys: what about vertans?

	data.path = this.parameters.path.toJSON();

	return data;
};

export { DirTubeGeometry, DirTubeBufferGeometry };