UNPKG

@polygonjs/polygonjs

Version:

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

181 lines (168 loc) 6.73 kB
import {BaseSopOperation} from './_Base'; import {CoreGroup} from '../../../core/geometry/Group'; import {Mesh, BufferGeometry, BufferAttribute, Vector2, Vector3, Vector4} from 'three'; import {ObjectType} from '../../../core/geometry/Constant'; import {InputCloneMode} from '../../../engine/poly/InputCloneMode'; import {rangeWithEnd} from '../../../core/ArrayUtils'; import {isBooleanTrue} from '../../../core/BooleanValue'; import {Attribute} from '../../../core/geometry/Attribute'; import {MeshSurfaceSampler} from '../../../modules/core/math/MeshSurfaceSampler'; import {CoreMath} from '../../../core/math/_Module'; import {DefaultOperationParams} from '../../../core/operations/_Base'; interface ScatterSopParams extends DefaultOperationParams { pointsCount: number; seed: number; useWeightAttribute: boolean; weightAttribute: string; transferAttributes: boolean; attributesToTransfer: string; addIdAttribute: boolean; addIdnAttribute: boolean; } const tmpV2 = new Vector2(); const tmpV4 = new Vector4(); export class ScatterSopOperation extends BaseSopOperation { static override readonly DEFAULT_PARAMS: ScatterSopParams = { pointsCount: 100, seed: 0, useWeightAttribute: false, weightAttribute: '', transferAttributes: false, attributesToTransfer: '', addIdAttribute: true, addIdnAttribute: true, }; static override readonly INPUT_CLONED_STATE = InputCloneMode.FROM_NODE; static override type(): Readonly<'scatter'> { return 'scatter'; } private _position = new Vector3(); private _normal = new Vector3(); override cook(inputContents: CoreGroup[], params: ScatterSopParams) { const coreGroup = inputContents[0]; let inputMesh = coreGroup.threejsObjectsWithGeo()[0] as Mesh; if (inputMesh) { return this._createPoints(inputMesh, coreGroup, params); } else { return this.createCoreGroupFromObjects([]); } } private _createPoints(inputMesh: Mesh, coreGroup: CoreGroup, params: ScatterSopParams) { const originalMesh = inputMesh; let inputGeometry = inputMesh.geometry; if (inputGeometry.index) { inputGeometry = inputGeometry.toNonIndexed(); const newMesh = new Mesh(inputGeometry); inputMesh = newMesh; } const transferAttributes = params.transferAttributes; let attribNames: string[] = []; if (isBooleanTrue(transferAttributes)) { attribNames = coreGroup.pointAttribNamesMatchingMask(params.attributesToTransfer); } const sampler = new MeshSurfaceSampler(inputMesh, attribNames); if (isBooleanTrue(params.useWeightAttribute)) { const weightAttributeName = params.weightAttribute.trim(); if (weightAttributeName != '') { sampler.setWeightAttribute(weightAttributeName); } } const baseSeed = (2454 * params.seed) % Number.MAX_SAFE_INTEGER; sampler.setRandomGenerator((index: number) => { return CoreMath.randFloat(baseSeed, index); }); sampler.build(); const pointsCount = params.pointsCount; const positions: number[] = new Array(pointsCount * 3); const normals: number[] = new Array(pointsCount * 3); // additional attributes const additionalVectors: Vector3[] = new Array(attribNames.length); const additionalAttribSizes: number[] = new Array(attribNames.length); const additionalAttribSizeByName: Map<string, number> = new Map(); const additionalAttribBuffers: number[][] = new Array(attribNames.length); const additionalAttribBuffersByName: Map<string, number[]> = new Map(); for (let i = 0; i < attribNames.length; i++) { const attribName = attribNames[i]; additionalVectors[i] = new Vector3(); const attribSize = inputMesh.geometry.getAttribute(attribNames[i]).itemSize; additionalAttribSizeByName.set(attribName, attribSize); additionalAttribSizes[i] = attribSize; additionalAttribBuffers[i] = new Array(pointsCount * attribSize); additionalAttribBuffersByName.set(attribName, additionalAttribBuffers[i]); } const processAdditionalAttributes = attribNames.length > 0; const _position = this._position; const _normal = this._normal; let i3 = 0; for (let i = 0; i < pointsCount; i++) { sampler.sample(i, _position, _normal, additionalVectors); i3 = i * 3; _position.toArray(positions, i3); _normal.toArray(normals, i3); // additional attributes if (processAdditionalAttributes) { let j = 0; for (let additionalVector of additionalVectors) { const array = additionalAttribBuffers[j]; const attribSize = additionalAttribSizes[j]; const arrayIndex = i * attribSize; if (i < pointsCount - 1) { additionalVector.toArray(array, arrayIndex); } else { // when copying to the last point, we need to make sure not to use the vector3 // if the attribsize is not 3, otherwise we end up increasing the size of the buffer if (attribSize == 3) { additionalVector.toArray(array, arrayIndex); } else { switch (attribSize) { case 1: { array[arrayIndex] = additionalVector.x; break; } case 2: { tmpV2.set(additionalVector.x, additionalVector.y); tmpV2.toArray(array, arrayIndex); break; } case 4: { tmpV4.set(additionalVector.x, additionalVector.y, additionalVector.z, 0); tmpV4.toArray(array, arrayIndex); break; } } } } j++; } } } const geometry = new BufferGeometry(); geometry.setAttribute(Attribute.POSITION, new BufferAttribute(new Float32Array(positions), 3)); geometry.setAttribute(Attribute.NORMAL, new BufferAttribute(new Float32Array(normals), 3)); for (let attribName of attribNames) { const attribSize = additionalAttribSizeByName.get(attribName); const buffer = additionalAttribBuffersByName.get(attribName); if (attribSize != null && buffer != null) { geometry.setAttribute(attribName, new BufferAttribute(new Float32Array(buffer), attribSize)); } } // add id if (isBooleanTrue(params.addIdAttribute) || isBooleanTrue(params.addIdnAttribute)) { const pointsCount = params.pointsCount; const ids: number[] = rangeWithEnd(pointsCount); if (isBooleanTrue(params.addIdAttribute)) { geometry.setAttribute('id', new BufferAttribute(new Float32Array(ids), 1)); } const idns = ids.map((id) => id / (pointsCount - 1)); if (isBooleanTrue(params.addIdnAttribute)) { geometry.setAttribute('idn', new BufferAttribute(new Float32Array(idns), 1)); } } const object = this.createObject(geometry, ObjectType.POINTS); object.position.copy(originalMesh.position); object.rotation.copy(originalMesh.rotation); object.scale.copy(originalMesh.scale); object.matrix.copy(originalMesh.matrix); return this.createCoreGroupFromObjects([object]); } }