UNPKG

@polygonjs/polygonjs

Version:

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

240 lines (239 loc) 8.29 kB
"use strict"; import { TypedSopNode } from "./_Base"; import { BufferAttribute, Vector2, Vector3, Vector4 } from "three"; import { NodeParamsConfig, ParamConfig } from "../utils/params/ParamsConfig"; import { InputCloneMode } from "../../poly/InputCloneMode"; import { pushOnArrayAtEntry } from "../../../core/MapUtils"; import { ObjectType, objectTypeFromObject } from "../../../core/geometry/Constant"; import { arrayUniq } from "../../../core/ArrayUtils"; import { mergeFaces, mergeFacesWithUnsharedEdges } from "../../../core/geometry/operation/Fuse"; import { CoreMask } from "../../../core/geometry/Mask"; import { SopType } from "../../poly/registers/nodes/types/Sop"; import { Attribute } from "../../../core/geometry/Attribute"; const roundedPosition = new Vector3(); const vector2 = new Vector2(); const vector3 = new Vector3(); const vector4 = new Vector4(); function clearAttributes(geometry) { const attributeNames = Object.keys(geometry.attributes); for (const attributeName of attributeNames) { const attribute = geometry.getAttribute(attributeName); if (attribute instanceof BufferAttribute) { const newAttribValues = []; geometry.setAttribute( attributeName, new BufferAttribute(new Float32Array(newAttribValues), attribute.itemSize) ); } } } class FuseSopParamsConfig extends NodeParamsConfig { constructor() { super(...arguments); /** @param group to assign the material to */ this.group = ParamConfig.STRING("", { objectMask: true }); /** @param distance threshold */ this.dist = ParamConfig.FLOAT(1e-3, { range: [0, 1], rangeLocked: [true, false], step: 1e-3 }); /** @param fuse only open edges */ this.onlyOpenEdges = ParamConfig.BOOLEAN(false); /** @param recompute normals */ this.computeNormals = ParamConfig.BOOLEAN(1); } } const ParamsConfig = new FuseSopParamsConfig(); export class FuseSopNode extends TypedSopNode { constructor() { super(...arguments); this.paramsConfig = ParamsConfig; } static type() { return SopType.FUSE; } initializeNode() { this.io.inputs.setCount(1); this.io.inputs.initInputsClonedState(InputCloneMode.FROM_NODE); } cook(inputCoreGroups) { const inputCoreGroup = inputCoreGroups[0]; const selectedObjects = CoreMask.filterThreejsObjects(inputCoreGroup, this.pv); for (const object of selectedObjects) { this._filterObject(object); } this.setCoreGroup(inputCoreGroup); } _filterObject(object) { const objectType = objectTypeFromObject(object); switch (objectType) { case ObjectType.MESH: { return this._filterMesh(object); } case ObjectType.LINE_SEGMENTS: { this._fuseGeometry(object.geometry); return this._filterLineSegments(object); } case ObjectType.POINTS: { this._fuseGeometry(object.geometry); return this._filterPoints(object); } } } _filterMesh(object) { const geometry = object.geometry; const index = geometry.getIndex(); if (!index) { return; } if (this.pv.onlyOpenEdges == true) { mergeFacesWithUnsharedEdges(geometry, this.pv.dist); } else { mergeFaces(geometry, this.pv.dist); } } _filterLineSegments(object) { const geometry = object.geometry; const index = geometry.getIndex(); if (!index) { return; } const newIndices = []; const indexArray = index.array; const segmentsCount = indexArray.length / 2; for (let i = 0; i < segmentsCount; i++) { vector2.fromArray(indexArray, i * 2); const a = vector2.x; const b = vector2.y; const segmentSnapped = a == b; if (!segmentSnapped) { vector2.toArray(newIndices, newIndices.length); } } geometry.setIndex(newIndices); if (newIndices.length == 0) { clearAttributes(geometry); } } _filterPoints(object) { const geometry = object.geometry; const index = geometry.getIndex(); if (!index) { return; } const indexArray = [...index.array]; const newIndices = []; arrayUniq(indexArray, newIndices); newIndices.sort((a, b) => a - b); geometry.setIndex(newIndices); if (newIndices.length == 0) { clearAttributes(geometry); } } _fuseGeometry(geometry) { const index = geometry.getIndex(); if (!index) { return; } const indexArray = index.array; const precision = this.pv.dist; const position = geometry.getAttribute(Attribute.POSITION); const srcPointsCount = position.array.length / 3; function roundedPos(index2, target) { target.fromBufferAttribute(position, index2); if (precision > 0) { target.x = Math.round(target.x / precision) * precision; target.y = Math.round(target.y / precision) * precision; target.z = Math.round(target.z / precision) * precision; } } const indicesByPosKey = /* @__PURE__ */ new Map(); const posKeyByIndex = /* @__PURE__ */ new Map(); for (let index2 = 0; index2 < srcPointsCount; index2++) { roundedPos(index2, roundedPosition); const posKey = `${roundedPosition.x},${roundedPosition.y},${roundedPosition.z}`; pushOnArrayAtEntry(indicesByPosKey, posKey, index2); posKeyByIndex.set(index2, posKey); } indicesByPosKey.forEach((indices, posKey) => { indices.sort((a, b) => a - b); }); const newIndicesAfterGapsCreated = /* @__PURE__ */ new Map(); let nextAvailableIndex = 0; for (let index2 = 0; index2 < srcPointsCount; index2++) { const posKey = posKeyByIndex.get(index2); const indices = indicesByPosKey.get(posKey); if (indices.length <= 1 || indices[0] == index2) { newIndicesAfterGapsCreated.set(index2, nextAvailableIndex); nextAvailableIndex++; } } const newIndexByOldIndex = /* @__PURE__ */ new Map(); indicesByPosKey.forEach((indices, posKey) => { const firstIndex = indices[0]; for (let i = 1; i < indices.length; i++) { const index2 = indices[i]; newIndexByOldIndex.set(index2, firstIndex); } }); const newIndices = []; const newIndexByOldIndexAfterAssignment = /* @__PURE__ */ new Map(); for (let i = 0; i < indexArray.length; i++) { const index2 = indexArray[i]; const targetIndex = newIndexByOldIndex.get(index2); const targetOffset = targetIndex != null ? newIndicesAfterGapsCreated.get(targetIndex) : newIndicesAfterGapsCreated.get(index2); let newIndex = index2; if (targetOffset != null) { newIndex = targetOffset; } else { if (targetIndex != null) { newIndex = targetIndex; } } newIndices.push(newIndex); newIndexByOldIndexAfterAssignment.set(index2, newIndex); } const attributeNames = Object.keys(geometry.attributes); for (const attributeName of attributeNames) { const attribute = geometry.getAttribute(attributeName); if (attribute instanceof BufferAttribute) { let getVector2 = function() { if (itemSize == 2) { return vector2; } if (itemSize == 3) { return vector3; } if (itemSize == 4) { return vector4; } }; var getVector = getVector2; const itemSize = attribute.itemSize; const newAttribValues = []; const vector = getVector2(); const visitedIndex = /* @__PURE__ */ new Set(); for (let i = 0; i < srcPointsCount; i++) { const index2 = newIndexByOldIndexAfterAssignment.get(i); if (index2 != null) { if (!visitedIndex.has(index2)) { visitedIndex.add(index2); if (vector) { vector.fromBufferAttribute(attribute, i); vector.toArray(newAttribValues, index2 * itemSize); } else { const currentVal = attribute.array[i]; newAttribValues[index2] = currentVal; } } } } geometry.setAttribute(attributeName, new BufferAttribute(new Float32Array(newAttribValues), itemSize)); } } geometry.setIndex(newIndices); } }