UNPKG

@polygonjs/polygonjs

Version:

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

139 lines (124 loc) 4.85 kB
import {stringToAttribNames} from './../../../core/String'; import {BaseSopOperation} from './_Base'; import {CoreGroup} from '../../../core/geometry/Group'; import {InputCloneMode} from '../../poly/InputCloneMode'; import {Mesh, Line3, BufferGeometry, Float32BufferAttribute} from 'three'; import {DefaultOperationParams} from '../../../core/operations/_Base'; import { SUBTRACTION, ADDITION, DIFFERENCE, INTERSECTION, Brush, Evaluator, } from '../../../core/thirdParty/three-bvh-csg'; import {ObjectType} from '../../../core/geometry/Constant'; import {CoreGeometryBuilderMerge} from '../../../core/geometry/modules/three/builders/Merge'; import {arrayPushItems} from '../../../core/ArrayUtils'; export enum BooleanOperation { INTERSECT = 'intersect', SUBTRACT = 'subtract', ADD = 'add', DIFFERENCE = 'difference', } export const BOOLEAN_OPERATIONS: BooleanOperation[] = [ BooleanOperation.INTERSECT, BooleanOperation.SUBTRACT, BooleanOperation.ADD, BooleanOperation.DIFFERENCE, ]; const evaluationIdByBooleanOperation: Record<BooleanOperation, number> = { [BooleanOperation.INTERSECT]: INTERSECTION, [BooleanOperation.SUBTRACT]: SUBTRACTION, [BooleanOperation.ADD]: ADDITION, [BooleanOperation.DIFFERENCE]: DIFFERENCE, }; interface BooleanSopParams extends DefaultOperationParams { operation: number; // keepVertexColor: boolean; additionalAttributes: string; // keepMaterials: boolean; useInputGroups: boolean; intersectionEdgesOnly: boolean; } const _attribNames: string[] = []; export class BooleanSopOperation extends BaseSopOperation { static override readonly DEFAULT_PARAMS: BooleanSopParams = { operation: BOOLEAN_OPERATIONS.indexOf(BooleanOperation.INTERSECT), keepVertexColor: false, additionalAttributes: '', keepMaterials: true, useInputGroups: false, intersectionEdgesOnly: false, }; static override readonly INPUT_CLONED_STATE = [InputCloneMode.FROM_NODE, InputCloneMode.NEVER]; static override type(): Readonly<'boolean'> { return 'boolean'; } override cook(inputCoreGroups: CoreGroup[], params: BooleanSopParams): CoreGroup { const meshA = inputCoreGroups[0].threejsObjectsWithGeo()[0] as Mesh | undefined; const meshB = inputCoreGroups[1].threejsObjectsWithGeo()[0] as Mesh | undefined; if (!(meshA && meshA.geometry && meshB && meshB.geometry)) { this.states?.error.set('input objects need to have mesh geometries at the top level'); return this.createCoreGroupFromObjects([]); } const csgEvaluator = new Evaluator(); const brush1 = new Brush(meshA.geometry, params.keepMaterials ? meshA.material : undefined); const brush2 = new Brush(meshB.geometry, params.keepMaterials ? meshB.material : undefined); const operation = BOOLEAN_OPERATIONS[params.operation]; const operationId = evaluationIdByBooleanOperation[operation]; const attributes = ['position', 'normal']; if (params.keepVertexColor) { attributes.push('color'); } if (params.additionalAttributes.trim() != '') { stringToAttribNames(params.additionalAttributes, _attribNames); arrayPushItems(_attribNames, attributes); } csgEvaluator.attributes = attributes; csgEvaluator.useGroups = params.keepMaterials || params.useInputGroups; // we currently need to use the returned object from .evaluate in order to // have a new object with a correct bounding box (csgEvaluator.debug as any).enabled = params.intersectionEdgesOnly; const output = csgEvaluator.evaluate(brush1, brush2, operationId); if (params.intersectionEdgesOnly) { const lines = csgEvaluator.debug.intersectionEdges as any as Line3[]; if (lines.length > 0) { const linesGeometry = _createLinesObject(lines); if (linesGeometry) { const object = BaseSopOperation.createObject(linesGeometry, ObjectType.LINE_SEGMENTS); return this.createCoreGroupFromObjects([object]); } } } else { output.disposeCacheData(); BaseSopOperation.createIndexIfNone(output.geometry); // brush1.geometry.computeBoundingBox(); if (!params.keepMaterials) { output.material = meshA.material; } return this.createCoreGroupFromObjects([output]); } return this.createCoreGroupFromObjects([]); } } function _createLinesObject(lines: Line3[]): BufferGeometry | undefined { const geometries = lines.map(_createLineGeometry); return CoreGeometryBuilderMerge.merge(geometries); } function _createLineGeometry(line: Line3): BufferGeometry { const pointsCount = 2; const positions: number[] = new Array(pointsCount * 3); const indices: number[] = new Array(pointsCount); const i = 0; line.start.toArray(positions, i * 3); line.end.toArray(positions, (i + 1) * 3); indices[0] = 0; indices[1] = 1; const geometry = new BufferGeometry(); geometry.setAttribute('position', new Float32BufferAttribute(positions, 3)); geometry.setIndex(indices); return geometry; }