UNPKG

@polygonjs/polygonjs

Version:

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

153 lines (141 loc) 5.19 kB
import {BaseSopOperation} from './_Base'; import {CoreGroup} from '../../../core/geometry/Group'; import {InputCloneMode} from '../../../engine/poly/InputCloneMode'; import {DefaultOperationParams} from '../../../core/operations/_Base'; import {CoreMask} from '../../../core/geometry/Mask'; import {SopType} from '../../poly/registers/nodes/types/Sop'; import {Mesh} from 'three'; import {triangleGraphFromGeometry} from '../../../core/geometry/modules/three/graph/triangle/TriangleGraphUtils'; import {ThreejsPrimitiveTriangle} from '../../../core/geometry/modules/three/ThreejsPrimitiveTriangle'; import {TriangleGraph} from '../../../core/geometry/modules/three/graph/triangle/TriangleGraph'; import {threeMeshFromPrimitives} from '../../../core/geometry/modules/three/builders/Mesh'; import {setToArray} from '../../../core/SetUtils'; import {arrayDifference} from '../../../core/ArrayUtils'; interface DeleteNonManifoldSopParams extends DefaultOperationParams { group: string; invert: boolean; } export class DeleteNonManifoldSopOperation extends BaseSopOperation { static override readonly DEFAULT_PARAMS: DeleteNonManifoldSopParams = { group: '', invert: false, }; static override readonly INPUT_CLONED_STATE = InputCloneMode.FROM_NODE; static override type(): Readonly<SopType.DELETE_NON_MANIFOLD> { return SopType.DELETE_NON_MANIFOLD; } override cook(inputCoreGroups: CoreGroup[], params: DeleteNonManifoldSopParams) { const coreGroup = inputCoreGroups[0]; const selectedObjects = CoreMask.filterObjects(coreGroup, params); for (const inputObject of selectedObjects) { const mesh = inputObject as Mesh; if (mesh.geometry != null && mesh.geometry.index != null) { this._deleteNonManifold(mesh, params.invert); } } coreGroup.resetBoundingBox(); return coreGroup; } private _deleteNonManifold(mesh: Mesh, invert: boolean) { const graph = triangleGraphFromGeometry(mesh.geometry); if (!graph) { return; } const edgeIds: string[] = []; const triangleIds: number[] = []; graph.edgeIds(edgeIds); graph.triangleIds(triangleIds); const sortedTriangleIds = sortTriangleIdsByScore(graph, triangleIds); const selectedTriangleIds: Set<number> = new Set(); let status = manifoldStatus(graph, triangleIds, selectedTriangleIds); if (status.nonManifoldEdges > 0) { for (const triangleId of sortedTriangleIds) { selectedTriangleIds.add(triangleId); status = manifoldStatus(graph, triangleIds, selectedTriangleIds); if (status.nonManifoldEdges == 0) { break; } } } if (invert) { const triangleIdsToRemove: number[] = []; setToArray(selectedTriangleIds, triangleIdsToRemove); this._cleanMesh(mesh, triangleIdsToRemove); } else { const selectedTriangleIdsArray: number[] = []; setToArray(selectedTriangleIds, selectedTriangleIdsArray); const triangleIdsToRemove: number[] = []; arrayDifference(triangleIds, selectedTriangleIdsArray, triangleIdsToRemove); this._cleanMesh(mesh, triangleIdsToRemove); } } private _cleanMesh(mesh: Mesh, triangleIdsToKeep: number[]) { const triangles = triangleIdsToKeep.map((triangleId) => new ThreejsPrimitiveTriangle(mesh, triangleId)); if (triangles.length > 0) { const newObject = threeMeshFromPrimitives(mesh, triangles) as Mesh; if (newObject) { mesh.geometry = newObject.geometry; } } else { const newObject = threeMeshFromPrimitives(mesh, []) as Mesh; if (newObject) { mesh.geometry = newObject.geometry; } } } } function sortTriangleIdsByScore(graph: TriangleGraph, triangleIds: number[]): number[] { const scoreByTriangleId: number[] = []; for (const triangleId of triangleIds) { scoreByTriangleId.push(triangleScore(graph, triangleId)); } const sortedTriangleIds = triangleIds.slice(); sortedTriangleIds.sort((a, b) => scoreByTriangleId[b] - scoreByTriangleId[a]); return sortedTriangleIds; } function triangleScore(graph: TriangleGraph, triangleId: number) { const edges = graph.edgesByTriangleId(triangleId); let potentialNonManifoldEdges = 0; let totalEdges = 0; if (edges) { for (const edge of edges) { if (edge.triangleIds.length != 2) { potentialNonManifoldEdges++; } totalEdges += edge.triangleIds.length; } } totalEdges /= 3; return potentialNonManifoldEdges * 10 + (totalEdges - 2); } interface ManifoldStatus { nonManifoldEdges: number; activeEdges: Map<string, number>; } function manifoldStatus(graph: TriangleGraph, triangleIds: number[], triangleIdsToRemove: Set<number>): ManifoldStatus { let nonManifoldEdges = 0; const activeEdges: Map<string, number> = new Map(); // Recalculate edge counts excluding removed faces triangleIds.forEach((triangleId) => { if (triangleIdsToRemove.has(triangleId)) return; const edges = graph.edgesByTriangleId(triangleId); if (edges) { edges.forEach((edge) => { if (!activeEdges.has(edge.id)) { activeEdges.set(edge.id, 0); } activeEdges.set(edge.id, activeEdges.get(edge.id)! + 1); }); } }); // Count non-manifold edges activeEdges.forEach((count, edge) => { if (count !== 2) { nonManifoldEdges++; } }); return { nonManifoldEdges, activeEdges, }; }