Why do the CylinderGeometry caps have multiplied centers?

In my addons hofk (Klaus Hoffmeister) · GitHub I defined the geometry myself. Old geometry, indexed and non-indexed.

The example of @prisoner849 and my one based on it ( Bend cylinder on specific path - #4 by prisoner849 ) change a CylinderGeometry.

There without caps.

For another project I also change the caps.

Therefore I looked into the three.js sourccode. There you find for the CircleGeometry the expected variant for an indexed geometry. The center is a vertex.
For the CylinderGeometry caps, however, an extra vertex is created for each triangle. This is actually the procedure for non-indexed geometry.

I have changed the function to only one vertex for the center. Works of course.

See code below. The changes are marked or commented.

Is there a reason (e.g. usage in another part of the framework) for the duplicated centers of the caps?

My problem is that I rely on continuity when using CylinderGeometry. If the definition is ever changed, my modification will no longer work.

The multiplied centers also make more effort.


The changed function:

function generateCap( top ) {

	// save the index of the first center vertex
	// const centerIndexStart = index;	//	is no longer required in case of change
	
	const uv = new Vector2();
	const vertex = new Vector3();

	let groupCount = 0;

	const radius = ( top ) ? radiusTop : radiusBottom;	// const radius = ( top === true ) ? radiusTop : radiusBottom;
	const sign = ( top ) ? 1 : - 1;	// 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,  // ??? why
	// we must generate a center vertex per face/segment // ???

	// for ( let x = 1; x <= radialSegments; x ++ ) {	// No loop here, only one vertex

		// vertex

		vertices.push( 0, halfHeight * sign, 0 );

		// normal

		normals.push( 0, sign, 0 );

		// uv

		uvs.push( 0.5, 0.5 );

		// increase index

		// index ++; //  see below:   index ++;  // added for bottom

	//}  // No loop here, only one vertex

	// save the index of the last center vertex
	const centerIndex = index;	//const centerIndexEnd = index; 	// only one index after change

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

	}
	
	index ++;  //  after change  added for bottom

	// generate indices

	for ( let x = 1; x <= radialSegments; x ++ ) { // for ( let x = 0; x < radialSegments; x ++ ) { // x = 0 to x = 1 , < to <=

		const c = centerIndex;	// const c = centerIndexStart + x;		//  only one index after change
		const i = centerIndex + x;	// const i = centerIndexEnd + x;	// only one index after change

		if ( top ) { // 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;

}

}


Addendum

I have now noticed that the description of the start angle in the docs does not fit.

thetaStart — Start angle for first segment, default = 0 (three o’clock position).

The mantle starts from +z.

However, the uv values of the upper cap are aligned to + x.

const  mat1 = new THREE.MeshBasicMaterial( { 
	wireframe: false, side: THREE.DoubleSide, map: new THREE.TextureLoader( ).load( 'uvgrid01.png')
	} );
const geometry = new THREE.CylinderGeometry( 1, 1, 2, 32, 5, false,  Math.PI / 4, Math.PI );
const cylinder = new THREE.Mesh( geometry, mat1 );
scene.add( cylinder );

I circumvent the possible problem by using a BasicGeometry derived from and greatly reduced by the CylinderGeometry instead. It is thus a self-defined geometry.

One advantage is that values for the cylinder do not have to be calculated first, which are then immediately replaced by the numbers for the curved geometry.

This is the reduced geometry code, the normals and some uv values are then determined later.

function BasicGeometry( radialSegments, heightSegments, withTop, withBottom ) {
	
	let indices = [];
	let uvs = [];
	
	let index = 0;
	let indexArray = [];
	let groupStart = 0; 
	
	let groupCount = 0;
	
	for ( let y = 0; y <= heightSegments; y ++ ) {
	
		let indexRow = [];
		
		let v = y / heightSegments;
		
		for ( let x = 0; x <= radialSegments; x ++ ) {
		
			uvs.push(  x / radialSegments, 1 - v );				
			indexRow.push( index ++ );
			
		}
		
		indexArray.push( indexRow );
		
	}
	
	let a, b, c, d;
	
	for ( let i = 0; i < radialSegments; i ++ ) {
	
		for ( let j = 0; j < heightSegments; j ++ ) {
			
			a = indexArray[ j ][ i ];
			b = indexArray[ j + 1 ] [ i ];
			c = indexArray[ j + 1 ][ i + 1 ];
			d = indexArray[ j ] [ i + 1 ];
			
			indices.push( a, b, d );
			indices.push( b, c, d );
			
			groupCount += 6;
			
		}
		
	}
	
	g.addGroup( groupStart, groupCount, 0 ); 
	
	groupStart += groupCount;
	
	let verticesCount = ( radialSegments + 1 ) * ( heightSegments + 1 )
	
	if ( wTop ) generateCap( true );
	if ( wBtm ) generateCap( false );
	
	g.setIndex( new THREE.BufferAttribute( new Uint32Array( indices ), 1 ) );
	g.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( verticesCount * 3 ), 3 ) );
	g.setAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( uvs ), 2 ) );
	
	function generateCap( top ) {

		let groupCount = 0;
		
		uvs.push( 0.5, 0.5 );
		
		const centerIndex = index; 
		
		for ( let x = 0; x <= radialSegments; x ++ ) {
			
			uvs.push( 0, 0 );				
			index ++;
			
		}
		
		index ++;
		
		for ( let x = 1; x <= radialSegments; x ++ ) {
		
			const c = centerIndex;
			const i = centerIndex + x;
			
			if ( top ) {
				
				indices.push( i, i + 1, c );	// face top
			
			} else {
				
				indices.push( i + 1, i, c ); 	// face bottom
				
			}
			
			groupCount += 3;
			
		}
		
		g.addGroup( groupStart, groupCount, top ? 1 : 2 );
		
		groupStart += groupCount;
		
		verticesCount += radialSegments + 2; // with center 
		
	}

}