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 };