UNPKG

@polygonjs/polygonjs

Version:

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

180 lines (179 loc) 6.7 kB
"use strict"; import { BaseSopOperation } from "./_Base"; import { BufferAttribute, BufferGeometry, LineSegments, Line3, BoxGeometry, Vector3, Plane } from "three"; import { InputCloneMode } from "../../../engine/poly/InputCloneMode"; import { DEFAULT_MATERIALS, ObjectType } from "../../../core/geometry/Constant"; import { ThreeMeshBVHHelper } from "../../../core/geometry/bvh/ThreeMeshBVHHelper"; import { isBooleanTrue } from "../../../core/Type"; import { SUBTRACTION, Brush, Evaluator } from "../../../core/thirdParty/three-bvh-csg"; import { rotateGeometry } from "../../../core/Transform"; import { CoreGeometryBuilderMesh } from "../../../core/geometry/modules/three/builders/Mesh"; import { corePointClassFactory } from "../../../core/geometry/CoreObjectFactory"; import { pointsFromObject } from "../../../core/geometry/entities/point/CorePointUtils"; import { copyObject3DProperties } from "../../../core/geometry/modules/three/ThreejsObjectUtils"; const tempVector = new Vector3(); const tempLine = new Line3(); const _plane = new Plane(); const DEFAULT_UP = new Vector3(0, 1, 0); const TMP_KEEP_ATTRIBUTE_NAME = "___keep___"; const TMP_KEEP_ATTRIBUTE_SIZE = 1; const objectsToRemove = /* @__PURE__ */ new Set(); const BOOLEAN_SIZE = 1e4; const _points = []; export class ClipSopOperation extends BaseSopOperation { static type() { return "clip"; } cook(inputCoreGroups, params) { const coreGroup = inputCoreGroups[0]; const newObjects = []; _plane.set(params.direction, -params.distance); _plane.translate(params.origin); const inputObjects = coreGroup.threejsObjects(); objectsToRemove.clear(); for (const object of inputObjects) { object.traverse((child) => { _processObjectClipped(child, params, newObjects); }); } objectsToRemove.forEach((child) => { child.removeFromParent(); }); return this.createCoreGroupFromObjects(newObjects); } } ClipSopOperation.DEFAULT_PARAMS = { origin: new Vector3(0, 0, 0), distance: 0, direction: new Vector3(0, 1, 0), intersectionEdges: false, keepBelowPlane: true, keepAbovePlane: false }; ClipSopOperation.INPUT_CLONED_STATE = InputCloneMode.ALWAYS; function _processObjectClipped(object, params, newObjects) { const geometry = object.geometry; if (!geometry) { return; } const mesh = object; function _addObject(newObject) { newObjects.push(newObject); copyObject3DProperties(object, newObject); objectsToRemove.add(object); } if (isBooleanTrue(params.keepBelowPlane) || isBooleanTrue(params.keepAbovePlane)) { if (isBooleanTrue(params.keepBelowPlane)) { const box = _createBox(params, true); const belowPlane = _createClipped(mesh, box); _addObject(belowPlane); } if (isBooleanTrue(params.keepAbovePlane)) { const box = _createBox(params, false); const abovePlane = _createClipped(mesh, box); _addObject(abovePlane); } } if (isBooleanTrue(params.intersectionEdges)) { const intersectionEdges = _createClipGeo(mesh); if (intersectionEdges) { const oldMaterial = intersectionEdges.material; _addObject(intersectionEdges); intersectionEdges.material = oldMaterial; } } } function _createClipped(mesh, box) { const csgEvaluator = new Evaluator(); const corePointClass = corePointClassFactory(mesh); function _addKeepAttribute(object2, value) { corePointClass.addNumericAttribute(object2, TMP_KEEP_ATTRIBUTE_NAME, TMP_KEEP_ATTRIBUTE_SIZE, value); } _addKeepAttribute(mesh, 1); _addKeepAttribute(box, 0); const brush1 = new Brush(mesh.geometry, mesh.material); const brush2 = new Brush(box.geometry); const existingAttributes = corePointClass.attributeNames(mesh); csgEvaluator.attributes = [...existingAttributes, TMP_KEEP_ATTRIBUTE_NAME]; const output = csgEvaluator.evaluate(brush1, brush2, SUBTRACTION); output.disposeCacheData(); BaseSopOperation.createIndexIfNone(output.geometry); pointsFromObject(output, _points); const keptPoints = _points.filter((p) => p.attribValue(TMP_KEEP_ATTRIBUTE_NAME) == 1); const builder = new CoreGeometryBuilderMesh(); const newGeometry = builder.fromPoints(output, keptPoints); const object = BaseSopOperation.createObject(newGeometry, ObjectType.MESH); return object; } function _createBox(params, above) { const { origin, direction, distance } = params; const geometry = new BoxGeometry(BOOLEAN_SIZE, BOOLEAN_SIZE, BOOLEAN_SIZE, 2, 2, 2); geometry.translate(0, BOOLEAN_SIZE * 0.5 * (above ? 1 : -1), 0); geometry.translate(origin.x, origin.y + distance, origin.z); rotateGeometry(geometry, DEFAULT_UP, direction); const object = BaseSopOperation.createObject(geometry, ObjectType.MESH); return object; } function _createClipGeo(mesh) { const meshBVH = mesh; let bvh = meshBVH.geometry.boundsTree; if (!bvh) { ThreeMeshBVHHelper.assignDefaultBVHIfNone(mesh); bvh = meshBVH.geometry.boundsTree; } const performIntersection = (posAttrib) => { let index2 = 0; const intersectsBounds = (box, isLeaf, score, depth, nodeIndex) => { return _plane.intersectsBox(box); }; const intersectsTriangle = (tri) => { let count = 0; tempLine.start.copy(tri.a); tempLine.end.copy(tri.b); if (_plane.intersectLine(tempLine, tempVector)) { posAttrib == null ? void 0 : posAttrib.setXYZ(index2, tempVector.x, tempVector.y, tempVector.z); index2++; count++; } tempLine.start.copy(tri.b); tempLine.end.copy(tri.c); if (_plane.intersectLine(tempLine, tempVector)) { posAttrib == null ? void 0 : posAttrib.setXYZ(index2, tempVector.x, tempVector.y, tempVector.z); count++; index2++; } tempLine.start.copy(tri.c); tempLine.end.copy(tri.a); if (_plane.intersectLine(tempLine, tempVector)) { posAttrib == null ? void 0 : posAttrib.setXYZ(index2, tempVector.x, tempVector.y, tempVector.z); count++; index2++; } if (count !== 2) { index2 -= count; } }; bvh.shapecast({ intersectsBounds, intersectsTriangle }); return { index: index2 }; }; const { index } = performIntersection(); const lineGeometry = new BufferGeometry(); const linePosAttr = new BufferAttribute(new Float32Array(index * 3), 3, false); lineGeometry.setAttribute("position", linePosAttr); const outlineLines = new LineSegments(lineGeometry, DEFAULT_MATERIALS[ObjectType.LINE_SEGMENTS]); outlineLines.frustumCulled = false; const posAttr = outlineLines.geometry.attributes.position; performIntersection(posAttr); return outlineLines; }