UNPKG

@polygonjs/polygonjs

Version:

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

265 lines (264 loc) 10.4 kB
"use strict"; import { BufferAttribute, Vector2, Vector3, Vector4 } from "three"; import { addToSetAtEntry } from "../../MapUtils"; import { triangleGraphFromGeometry } from "../modules/three/graph/triangle/TriangleGraphUtils"; import { setToArray } from "../../SetUtils"; const tmpV2 = new Vector2(); const tmpV3 = new Vector3(); const tmpV4 = new Vector4(); class FusePosition { constructor(positionAttribute, index, tolerance) { this.positionAttribute = positionAttribute; this.index = index; this.originalPosition = new Vector3(); this.snappedPosition = new Vector3(); this.originalPosition.fromBufferAttribute(positionAttribute, this.index); roundedPos(positionAttribute, this.index, this.snappedPosition, tolerance); this.snappedKey = `${this.snappedPosition.x}:${this.snappedPosition.y}:${this.snappedPosition.z}`; } addAttribValue(geometry, attribName, targetArray) { const attribute = geometry.getAttribute(attribName); switch (attribute.itemSize) { case 1: { const val = attribute.getX(this.index); targetArray.push(val); break; } case 2: { tmpV2.fromBufferAttribute(attribute, this.index); tmpV2.toArray(targetArray, targetArray.length); break; } case 3: { tmpV3.fromBufferAttribute(attribute, this.index); tmpV3.toArray(targetArray, targetArray.length); break; } case 4: { tmpV4.fromBufferAttribute(attribute, this.index); tmpV4.toArray(targetArray, targetArray.length); break; } } } } class FuseFace { constructor(a, b, c) { this.a = a; this.b = b; this.c = c; } } function averagePosition(positions, target) { target.set(0, 0, 0); positions.forEach((position) => { target.add(position.originalPosition); }); target.divideScalar(positions.size); } function roundedPos(position, index, target, tolerance) { target.fromBufferAttribute(position, index); if (tolerance > 0) { target.x = Math.round(target.x / tolerance) * tolerance; target.y = Math.round(target.y / tolerance) * tolerance; target.z = Math.round(target.z / tolerance) * tolerance; } } function isFaceCollapsed(face) { return face.a.snappedKey == face.b.snappedKey || face.a.snappedKey == face.c.snappedKey || face.b.snappedKey == face.c.snappedKey; } const tmpAttribute = new BufferAttribute(new Float32Array(0), 0); const _positions = [ new FusePosition(tmpAttribute, 0, 0.1), new FusePosition(tmpAttribute, 0, 0.1), new FusePosition(tmpAttribute, 0, 0.1) ]; export function mergeFacesWithUnsharedEdges(geometry, tolerance) { function getUnsharedPointAndFaceIndices(geometry2, pointIndices) { const graph = triangleGraphFromGeometry(geometry2); const edgeIds = []; graph.edgeIds(edgeIds); const unsharedPointIndicesSet = /* @__PURE__ */ new Set(); const unsharedFaceIndicesSet = /* @__PURE__ */ new Set(); for (const edgeId of edgeIds) { const edge = graph.edge(edgeId); if (edge.triangleIds.length == 1) { unsharedPointIndicesSet.add(edge.pointIdPair.id0); unsharedPointIndicesSet.add(edge.pointIdPair.id1); unsharedFaceIndicesSet.add(edge.triangleIds[0]); } } setToArray(unsharedPointIndicesSet, pointIndices); } const index = geometry.getIndex(); if (!index) { return; } const fusablePointIndices = []; getUnsharedPointAndFaceIndices(geometry, fusablePointIndices); const indexArray = index.array; const positionAttribute = geometry.getAttribute("position"); const positionsCount = positionAttribute.count; const facesCount = indexArray.length / 3; const fusedPositions = new Array(positionsCount); const pointsBySnappedPos = /* @__PURE__ */ new Map(); const firstPointBySnappedPos = /* @__PURE__ */ new Map(); const averagePosBySnappedKey = /* @__PURE__ */ new Map(); const newIndexBySnappedKey = /* @__PURE__ */ new Map(); const newPositions = []; const newIndices = []; const newAttributeValues = {}; const otherAttributeNames = Object.keys(geometry.attributes).filter((attribName) => attribName != "position"); const otherAttributeNamesCount = otherAttributeNames.length; for (let k = 0; k < otherAttributeNamesCount; k++) { const otherAttributeName = otherAttributeNames[k]; newAttributeValues[otherAttributeName] = []; } for (let i = 0; i < fusablePointIndices.length; i++) { const index2 = fusablePointIndices[i]; const position = new FusePosition(positionAttribute, index2, tolerance); fusedPositions[index2] = position; addToSetAtEntry(pointsBySnappedPos, position.snappedKey, position); if (!firstPointBySnappedPos.has(position.snappedKey)) { firstPointBySnappedPos.set(position.snappedKey, position); } } const snappedIndicesSet = /* @__PURE__ */ new Set(); pointsBySnappedPos.forEach((points, snappedKey) => { const averageV3 = new Vector3(); if (points.size > 1) { const pointIndices = []; let i = 0; points.forEach((point) => { pointIndices.push(point.index); snappedIndicesSet.add(point.index); i++; }); pointIndices.sort((a, b) => a - b); averagePosition(points, averageV3); averagePosBySnappedKey.set(snappedKey, averageV3); } }); const newIndexByOldIndex = []; const indicesToReplace = /* @__PURE__ */ new Set(); const indicesInUse = /* @__PURE__ */ new Set(); for (let fi = 0; fi < facesCount; fi++) { for (let vi = 0; vi < 3; vi++) { const currentIndexIndex = fi * 3 + vi; const index2 = indexArray[currentIndexIndex]; if (snappedIndicesSet.has(index2)) { const fusedPosition = fusedPositions[index2]; let newIndex = newIndexBySnappedKey.get(fusedPosition.snappedKey); const averagePos = averagePosBySnappedKey.get(fusedPosition.snappedKey); if (newIndex == null) { newIndex = newPositions.length / 3; newIndexBySnappedKey.set(fusedPosition.snappedKey, newIndex); } newIndices.push(newIndex); averagePos.toArray(newPositions, newIndex * 3); if (newIndex != index2 && indicesInUse.has(index2) == false) { indicesToReplace.add(index2); } indicesInUse.add(newIndex); } else { let newIndex = newIndexByOldIndex[index2]; if (newIndex == null) { newIndex = newPositions.length / 3; newIndexByOldIndex[index2] = newIndex; } indicesInUse.add(newIndex); if (newIndex != index2 && indicesInUse.has(index2) == false) { indicesToReplace.add(index2); } tmpV3.fromBufferAttribute(positionAttribute, index2); newIndices.push(newIndex); tmpV3.toArray(newPositions, newIndex * 3); } } } geometry.setAttribute("position", new BufferAttribute(new Float32Array(newPositions), 3)); geometry.setIndex(newIndices); } export function mergeFaces(geometry, tolerance) { const index = geometry.getIndex(); if (!index) { return; } const indexArray = index.array; const positionAttribute = geometry.getAttribute("position"); const positionsCount = positionAttribute.count; const facesCount = indexArray.length / 3; const positions = new Array(positionsCount); const faces = new Array(facesCount); const pointsBySnappedPos = /* @__PURE__ */ new Map(); const firstPointBySnappedPos = /* @__PURE__ */ new Map(); const averagePosBySnappedKey = /* @__PURE__ */ new Map(); const newIndexBySnappedKey = /* @__PURE__ */ new Map(); const newPositions = []; const newIndices = []; const newAttributeValues = {}; const otherAttributeNames = Object.keys(geometry.attributes).filter((attribName) => attribName != "position"); const otherAttributeNamesCount = otherAttributeNames.length; for (let k = 0; k < otherAttributeNamesCount; k++) { const otherAttributeName = otherAttributeNames[k]; newAttributeValues[otherAttributeName] = []; } for (let i = 0; i < positionsCount; i++) { const position = new FusePosition(positionAttribute, i, tolerance); positions[i] = position; addToSetAtEntry(pointsBySnappedPos, position.snappedKey, position); if (!firstPointBySnappedPos.has(position.snappedKey)) { firstPointBySnappedPos.set(position.snappedKey, position); } } pointsBySnappedPos.forEach((points, snappedKey) => { const averageV3 = new Vector3(); averagePosition(points, averageV3); averagePosBySnappedKey.set(snappedKey, averageV3); }); for (let i = 0; i < facesCount; i++) { const a = positions[indexArray[i * 3]]; const b = positions[indexArray[i * 3 + 1]]; const c = positions[indexArray[i * 3 + 2]]; const face = new FuseFace(a, b, c); faces[i] = face; } const fusedFacesToKeep = []; for (let i = 0; i < facesCount; i++) { const face = faces[i]; if (!isFaceCollapsed(face)) { fusedFacesToKeep.push(face); } } const fusedFacesToKeepCount = fusedFacesToKeep.length; for (let i = 0; i < fusedFacesToKeepCount; i++) { const face = fusedFacesToKeep[i]; _positions[0] = face.a; _positions[1] = face.b; _positions[2] = face.c; for (let j = 0; j < 3; j++) { const position = _positions[j]; let newIndex = newIndexBySnappedKey.get(position.snappedKey); const averagePos = averagePosBySnappedKey.get(position.snappedKey); if (newIndex == null) { newIndex = newPositions.length / 3; newIndexBySnappedKey.set(position.snappedKey, newIndex); averagePos.toArray(newPositions, newPositions.length); const firstPoint = firstPointBySnappedPos.get(position.snappedKey); for (let k = 0; k < otherAttributeNamesCount; k++) { const otherAttribName = otherAttributeNames[k]; firstPoint.addAttribValue(geometry, otherAttribName, newAttributeValues[otherAttribName]); } } newIndices.push(newIndex); } } geometry.setAttribute("position", new BufferAttribute(new Float32Array(newPositions), 3)); for (let k = 0; k < otherAttributeNamesCount; k++) { const attribName = otherAttributeNames[k]; const attribute = geometry.getAttribute(attribName); const newValues = newAttributeValues[attribName]; geometry.setAttribute(attribName, new BufferAttribute(new Float32Array(newValues), attribute.itemSize)); } geometry.setIndex(newIndices); }