UNPKG

@polygonjs/polygonjs

Version:

node-based WebGL 3D engine https://polygonjs.com

201 lines (170 loc) 5.99 kB
import {BaseSopOperation} from './_Base'; import {CoreGroup} from '../../../core/geometry/Group'; import {InputCloneMode} from '../../../engine/poly/InputCloneMode'; import {rotateGeometry} from '../../../core/Transform'; import {isBooleanTrue} from '../../../core/Type'; import {Attribute} from '../../../core/geometry/Attribute'; import {Vector3, Triangle, BufferGeometry, Float32BufferAttribute} from 'three'; import {DefaultOperationParams} from '../../../core/operations/_Base'; interface TorusSopParams extends DefaultOperationParams { radius: number; radiusTube: number; segmentsRadial: number; segmentsTube: number; open: boolean; arc: number; cap: boolean; direction: Vector3; center: Vector3; } const DEFAULT_UP = new Vector3(0, 0, 1); const capPositionsCenter0 = new Vector3(); const capCenter0 = new Vector3(); const tmpPos0 = new Vector3(); const tmpPos1 = new Vector3(); const tmpN = new Vector3(); const triangle = new Triangle(); const center = new Vector3(); const position = new Vector3(); const normal = new Vector3(); export class TorusSopOperation extends BaseSopOperation { static override readonly DEFAULT_PARAMS: TorusSopParams = { radius: 1, radiusTube: 0.25, segmentsRadial: 20, segmentsTube: 50, open: false, arc: Math.PI * 2, cap: true, direction: new Vector3(0, 1, 0), center: new Vector3(0, 0, 0), }; static override readonly INPUT_CLONED_STATE = InputCloneMode.FROM_NODE; static override type(): Readonly<'torus'> { return 'torus'; } override cook(input_contents: CoreGroup[], params: TorusSopParams) { const arc = isBooleanTrue(params.open) ? params.arc : Math.PI * 2; const cap: boolean = isBooleanTrue(params.open) ? params.cap : false; const radialSegments = Math.floor(params.segmentsRadial); const tubularSegments = Math.floor(params.segmentsTube); const {radius, radiusTube} = params; // buffers const indices: number[] = []; const positions: number[] = []; const normals: number[] = []; const uvs: number[] = []; // generate positions, normals and uvs const capIndices0: number[] = []; const capIndices1: number[] = []; function setPosition(i: number, j: number, position: Vector3) { const u = (i / tubularSegments) * arc; const v = (j / radialSegments) * Math.PI * 2; // vertex position.x = (radius + radiusTube * Math.cos(v)) * Math.cos(u); position.y = (radius + radiusTube * Math.cos(v)) * Math.sin(u); position.z = radiusTube * Math.sin(v); return {u, v}; } for (let j = 0; j <= radialSegments; j++) { for (let i = 0; i <= tubularSegments; i++) { const {u} = setPosition(i, j, position); positions.push(position.x, position.y, position.z); // if (cap) { // if (i == 0) { // capIndices0.push(positions.length / 3 - 1); // } else if (i == tubularSegments) { // capIndices1.push(positions.length / 3 - 1); // } // } // normal center.x = radius * Math.cos(u); center.y = radius * Math.sin(u); normal.subVectors(position, center).normalize(); normals.push(normal.x, normal.y, normal.z); // uv uvs.push(i / tubularSegments); uvs.push(j / radialSegments); } } if (cap) { let i = 0; for (let j = 0; j <= radialSegments; j++) { setPosition(i, j, position); positions.push(position.x, position.y, position.z); // normal is pushed here to correctly set the length of the array // but will be overriden in _addCap normals.push(-1, -1, -1); capIndices0.push(positions.length / 3 - 1); } i = tubularSegments; for (let j = 0; j <= radialSegments; j++) { setPosition(i, j, position); positions.push(position.x, position.y, position.z); normals.push(-1, -1, -1); capIndices1.push(positions.length / 3 - 1); } } // generate indices for (let j = 1; j <= radialSegments; j++) { for (let i = 1; i <= tubularSegments; i++) { // indices const a = (tubularSegments + 1) * j + i - 1; const b = (tubularSegments + 1) * (j - 1) + i - 1; const c = (tubularSegments + 1) * (j - 1) + i; const d = (tubularSegments + 1) * j + i; // faces indices.push(a, b, d); indices.push(b, c, d); } } if (cap) { this._addCap(capIndices0, positions, normals, indices, false); this._addCap(capIndices1, positions, normals, indices, true); } // build geometry const geometry = new BufferGeometry(); geometry.setIndex(indices); geometry.setAttribute(Attribute.POSITION, new Float32BufferAttribute(positions, 3)); geometry.setAttribute(Attribute.NORMAL, new Float32BufferAttribute(normals, 3)); geometry.setAttribute(Attribute.UV, new Float32BufferAttribute(uvs, 2)); rotateGeometry(geometry, DEFAULT_UP, params.direction); geometry.translate(params.center.x, params.center.y, params.center.z); return this.createCoreGroupFromGeometry(geometry); } private _addCap(capIndices: number[], positions: number[], normals: number[], indices: number[], invert: boolean) { const capPointsCount = capIndices.length; if (capPointsCount <= 2) { return; } capCenter0.set(0, 0, 0); for (let i = 0; i < capPointsCount; i++) { capPositionsCenter0.fromArray(positions, capIndices[i] * 3); capCenter0.add(capPositionsCenter0); } capCenter0.divideScalar(capIndices.length); positions.push(capCenter0.x, capCenter0.y, capCenter0.z); const centerIndex = positions.length / 3 - 1; tmpPos0.fromArray(positions, capIndices[0] * 3); tmpPos1.fromArray(positions, capIndices[1] * 3); triangle.a.copy(tmpPos0); triangle.b.copy(tmpPos1); triangle.c.copy(capCenter0); triangle.getNormal(tmpN); if (invert) { tmpN.multiplyScalar(-1); } normals.push(tmpN.x, tmpN.y, tmpN.z); for (let i = 0; i < capIndices.length - 1; i++) { const a = centerIndex; const b = capIndices[i]; const c = capIndices[i + 1]; if (invert) { indices.push(c, b, a); } else { indices.push(a, b, c); } tmpN.toArray(normals, 3 * b); } } }