import { BufferGeometry } from '../core/BufferGeometry.js'; import { Float32BufferAttribute } from '../core/BufferAttribute.js'; import { Vector3 } from '../math/Vector3.js'; import { Vector2 } from '../math/Vector2.js'; class PolyhedronGeometry extends BufferGeometry { constructor( vertices, indices, radius = 1, detail = 0 ) { super(); this.type = 'PolyhedronGeometry'; this.parameters = { vertices: vertices, indices: indices, radius: radius, detail: detail }; // default buffer data const vertexBuffer = []; const uvBuffer = []; // the subdivision creates the vertex buffer data subdivide( detail ); // all vertices should lie on a conceptual sphere with a given radius applyRadius( radius ); // finally, create the uv data generateUVs(); // build non-indexed geometry this.setAttribute( 'position', new Float32BufferAttribute( vertexBuffer, 3 ) ); this.setAttribute( 'normal', new Float32BufferAttribute( vertexBuffer.slice(), 3 ) ); this.setAttribute( 'uv', new Float32BufferAttribute( uvBuffer, 2 ) ); if ( detail === 0 ) { this.computeVertexNormals(); // flat normals } else { this.normalizeNormals(); // smooth normals } // helper functions function subdivide( detail ) { const a = new Vector3(); const b = new Vector3(); const c = new Vector3(); // iterate over all faces and apply a subdivison with the given detail value for ( let i = 0; i < indices.length; i += 3 ) { // get the vertices of the face getVertexByIndex( indices[ i + 0 ], a ); getVertexByIndex( indices[ i + 1 ], b ); getVertexByIndex( indices[ i + 2 ], c ); // perform subdivision subdivideFace( a, b, c, detail ); } } function subdivideFace( a, b, c, detail ) { const cols = detail + 1; // we use this multidimensional array as a data structure for creating the subdivision const v = []; // construct all of the vertices for this subdivision for ( let i = 0; i <= cols; i ++ ) { v[ i ] = []; const aj = a.clone().lerp( c, i / cols ); const bj = b.clone().lerp( c, i / cols ); const rows = cols - i; for ( let j = 0; j <= rows; j ++ ) { if ( j === 0 && i === cols ) { v[ i ][ j ] = aj; } else { v[ i ][ j ] = aj.clone().lerp( bj, j / rows ); } } } // construct all of the faces for ( let i = 0; i < cols; i ++ ) { for ( let j = 0; j < 2 * ( cols - i ) - 1; j ++ ) { const k = Math.floor( j / 2 ); if ( j % 2 === 0 ) { pushVertex( v[ i ][ k + 1 ] ); pushVertex( v[ i + 1 ][ k ] ); pushVertex( v[ i ][ k ] ); } else { pushVertex( v[ i ][ k + 1 ] ); pushVertex( v[ i + 1 ][ k + 1 ] ); pushVertex( v[ i + 1 ][ k ] ); } } } } function applyRadius( radius ) { const vertex = new Vector3(); // iterate over the entire buffer and apply the radius to each vertex for ( let i = 0; i < vertexBuffer.length; i += 3 ) { vertex.x = vertexBuffer[ i + 0 ]; vertex.y = vertexBuffer[ i + 1 ]; vertex.z = vertexBuffer[ i + 2 ]; vertex.normalize().multiplyScalar( radius ); vertexBuffer[ i + 0 ] = vertex.x; vertexBuffer[ i + 1 ] = vertex.y; vertexBuffer[ i + 2 ] = vertex.z; } } function generateUVs() { const vertex = new Vector3(); for ( let i = 0; i < vertexBuffer.length; i += 3 ) { vertex.x = vertexBuffer[ i + 0 ]; vertex.y = vertexBuffer[ i + 1 ]; vertex.z = vertexBuffer[ i + 2 ]; const u = azimuth( vertex ) / 2 / Math.PI + 0.5; const v = inclination( vertex ) / Math.PI + 0.5; uvBuffer.push( u, 1 - v ); } correctUVs(); correctSeam(); } function correctSeam() { // handle case when face straddles the seam, see #3269 for ( let i = 0; i < uvBuffer.length; i += 6 ) { // uv data of a single face const x0 = uvBuffer[ i + 0 ]; const x1 = uvBuffer[ i + 2 ]; const x2 = uvBuffer[ i + 4 ]; const max = Math.max( x0, x1, x2 ); const min = Math.min( x0, x1, x2 ); // 0.9 is somewhat arbitrary if ( max > 0.9 && min < 0.1 ) { if ( x0 < 0.2 ) uvBuffer[ i + 0 ] += 1; if ( x1 < 0.2 ) uvBuffer[ i + 2 ] += 1; if ( x2 < 0.2 ) uvBuffer[ i + 4 ] += 1; } } } function pushVertex( vertex ) { vertexBuffer.push( vertex.x, vertex.y, vertex.z ); } function getVertexByIndex( index, vertex ) { const stride = index * 3; vertex.x = vertices[ stride + 0 ]; vertex.y = vertices[ stride + 1 ]; vertex.z = vertices[ stride + 2 ]; } function correctUVs() { const a = new Vector3(); const b = new Vector3(); const c = new Vector3(); const centroid = new Vector3(); const uvA = new Vector2(); const uvB = new Vector2(); const uvC = new Vector2(); for ( let i = 0, j = 0; i < vertexBuffer.length; i += 9, j += 6 ) { a.set( vertexBuffer[ i + 0 ], vertexBuffer[ i + 1 ], vertexBuffer[ i + 2 ] ); b.set( vertexBuffer[ i + 3 ], vertexBuffer[ i + 4 ], vertexBuffer[ i + 5 ] ); c.set( vertexBuffer[ i + 6 ], vertexBuffer[ i + 7 ], vertexBuffer[ i + 8 ] ); uvA.set( uvBuffer[ j + 0 ], uvBuffer[ j + 1 ] ); uvB.set( uvBuffer[ j + 2 ], uvBuffer[ j + 3 ] ); uvC.set( uvBuffer[ j + 4 ], uvBuffer[ j + 5 ] ); centroid.copy( a ).add( b ).add( c ).divideScalar( 3 ); const azi = azimuth( centroid ); correctUV( uvA, j + 0, a, azi ); correctUV( uvB, j + 2, b, azi ); correctUV( uvC, j + 4, c, azi ); } } function correctUV( uv, stride, vector, azimuth ) { if ( ( azimuth < 0 ) && ( uv.x === 1 ) ) { uvBuffer[ stride ] = uv.x - 1; } if ( ( vector.x === 0 ) && ( vector.z === 0 ) ) { uvBuffer[ stride ] = azimuth / 2 / Math.PI + 0.5; } } // Angle around the Y axis, counter-clockwise when looking from above. function azimuth( vector ) { return Math.atan2( vector.z, - vector.x ); } // Angle above the XZ plane. function inclination( vector ) { return Math.atan2( - vector.y, Math.sqrt( ( vector.x * vector.x ) + ( vector.z * vector.z ) ) ); } } static fromJSON( data ) { return new PolyhedronGeometry( data.vertices, data.indices, data.radius, data.details ); } } export { PolyhedronGeometry, PolyhedronGeometry as PolyhedronBufferGeometry };