@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
240 lines (239 loc) • 8.29 kB
JavaScript
;
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);
}
}