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 subdivision 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 ) ) );
}
}
copy( source ) {
super.copy( source );
this.parameters = Object.assign( {}, source.parameters );
return this;
}
static fromJSON( data ) {
return new PolyhedronGeometry( data.vertices, data.indices, data.radius, data.details );
}
}
export { PolyhedronGeometry };