@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
139 lines (124 loc) • 4.85 kB
text/typescript
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;
}