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