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 CylinderGeometry extends BufferGeometry { constructor( radiusTop = 1, radiusBottom = 1, height = 1, radialSegments = 8, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2 ) { super(); this.type = 'CylinderGeometry'; this.parameters = { radiusTop: radiusTop, radiusBottom: radiusBottom, height: height, radialSegments: radialSegments, heightSegments: heightSegments, openEnded: openEnded, thetaStart: thetaStart, thetaLength: thetaLength, }; const scope = this; radialSegments = Math.floor(radialSegments); heightSegments = Math.floor(heightSegments); // buffers const indices = []; const vertices = []; const normals = []; const uvs = []; // helper variables let index = 0; const indexArray = []; const halfHeight = height / 2; let groupStart = 0; // generate geometry generateTorso(); if (openEnded === false) { if (radiusTop > 0) generateCap(true); if (radiusBottom > 0) generateCap(false); } // 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)); function generateTorso() { const normal = new Vector3(); const vertex = new Vector3(); let groupCount = 0; // this will be used to calculate the normal const slope = (radiusBottom - radiusTop) / height; // generate vertices, normals and uvs for (let y = 0; y <= heightSegments; y++) { const indexRow = []; const v = y / heightSegments; // calculate the radius of the current row const radius = v * (radiusBottom - radiusTop) + radiusTop; for (let x = 0; x <= radialSegments; x++) { const u = x / radialSegments; const theta = u * thetaLength + thetaStart; const sinTheta = Math.sin(theta); const cosTheta = Math.cos(theta); // vertex vertex.x = radius * sinTheta; vertex.y = -v * height + halfHeight; vertex.z = radius * cosTheta; vertices.push(vertex.x, vertex.y, vertex.z); // normal normal.set(sinTheta, slope, cosTheta).normalize(); normals.push(normal.x, normal.y, normal.z); // uv uvs.push(u, 1 - v); // save index of vertex in respective row indexRow.push(index++); } // now save vertices of the row in our index array indexArray.push(indexRow); } // generate indices for (let x = 0; x < radialSegments; x++) { for (let y = 0; y < heightSegments; y++) { // we use the index array to access the correct indices const a = indexArray[y][x]; const b = indexArray[y + 1][x]; const c = indexArray[y + 1][x + 1]; const d = indexArray[y][x + 1]; // faces indices.push(a, b, d); indices.push(b, c, d); // update group counter groupCount += 6; } } // add a group to the geometry. this will ensure multi material support scope.addGroup(groupStart, groupCount, 0); // calculate new start value for groups groupStart += groupCount; } function generateCap(top) { // save the index of the first center vertex const centerIndexStart = index; const uv = new Vector2(); const vertex = new Vector3(); let groupCount = 0; const radius = top === true ? radiusTop : radiusBottom; 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, // we must generate a center vertex per face/segment for (let x = 1; x <= radialSegments; x++) { // vertex vertices.push(0, halfHeight * sign, 0); // normal normals.push(0, sign, 0); // uv uvs.push(0.5, 0.5); // increase index index++; } // save the index of the last center vertex const centerIndexEnd = index; // 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++; } // generate indices for (let x = 0; x < radialSegments; x++) { const c = centerIndexStart + x; const i = centerIndexEnd + x; 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; } } static fromJSON(data) { return new CylinderGeometry( data.radiusTop, data.radiusBottom, data.height, data.radialSegments, data.heightSegments, data.openEnded, data.thetaStart, data.thetaLength ); } } export { CylinderGeometry, CylinderGeometry as CylinderBufferGeometry };