UNPKG

@polygonjs/polygonjs

Version:

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

404 lines (374 loc) 14.1 kB
/** * Delete parts of the input geometry * * @remarks * This can be used in many ways to delete points or objects from the input. * */ import {TypedSopNode} from './_Base'; import { AttribClass, ObjectType, objectTypeFromObject, AttribType, AttribTypeMenuEntries, ATTRIBUTE_TYPES, AttribSize, ATTRIBUTE_CLASSES, ATTRIBUTE_SIZE_RANGE, ATTRIBUTE_CLASSES_WITHOUT_CORE_GROUP, AttribClassMenuEntriesWithoutCoreGroup, } from '../../../core/geometry/Constant'; import {CoreGroup} from '../../../core/geometry/Group'; import {InputCloneMode} from '../../poly/InputCloneMode'; import {BaseCorePoint, CorePoint} from '../../../core/geometry/entities/point/CorePoint'; import {ThreejsCoreObject} from '../../../core/geometry/modules/three/ThreejsCoreObject'; import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig'; import {EntitySelectionHelper} from './utils/delete/EntitySelectionHelper'; import { ByAttributeHelper, ComparisonOperatorMenuEntries, ComparisonOperator, COMPARISON_OPERATORS, } from './utils/delete/ByAttributeHelper'; import {ByExpressionHelper} from './utils/delete/ByExpressionHelper'; import {ByBboxHelper} from './utils/delete/ByBboxHelper'; import {ByObjectTypeHelper, OBJECT_TYPE_MENU_ENTRIES, OBJECT_TYPES} from './utils/delete/ByObjectTypeHelper'; import {isBooleanTrue} from '../../../core/BooleanValue'; import {ByBoundingObjectHelper} from './utils/delete/ByBoundingObjectHelper'; import {geometryBuilder} from '../../../core/geometry/modules/three/builders/geometryBuilder'; import {SopType} from '../../poly/registers/nodes/types/Sop'; import {TypeAssert} from '../../poly/Assert'; import {primitivesFromObject} from '../../../core/geometry/entities/primitive/CorePrimitiveUtils'; import {CoreObjectType, ObjectContent} from '../../../core/geometry/ObjectContent'; import {CorePrimitive} from '../../../core/geometry/entities/primitive/CorePrimitive'; import {pointsFromObject} from '../../../core/geometry/entities/point/CorePointUtils'; const _points: CorePoint<CoreObjectType>[] = []; class DeleteSopParamsConfig extends NodeParamsConfig { /** @param defines the class that should be deleted (objects or vertices) */ class = ParamConfig.INTEGER(ATTRIBUTE_CLASSES_WITHOUT_CORE_GROUP.indexOf(AttribClass.POINT), { menu: { entries: AttribClassMenuEntriesWithoutCoreGroup, }, }); /** @param invert the selection created in the parameters below */ invert = ParamConfig.BOOLEAN(0); // hide_objects = ParamConfig.BOOLEAN(0, { // visibleIf: {class: ATTRIBUTE_CLASSES.indexOf(AttribClass.OBJECT)}, // }); // byObjectType /** @param deletes objects by object type */ byObjectType = ParamConfig.BOOLEAN(0, { visibleIf: {class: ATTRIBUTE_CLASSES.indexOf(AttribClass.OBJECT)}, }); /** @param sets which object types should be deleted */ objectType = ParamConfig.INTEGER(OBJECT_TYPES.indexOf(ObjectType.MESH), { menu: { entries: OBJECT_TYPE_MENU_ENTRIES, }, visibleIf: { class: ATTRIBUTE_CLASSES.indexOf(AttribClass.OBJECT), byObjectType: true, }, separatorAfter: true, }); // byExpression /** @param deletes objects by an expression */ byExpression = ParamConfig.BOOLEAN(0); /** @param sets the expression to select what should be deleted */ expression = ParamConfig.BOOLEAN('@ptnum==0', { visibleIf: {byExpression: true}, expression: {forEntities: true}, separatorAfter: true, }); // byAttrib /** @param deletes objects by an attribute */ byAttrib = ParamConfig.BOOLEAN(0); /** @param sets the type of the attribute for which items should be deleted */ attribType = ParamConfig.INTEGER(ATTRIBUTE_TYPES.indexOf(AttribType.NUMERIC), { menu: { entries: AttribTypeMenuEntries, }, visibleIf: {byAttrib: 1}, }); /** @param name of the attribute used */ attribName = ParamConfig.STRING('', { visibleIf: {byAttrib: 1}, }); /** @param size of the attribute used */ attribSize = ParamConfig.INTEGER(1, { range: ATTRIBUTE_SIZE_RANGE, rangeLocked: [true, true], visibleIf: {byAttrib: 1, attribType: ATTRIBUTE_TYPES.indexOf(AttribType.NUMERIC)}, }); /** @param comparison operator */ attribComparisonOperator = ParamConfig.INTEGER(COMPARISON_OPERATORS.indexOf(ComparisonOperator.EQUAL), { menu: { entries: ComparisonOperatorMenuEntries, }, visibleIf: { byAttrib: true, attribType: ATTRIBUTE_TYPES.indexOf(AttribType.NUMERIC), attribSize: AttribSize.FLOAT, }, }); /** @param value of the attribute to compare with (when using float attribute) */ attribValue1 = ParamConfig.FLOAT(0, { visibleIf: {byAttrib: 1, attribType: ATTRIBUTE_TYPES.indexOf(AttribType.NUMERIC), attribSize: 1}, }); /** @param value of the attribute to compare with (when using vector2 attribute) */ attribValue2 = ParamConfig.VECTOR2([0, 0], { visibleIf: {byAttrib: 1, attribType: ATTRIBUTE_TYPES.indexOf(AttribType.NUMERIC), attribSize: 2}, }); /** @param value of the attribute to compare with (when using vector3 attribute) */ attribValue3 = ParamConfig.VECTOR3([0, 0, 0], { visibleIf: {byAttrib: 1, attribType: ATTRIBUTE_TYPES.indexOf(AttribType.NUMERIC), attribSize: 3}, }); /** @param value of the attribute to compare with (when using vector4 attribute) */ attribValue4 = ParamConfig.VECTOR4([0, 0, 0, 0], { visibleIf: {byAttrib: 1, attribType: ATTRIBUTE_TYPES.indexOf(AttribType.NUMERIC), attribSize: 4}, }); /** @param value of the attribute to compare with (when using string attribute) */ attribString = ParamConfig.STRING('', { visibleIf: {byAttrib: 1, attribType: ATTRIBUTE_TYPES.indexOf(AttribType.STRING)}, separatorAfter: true, }); // byBbox /** @param deletes objects that are inside a bounding box */ byBbox = ParamConfig.BOOLEAN(0, { visibleIf: [ { class: ATTRIBUTE_CLASSES.indexOf(AttribClass.POINT), }, { class: ATTRIBUTE_CLASSES.indexOf(AttribClass.PRIMITIVE), }, ], }); /** @param the bounding box size */ bboxSize = ParamConfig.VECTOR3([1, 1, 1], { visibleIf: [ { class: ATTRIBUTE_CLASSES.indexOf(AttribClass.POINT), byBbox: true, }, { class: ATTRIBUTE_CLASSES.indexOf(AttribClass.PRIMITIVE), byBbox: true, }, ], }); /** @param the bounding box center */ bboxCenter = ParamConfig.VECTOR3([0, 0, 0], { visibleIf: [ { class: ATTRIBUTE_CLASSES.indexOf(AttribClass.POINT), byBbox: true, }, { class: ATTRIBUTE_CLASSES.indexOf(AttribClass.PRIMITIVE), byBbox: true, }, ], separatorAfter: true, }); // byBoundingObject /** @param deletes objects that are inside an object. This uses the object from the 2nd input */ byBoundingObject = ParamConfig.BOOLEAN(0, { visibleIf: [ { class: ATTRIBUTE_CLASSES.indexOf(AttribClass.POINT), }, { class: ATTRIBUTE_CLASSES.indexOf(AttribClass.PRIMITIVE), }, ], }); // by_visible // by_visible = ParamConfig.BOOLEAN(0, { // visibleIf: {class: ATTRIBUTE_CLASSES.indexOf(AttribClass.OBJECT)}, // }); /** @param keeps points */ keepPoints = ParamConfig.BOOLEAN(0, { visibleIf: {class: ATTRIBUTE_CLASSES.indexOf(AttribClass.OBJECT)}, }); } const ParamsConfig = new DeleteSopParamsConfig(); export class DeleteSopNode extends TypedSopNode<DeleteSopParamsConfig> { override paramsConfig = ParamsConfig; static override type() { return SopType.DELETE; } private _markedForDeletionPerObjectIndex: Map<number, boolean> = new Map(); public readonly entitySelectionHelper = new EntitySelectionHelper(this); public readonly byExpressionHelper = new ByExpressionHelper(this); public readonly byAttributeHelper = new ByAttributeHelper(this); public readonly byObjectTypeHelper = new ByObjectTypeHelper(this); public readonly byBboxHelper = new ByBboxHelper(this); public readonly byBoundingObjectHelper = new ByBoundingObjectHelper(this); override initializeNode() { this.io.inputs.setCount(1, 2); this.io.inputs.initInputsClonedState(InputCloneMode.FROM_NODE); } override async cook(inputCoreGroups: CoreGroup[]) { const coreGroup0 = inputCoreGroups[0]; const coreGroup1 = inputCoreGroups[1]; if (!coreGroup0) { this.cookController.endCook(); return; } const attribClass = ATTRIBUTE_CLASSES_WITHOUT_CORE_GROUP[this.pv.class]; switch (attribClass) { case AttribClass.POINT: return await this._evalForPoints(coreGroup0, coreGroup1); case AttribClass.VERTEX: this.states.error.set(`vertex not supported yet`); case AttribClass.PRIMITIVE: return await this._evalForPrimitives(coreGroup0, coreGroup1); case AttribClass.OBJECT: return await this._evalForObjects(coreGroup0); case AttribClass.CORE_GROUP: this.states.error.set(`core group not supported yet`); return; } TypeAssert.unreachable(attribClass); } setAttribClass(attribClass: AttribClass) { this.p.class.set(ATTRIBUTE_CLASSES.indexOf(attribClass)); } attribClass() { return ATTRIBUTE_CLASSES[this.pv.class]; } setAttribType(attribType: AttribType) { this.p.attribType.set(ATTRIBUTE_TYPES.indexOf(attribType)); } attribType() { return ATTRIBUTE_TYPES[this.pv.attribType]; } private async _evalForPoints(coreGroup: CoreGroup, coreGroup2?: CoreGroup) { const objects = coreGroup.allObjects(); const newObjects: ObjectContent<CoreObjectType>[] = []; for (const object of objects) { const entities: CorePoint<CoreObjectType>[] = []; pointsFromObject(object, entities); this.entitySelectionHelper.init(entities); const initEntitiesCount = entities.length; if (isBooleanTrue(this.pv.byExpression)) { await this.byExpressionHelper.evalForEntities(entities); } // TODO: the helpers do not yet take into account if an entity has been selected or not. // This could really speed up iterating through them, as I could skip the ones that have already been if (isBooleanTrue(this.pv.byAttrib) && this.pv.attribName != '') { this.byAttributeHelper.evalForEntities(entities); } if (isBooleanTrue(this.pv.byBbox)) { this.byBboxHelper.evalForEntities(entities); } if (isBooleanTrue(this.pv.byBoundingObject)) { this.byBoundingObjectHelper.evalForEntities(entities, coreGroup2); } const keptEntities = this.entitySelectionHelper.entitiesToKeep() as BaseCorePoint[]; if (keptEntities.length == initEntitiesCount) { newObjects.push(object); } else { if (keptEntities.length > 0) { const objectType = objectTypeFromObject(object); if (objectType) { const builder = geometryBuilder(objectType); if (builder) { const newGeo = builder.fromPoints(object, keptEntities); if (newGeo) { object.geometry = newGeo; newObjects.push(object); } } else { console.warn('no builder found for', objectType); } } else { console.warn('no object type found for', object); } } } } this.setObjects(newObjects); } private async _evalForPrimitives(coreGroup: CoreGroup, coreGroup2?: CoreGroup) { const objects = coreGroup.allObjects(); const newObjects: ObjectContent<CoreObjectType>[] = []; for (const object of objects) { const entities: CorePrimitive<CoreObjectType>[] = []; primitivesFromObject(object, entities); this.entitySelectionHelper.init(entities); const initEntitiesCount = entities.length; if (isBooleanTrue(this.pv.byExpression)) { await this.byExpressionHelper.evalForEntities(entities); } // TODO: the helpers do not yet take into account if an entity has been selected or not. // This could really speed up iterating through them, as I could skip the ones that have already been if (isBooleanTrue(this.pv.byAttrib) && this.pv.attribName != '') { this.byAttributeHelper.evalForEntities(entities); } if (isBooleanTrue(this.pv.byBbox)) { this.byBboxHelper.evalForEntities(entities); } if (isBooleanTrue(this.pv.byBoundingObject)) { this.byBoundingObjectHelper.evalForEntities(entities, coreGroup2); } const keptEntities = this.entitySelectionHelper.entitiesToKeep() as CorePrimitive<CoreObjectType>[]; if (keptEntities.length == initEntitiesCount) { newObjects.push(object); } else { if (keptEntities.length > 0) { const builder = keptEntities[0].builder(); if (builder) { const newObject = builder(object, keptEntities); if (newObject) { newObjects.push(newObject); } } else { const attribClass = ATTRIBUTE_CLASSES_WITHOUT_CORE_GROUP[this.pv.class]; console.warn(`no ${attribClass} builder for object`, object); } } } } this.setObjects(newObjects); } private async _evalForObjects(coreGroup: CoreGroup) { const coreObjects = coreGroup.allCoreObjects(); this.entitySelectionHelper.init(coreObjects); this._markedForDeletionPerObjectIndex = new Map(); for (let coreObject of coreObjects) { this._markedForDeletionPerObjectIndex.set(coreObject.index(), false); } if (isBooleanTrue(this.pv.byExpression)) { await this.byExpressionHelper.evalForEntities(coreObjects); } if (isBooleanTrue(this.pv.byObjectType)) { this.byObjectTypeHelper.evalForObjects(coreObjects); } if (isBooleanTrue(this.pv.byAttrib) && this.pv.attribName != '') { this.byAttributeHelper.evalForEntities(coreObjects); } const coreObjectsToKeep = this.entitySelectionHelper.entitiesToKeep() as ThreejsCoreObject[]; const objectsToKeep = coreObjectsToKeep.map((co) => co.object()); if (isBooleanTrue(this.pv.keepPoints)) { const coreObjectsToDelete = this.entitySelectionHelper.entitiesToDelete() as ThreejsCoreObject[]; for (let coreObjectToDelete of coreObjectsToDelete) { const pointObject = this._pointObject(coreObjectToDelete.object()); if (pointObject) { objectsToKeep.push(pointObject); } } } this.setObjects(objectsToKeep); } private _pointObject<T extends CoreObjectType>(object: ObjectContent<T>) { pointsFromObject(object, _points); const builder = geometryBuilder(ObjectType.POINTS); if (builder) { const geometry = builder.fromPoints(object, _points); if (geometry) return this.createObject(geometry, ObjectType.POINTS); } } }