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