@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
386 lines (355 loc) • 14 kB
text/typescript
/**
* Copies a geometry onto every point from the right input.
*
* @remarks
* This is different than the instance SOP, as the operation here is more expensive, but allows for more flexibility.
*
*
*/
import {ObjectTransformMode, ObjectTransformSpace} from './../../../core/TransformSpace';
import {SopType} from './../../poly/registers/nodes/types/Sop';
import {TypedSopNode} from './_Base';
import {CoreGroup} from '../../../core/geometry/Group';
import {BaseCoreObject} from '../../../core/geometry/entities/object/BaseCoreObject';
import {CoreInstancer} from '../../../core/geometry/Instancer';
import {SopCopyStamp} from './utils/CopyStamp';
import {Matrix4} from 'three';
import {BaseCorePoint, CorePoint} from '../../../core/geometry/entities/point/CorePoint';
import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig';
import {InputCloneMode} from '../../poly/InputCloneMode';
import {isBooleanTrue} from '../../../core/BooleanValue';
import {
filterCoreObjectsFromCoreGroup,
filterObjectsWithGroup,
filterThreejsCoreObjectsFromCoreGroup,
} from '../../../core/geometry/Mask';
import {CoreTransform, RotationOrder, TRANSFORM_TARGET_TYPES, TransformTargetType} from '../../../core/Transform';
import {OBJECT_TRANSFORM_SPACE_MENU_ENTRIES, OBJECT_TRANSFORM_SPACES} from '../../../core/TransformSpace';
import {CoreObjectType, ObjectContent} from '../../../core/geometry/ObjectContent';
import {coreObjectClassFactory} from '../../../core/geometry/CoreObjectFactory';
import {pointsFromCoreObjects} from '../../../core/geometry/entities/point/CorePointUtils';
// export enum TransformMode {
// OBJECT = 'object',
// GEOMETRY = 'geometry',
// }
// export const TRANSFORM_MODES: TransformMode[] = [TransformMode.OBJECT, TransformMode.GEOMETRY];
// const TransformModeMenuEntries = [
// {name: 'object', value: TRANSFORM_MODES.indexOf(TransformMode.OBJECT)},
// {name: 'geometry', value: TRANSFORM_MODES.indexOf(TransformMode.GEOMETRY)},
// ];
class CopySopParamsConfig extends NodeParamsConfig {
/** @param select which objects are copied */
srcGroup = ParamConfig.STRING('', {
objectMask: true,
});
/** @param select which objects the src objects are copied onto */
templateGroup = ParamConfig.STRING('', {
objectMask: {
inputIndex: 1,
},
});
/** @param copies count, used when the second input is not given */
count = ParamConfig.INTEGER(1, {
range: [1, 20],
rangeLocked: [true, false],
});
/** @param translate each copy */
t = ParamConfig.VECTOR3([0, 0, 0]);
/** @param rotate each copy */
r = ParamConfig.VECTOR3([0, 0, 0]);
/** @param scale each copy */
s = ParamConfig.VECTOR3([1, 1, 1]);
/** @param scale multiplier for each copy */
scale = ParamConfig.FLOAT(1);
/** @param transforms every input object each on a single input point */
transformOnly = ParamConfig.BOOLEAN(0);
/** @param defines if the objects or the geometries are transformed */
transformMode = ParamConfig.INTEGER(TRANSFORM_TARGET_TYPES.indexOf(TransformTargetType.OBJECT), {
menu: {
entries: TRANSFORM_TARGET_TYPES.map((name, value) => ({name, value})),
},
});
/** @param defines how the objects are transformed */
objectTransformSpace = ParamConfig.INTEGER(0, {
visibleIf: {transformMode: TRANSFORM_TARGET_TYPES.indexOf(TransformTargetType.OBJECT)},
menu: {
entries: OBJECT_TRANSFORM_SPACE_MENU_ENTRIES,
},
});
/** @param toggles on to copy attributes from the input points to the created objects. Note that the vertex attributes from the points become object attributes */
copyAttributes = ParamConfig.BOOLEAN(0);
/** @param names of attributes to copy */
attributesToCopy = ParamConfig.STRING('', {
visibleIf: {copyAttributes: true},
});
/** @param toggle on to use the `copy` expression, which allows to change how the left input is evaluated for each point */
useCopyExpr = ParamConfig.BOOLEAN(1);
}
const ParamsConfig = new CopySopParamsConfig();
export class CopySopNode extends TypedSopNode<CopySopParamsConfig> {
override paramsConfig = ParamsConfig;
static override type() {
return SopType.COPY;
}
private _attribNamesToCopy: string[] = [];
private _objects: ObjectContent<CoreObjectType>[] = [];
private _stampNode!: SopCopyStamp;
override initializeNode() {
this.io.inputs.setCount(1, 2);
this.io.inputs.initInputsClonedState([InputCloneMode.FROM_NODE, InputCloneMode.NEVER]);
}
setTransformMode(transformMode: TransformTargetType) {
this.p.transformMode.set(TRANSFORM_TARGET_TYPES.indexOf(transformMode));
}
setObjectTransformSpace(transformSpace: ObjectTransformSpace) {
this.p.objectTransformSpace.set(OBJECT_TRANSFORM_SPACES.indexOf(transformSpace));
}
override async cook(inputCoreGroups: CoreGroup[]) {
if (!isBooleanTrue(this.pv.useCopyExpr)) {
this.stampNode().reset();
}
const coreGroup0 = inputCoreGroups[0];
if (!this.io.inputs.hasInput(1)) {
await this.cookWithoutTemplate(coreGroup0);
return;
}
const coreGroup1 = inputCoreGroups[1];
if (!coreGroup1) {
this.states.error.set('second input invalid');
return;
}
await this.cookWithTemplate(coreGroup0, coreGroup1);
this.stampNode().reset();
}
private _instancer = new CoreInstancer();
private async cookWithTemplate(instanceCoreGroup: CoreGroup, templateCoreGroup: CoreGroup) {
this._objects = [];
const templateCoreObjects = filterThreejsCoreObjectsFromCoreGroup(templateCoreGroup, {
group: this.pv.templateGroup,
});
const templatePoints: CorePoint<CoreObjectType>[] = [];
pointsFromCoreObjects(templateCoreObjects, templatePoints);
this._instancer.setCoreGroup(templateCoreGroup);
this._attribNamesToCopy = templateCoreGroup.pointAttribNamesMatchingMask(this.pv.attributesToCopy);
await this._copyMovedObjectsOnTemplatePoints(instanceCoreGroup, templatePoints);
this.setObjects(this._objects);
}
// https://stackoverflow.com/questions/24586110/resolve-promises-one-after-another-i-e-in-sequence
private async _copyMovedObjectsOnTemplatePoints(instanceCoreGroup: CoreGroup, templatePoints: BaseCorePoint[]) {
this._initAccumulatedTransform();
for (let pointIndex = 0; pointIndex < templatePoints.length; pointIndex++) {
await this._copyMovedObjectOnTemplatePoint(instanceCoreGroup, templatePoints, pointIndex);
this._accumulateTransform();
}
}
private _instanceMatrix = new Matrix4();
private async _copyMovedObjectOnTemplatePoint(
instanceCoreGroup: CoreGroup,
templatePoints: BaseCorePoint[],
point_index: number
) {
this._instancer.matrixFromPoint(templatePoints[point_index], this._instanceMatrix);
const templatePoint = templatePoints[point_index];
if (isBooleanTrue(this.pv.useCopyExpr)) {
this.stampNode().setPoint(templatePoint);
}
const movedObjects = await this._getMovedObjectsForTemplatePoint(instanceCoreGroup, point_index);
for (const movedObject of movedObjects) {
if (isBooleanTrue(this.pv.copyAttributes)) {
this._copyAttributesGromTemplate(movedObject, templatePoint);
}
// TODO: that node is getting inconsistent...
// should I always only move the object?
// and have a toggle to bake back to the geo?
// or just enfore the use of a merge?
if (isBooleanTrue(this.pv.transformOnly)) {
movedObject.applyMatrix4(this._instanceMatrix);
} else {
this._applyMatrixToObject(movedObject, this._instanceMatrix);
}
this._applyAccumulatedTransform(movedObject);
this._objects.push(movedObject);
}
}
private async _getMovedObjectsForTemplatePoint(
instanceCoreGroup: CoreGroup,
pointIndex: number
): Promise<ObjectContent<CoreObjectType>[]> {
const stampedInstanceCoreGroup = await this._stampInstanceGroupIfRequired(instanceCoreGroup);
if (stampedInstanceCoreGroup) {
// duplicate or select from instance children
const getObjectsForTransformOnly = () => {
const objects = filterObjectsWithGroup(stampedInstanceCoreGroup, {group: this.pv.srcGroup});
const object = objects[pointIndex];
if (object) {
return [coreObjectClassFactory(object).clone(object)];
} else {
return [];
}
};
const movedObjects = isBooleanTrue(this.pv.transformOnly)
? // TODO: why is doing a transform slower than cloning the input??
getObjectsForTransformOnly()
: filterObjectsWithGroup(stampedInstanceCoreGroup.clone(), {group: this.pv.srcGroup});
return movedObjects;
} else {
return [];
}
}
private async _stampInstanceGroupIfRequired(instanceCoreGroup: CoreGroup): Promise<CoreGroup | undefined> {
// we do not test here for pv.useCopyExpr
// as we use the stampNode.reset() at the beginning of the cook
// to reupdate it if necessary,
// which can be needed when we switch from
// useCopyExpr=true to useCopyExpr=false
// in which case the copy() expression should return 0.
// If we were not doing that, it would return the last evaluated value
// if (isBooleanTrue(this.pv.useCopyExpr)) {
const container0 = await this.containerController.requestInputContainer(0);
if (container0) {
const coreGroup0 = container0.coreContent();
if (coreGroup0) {
return coreGroup0;
} else {
return;
}
} else {
this.states.error.set(`input failed for index ${this.stampValue()}`);
return;
}
// } else {
// return instanceCoreGroup;
// }
}
private async _copyMovedObjectsForEachInstance(instanceCoreGroup: CoreGroup) {
this._initAccumulatedTransform();
for (let i = 0; i < this.pv.count; i++) {
await this._copyMovedObjectsForInstance(instanceCoreGroup, i);
this._accumulateTransform();
}
}
private async _copyMovedObjectsForInstance(instanceCoreGroup: CoreGroup, i: number) {
if (isBooleanTrue(this.pv.useCopyExpr)) {
this.stampNode().setGlobalIndex(i);
}
const stamptedInstanceCoreGroup = await this._stampInstanceGroupIfRequired(instanceCoreGroup);
if (stamptedInstanceCoreGroup) {
const srcCoreObjects = filterCoreObjectsFromCoreGroup(stamptedInstanceCoreGroup, {
group: this.pv.srcGroup,
});
for (const coreObject of srcCoreObjects) {
// TODO: I should use the Core Group, to ensure that material.linewidth is properly cloned
const clonedObject = coreObject.clone().object();
if (clonedObject) {
this._applyAccumulatedTransform(clonedObject);
this._objects.push(clonedObject);
}
}
}
}
// TODO: what if I combine both param_count and stamping?!
private async cookWithoutTemplate(instanceCoreGroup: CoreGroup) {
this._objects = [];
await this._copyMovedObjectsForEachInstance(instanceCoreGroup);
this.setObjects(this._objects);
}
private _copyAttributesGromTemplate(object: ObjectContent<CoreObjectType>, templatePoint: BaseCorePoint) {
this._attribNamesToCopy.forEach((attribName, i) => {
const attribValue = templatePoint.attribValue(attribName);
BaseCoreObject.addAttribute(object, attribName, attribValue);
});
}
//
//
// STAMP
//
//
stampValue(attribName?: string) {
return this.stampNode().value(attribName);
}
stampNode() {
return (this._stampNode = this._stampNode || this._createStampNode());
}
private _createStampNode() {
const stampNode = new SopCopyStamp(this.scene());
// this.dirtyController.setForbiddenTriggerNodes([stampNode]);
stampNode.setForbiddenTriggerNodes(this);
return stampNode;
}
override dispose() {
super.dispose();
if (this._stampNode) {
this._stampNode.dispose();
}
}
//
//
// ACCUMULATE TRANSFORM
//
//
private _coreTransform = new CoreTransform();
private _transformAccumulatedMatrix = new Matrix4();
private _transformMatrix = new Matrix4();
private _initAccumulatedTransform() {
const pv = this.pv;
this._transformMatrix = this._coreTransform.matrix(pv.t, pv.r, pv.s, pv.scale, RotationOrder.XYZ);
this._transformAccumulatedMatrix.identity();
}
private _accumulateTransform() {
this._transformAccumulatedMatrix.multiply(this._transformMatrix);
}
private _applyMatrixToObject(object: ObjectContent<CoreObjectType>, matrix: Matrix4) {
coreObjectClassFactory(object).applyMatrix(
object,
matrix,
TRANSFORM_TARGET_TYPES[this.pv.transformMode],
OBJECT_TRANSFORM_SPACES[this.pv.objectTransformSpace],
ObjectTransformMode.MULT
);
// if (isObject3D(object)) {
// this._applyMatrixToObjectOrGeometry(object, this._transformAccumulatedMatrix);
// } else {
// console.warn('transform cad accumulative not implemented');
// }
// object.matrix.multiply(this._transformAccumulatedMatrix);
// object.matrix.decompose(object.position, object.quaternion, object.scale);
// object.matrixAutoUpdate = false;
}
private _applyAccumulatedTransform(object: ObjectContent<CoreObjectType>) {
this._applyMatrixToObject(object, this._transformAccumulatedMatrix);
// if (isObject3D(object)) {
// this._applyMatrixToObjectOrGeometry(object, this._transformAccumulatedMatrix);
// } else {
// console.warn('transform cad accumulative not implemented');
// }
// object.matrix.multiply(this._transformAccumulatedMatrix);
// object.matrix.decompose(object.position, object.quaternion, object.scale);
// object.matrixAutoUpdate = false;
}
//
//
// MATRIX OPERATIONS
//
//
// private _applyMatrixToObjectOrGeometry(object: ObjectContent<CoreObjectType>, matrix: Matrix4) {
// const transformMode = TRANSFORM_MODES[this.pv.transformMode];
// switch (transformMode) {
// case TransformMode.OBJECT: {
// this._applyMatrixToObject(object, matrix);
// return;
// }
// case TransformMode.GEOMETRY: {
// const geometry = (object as Object3DWithGeometry).geometry;
// if (geometry) {
// geometry.applyMatrix4(matrix);
// }
// return;
// }
// }
// TypeAssert.unreachable(transformMode);
// }
// private _object_position = new Vector3();
// private _applyMatrixToObject(object: ObjectContent<CoreObjectType>, matrix: Matrix4) {
// applyTransformWithSpaceToObject(object, matrix, OBJECT_TRANSFORM_SPACES[this.pv.objectTransformSpace]);
// }
}