UNPKG

@polygonjs/polygonjs

Version:

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

311 lines (281 loc) 11.5 kB
import {BufferGeometry, 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 { public readonly originalPosition: Vector3 = new Vector3(); public readonly snappedPosition: Vector3 = new Vector3(); public readonly snappedKey: string; constructor(public readonly positionAttribute: BufferAttribute, public readonly index: number, tolerance: number) { 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: BufferGeometry, attribName: string, targetArray: number[]) { const attribute = geometry.getAttribute(attribName) as BufferAttribute; 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(public a: FusePosition, public b: FusePosition, public c: FusePosition) {} } function averagePosition(positions: Set<FusePosition>, target: Vector3) { target.set(0, 0, 0); positions.forEach((position) => { target.add(position.originalPosition); }); target.divideScalar(positions.size); } function roundedPos(position: BufferAttribute, index: number, target: Vector3, tolerance: number) { 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: FuseFace): boolean { return ( face.a.snappedKey == face.b.snappedKey || face.a.snappedKey == face.c.snappedKey || face.b.snappedKey == face.c.snappedKey ); } const tmpAttribute: BufferAttribute = new BufferAttribute(new Float32Array(0), 0); const _positions: [FusePosition, FusePosition, FusePosition] = [ new FusePosition(tmpAttribute, 0, 0.1), new FusePosition(tmpAttribute, 0, 0.1), new FusePosition(tmpAttribute, 0, 0.1), ]; export function mergeFacesWithUnsharedEdges(geometry: BufferGeometry, tolerance: number) { function getUnsharedPointAndFaceIndices( geometry: BufferGeometry, pointIndices: number[] // , faceIndices: number[] ) { const graph = triangleGraphFromGeometry(geometry)!; const edgeIds: string[] = []; graph.edgeIds(edgeIds); const unsharedPointIndicesSet: Set<number> = new Set(); const unsharedFaceIndicesSet: Set<number> = new Set(); for (const edgeId of edgeIds) { const edge = graph.edge(edgeId)!; // if (!edge) continue; if (edge.triangleIds.length == 1) { unsharedPointIndicesSet.add(edge.pointIdPair.id0); unsharedPointIndicesSet.add(edge.pointIdPair.id1); unsharedFaceIndicesSet.add(edge.triangleIds[0]); } } setToArray(unsharedPointIndicesSet, pointIndices); // setToArray(unsharedFaceIndicesSet, faceIndices); } const index = geometry.getIndex(); if (!index) { return; } const fusablePointIndices: number[] = []; // const fusableFaceIndices: number[] = []; getUnsharedPointAndFaceIndices(geometry, fusablePointIndices); const indexArray = index.array; const positionAttribute = geometry.getAttribute('position') as BufferAttribute; const positionsCount = positionAttribute.count; const facesCount = indexArray.length / 3; const fusedPositions: FusePosition[] = new Array(positionsCount); // const faces: FuseFace[] = new Array(facesCount); const pointsBySnappedPos: Map<string, Set<FusePosition>> = new Map(); const firstPointBySnappedPos: Map<string, FusePosition> = new Map(); const averagePosBySnappedKey: Map<string, Vector3> = new Map(); const newIndexBySnappedKey: Map<string, number> = new Map(); const newPositions: number[] = []; const newIndices: number[] = []; const newAttributeValues: Record<string, number[]> = {}; 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 index = fusablePointIndices[i]; const position = new FusePosition(positionAttribute, index, tolerance); fusedPositions[index] = position; addToSetAtEntry(pointsBySnappedPos, position.snappedKey, position); if (!firstPointBySnappedPos.has(position.snappedKey)) { firstPointBySnappedPos.set(position.snappedKey, position); } } const snappedIndicesSet: Set<number> = new Set(); pointsBySnappedPos.forEach((points, snappedKey) => { const averageV3 = new Vector3(); if (points.size > 1) { const pointIndices: number[] = []; 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: number[] = []; const indicesToReplace: Set<number> = new Set(); const indicesInUse: Set<number> = new Set(); for (let fi = 0; fi < facesCount; fi++) { for (let vi = 0; vi < 3; vi++) { const currentIndexIndex = fi * 3 + vi; const index = indexArray[currentIndexIndex]; if (snappedIndicesSet.has(index)) { const fusedPosition = fusedPositions[index]; let newIndex = newIndexBySnappedKey.get(fusedPosition.snappedKey); const averagePos = averagePosBySnappedKey.get(fusedPosition.snappedKey)!; if (newIndex == null) { newIndex = newPositions.length / 3; newIndexBySnappedKey.set(fusedPosition.snappedKey, newIndex); // const firstPoint = firstPointBySnappedPos.get(fusedPosition.snappedKey)!; // for (let k = 0; k < otherAttributeNamesCount; k++) { // const otherAttribName = otherAttributeNames[k]; // firstPoint.addAttribValue(geometry, otherAttribName, newAttributeValues[otherAttribName]); // } } newIndices.push(newIndex); averagePos.toArray(newPositions, newIndex * 3); if (newIndex != index && indicesInUse.has(index) == false) { indicesToReplace.add(index); } indicesInUse.add(newIndex); } else { let newIndex = newIndexByOldIndex[index]; if (newIndex == null) { newIndex = newPositions.length / 3; //index - removedPointsCount; newIndexByOldIndex[index] = newIndex; } indicesInUse.add(newIndex); if (newIndex != index && indicesInUse.has(index) == false) { indicesToReplace.add(index); } tmpV3.fromBufferAttribute(positionAttribute, index); newIndices.push(newIndex); tmpV3.toArray(newPositions, newIndex * 3); } } } 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) as BufferAttribute; // const newValues = newAttributeValues[attribName]; // geometry.setAttribute(attribName, new BufferAttribute(new Float32Array(newValues), attribute.itemSize)); // } geometry.setIndex(newIndices); } export function mergeFaces(geometry: BufferGeometry, tolerance: number) { const index = geometry.getIndex(); if (!index) { return; } const indexArray = index.array; const positionAttribute = geometry.getAttribute('position') as BufferAttribute; const positionsCount = positionAttribute.count; const facesCount = indexArray.length / 3; const positions: FusePosition[] = new Array(positionsCount); const faces: FuseFace[] = new Array(facesCount); const pointsBySnappedPos: Map<string, Set<FusePosition>> = new Map(); const firstPointBySnappedPos: Map<string, FusePosition> = new Map(); const averagePosBySnappedKey: Map<string, Vector3> = new Map(); const newIndexBySnappedKey: Map<string, number> = new Map(); const newPositions: number[] = []; const newIndices: number[] = []; const newAttributeValues: Record<string, number[]> = {}; 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: FuseFace[] = []; 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) as BufferAttribute; const newValues = newAttributeValues[attribName]; geometry.setAttribute(attribName, new BufferAttribute(new Float32Array(newValues), attribute.itemSize)); } geometry.setIndex(newIndices); }