@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
476 lines (443 loc) • 16.5 kB
text/typescript
/**
* Updates points with JS nodes
*
*
*/
import {TypedSopNode} from './_Base';
import {CoreGroup} from '../../../core/geometry/Group';
import {InputCloneMode} from '../../poly/InputCloneMode';
import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig';
import {EntityBuilderFunctionData} from '../js/code/assemblers/entityBuilder/_BaseEntityBuilderPersistedConfig';
import {AssemblerName} from '../../poly/registers/assemblers/_BaseRegister';
import {JsAssemblerController} from '../js/code/Controller';
import type {JsAssemblerEntityBuilder} from '../js/code/assemblers/entityBuilder/EntityBuilderAssembler';
import {
EntityContainer,
EntityBuilderAssemblerConstant,
} from '../js/code/assemblers/entityBuilder/EntityBuilderAssemblerCommon';
import {Poly} from '../../Poly';
import {NodeContext} from '../../poly/NodeContext';
import {JsNodeChildrenMap} from '../../poly/registers/nodes/Js';
import {NodeCreateOptions} from '../utils/hierarchy/ChildrenController';
import {Constructor, valueof} from '../../../types/GlobalTypes';
import {BaseJsNodeType} from '../js/_Base';
import {JsParamConfig} from '../js/code/utils/JsParamConfig';
import {ParamType} from '../../poly/ParamType';
import {RegisterableVariable, createVariable} from '../js/code/assemblers/_BaseJsPersistedConfigUtils';
import {JsNodeFinder} from '../js/code/utils/NodeFinder';
import {isBoolean, isNumberValid, isColor, isVector, isNumber} from '../../../core/Type';
import {BufferAttribute, Color, InterleavedBufferAttribute, Vector2, Vector3, Vector4} from 'three';
import {JsConnectionPointComponentsCountMap, JsConnectionPointType} from '../utils/io/connections/Js';
import {logBlue as _logBlue} from '../../../core/logger/Console';
import {EntityBuilderEvaluator} from '../js/code/assemblers/entityBuilder/EntityBuilderEvaluator';
import {primitivesCountFromObject} from '../../../core/geometry/entities/primitive/CorePrimitiveUtils';
import {CoreObjectType, ObjectContent} from '../../../core/geometry/ObjectContent';
import {
corePointClassFactory,
corePrimitiveClassFactory,
coreVertexClassFactory,
} from '../../../core/geometry/CoreObjectFactory';
import {filterObjectsFromCoreGroup} from '../../../core/geometry/Mask';
import {AttribClass} from '../../../core/geometry/Constant';
import {pointsCountFromObject} from '../../../core/geometry/entities/point/CorePointUtils';
import {verticesCountFromObject} from '../../../core/geometry/entities/vertex/CoreVertexUtils';
import {BaseVertexAttribute} from '../../../core/geometry/entities/vertex/VertexAttribute';
import {BasePrimitiveAttribute} from '../../../core/geometry/entities/primitive/PrimitiveAttribute';
import {CorePoint} from '../../../core/geometry/entities/point/CorePoint';
import {CorePrimitive} from '../../../core/geometry/entities/primitive/CorePrimitive';
import {CoreVertex} from '../../../core/geometry/entities/vertex/CoreVertex';
type EntityFunction = Function; //(object:Object3D)=>Object3D
type AttributeItem = boolean | number | string | Color | Vector2 | Vector3 | Vector4;
type AttributesDict = Map<string, AttributeItem>;
type AvailableEntity = AttribClass.POINT | AttribClass.VERTEX | AttribClass.PRIMITIVE;
export const AVAILABLE_ENTITIES: AvailableEntity[] = [AttribClass.POINT, AttribClass.VERTEX, AttribClass.PRIMITIVE];
type EntityAttribute = BufferAttribute | InterleavedBufferAttribute | BaseVertexAttribute | BasePrimitiveAttribute;
export class BaseEntityBuilderSopParamsConfig extends NodeParamsConfig {
/** @param group to assign the material to */
group = ParamConfig.STRING('', {
objectMask: true,
});
entity = ParamConfig.INTEGER(AVAILABLE_ENTITIES.indexOf(AttribClass.POINT), {
menu: {
entries: AVAILABLE_ENTITIES.map((entity, i) => {
return {name: entity, value: i};
}),
},
});
}
export abstract class BaseEntityBuilderSopNode<P extends BaseEntityBuilderSopParamsConfig> extends TypedSopNode<P> {
assemblerController() {
return this._assemblerController;
}
public override usedAssembler(): Readonly<AssemblerName.JS_ENTITY_BUILDER> {
return AssemblerName.JS_ENTITY_BUILDER;
}
protected _assemblerController = this._createAssemblerController();
private _createAssemblerController(): JsAssemblerController<JsAssemblerEntityBuilder> | undefined {
return Poly.assemblersRegister.assembler(this, this.usedAssembler());
}
protected override _childrenControllerContext = NodeContext.JS;
override createNode<S extends keyof JsNodeChildrenMap>(
node_class: S,
options?: NodeCreateOptions
): JsNodeChildrenMap[S];
override createNode<K extends valueof<JsNodeChildrenMap>>(
node_class: Constructor<K>,
options?: NodeCreateOptions
): K;
override createNode<K extends valueof<JsNodeChildrenMap>>(
node_class: Constructor<K>,
options?: NodeCreateOptions
): K {
return super.createNode(node_class, options) as K;
}
override children() {
return super.children() as BaseJsNodeType[];
}
override nodesByType<K extends keyof JsNodeChildrenMap>(type: K): JsNodeChildrenMap[K][] {
return super.nodesByType(type) as JsNodeChildrenMap[K][];
}
override childrenAllowed() {
if (this.assemblerController()) {
return super.childrenAllowed();
}
return false;
}
override sceneReadonly() {
return this.assemblerController() == null;
}
override initializeNode() {
this.io.inputs.setCount(1);
this.io.inputs.initInputsClonedState(InputCloneMode.FROM_NODE);
}
override async cook(inputCoreGroups: CoreGroup[]) {
const coreGroup = inputCoreGroups[0];
// compile
this.compileIfRequired();
// eval
const _func = this._function;
if (_func) {
const args = this.functionEvalArgsWithParamConfigs();
const evaluator = _func(...args) as EntityBuilderEvaluator;
const objects = filterObjectsFromCoreGroup(coreGroup, this.pv);
let objnum = 0;
for (const object of objects) {
this._processObject(object, objnum, evaluator);
objnum++;
}
this.setObjects(objects);
} else {
this.setObjects([]);
}
}
protected abstract _processObject<T extends CoreObjectType>(
object: ObjectContent<T>,
objnum: number,
evaluator: EntityBuilderEvaluator
): void;
protected _resetRequiredAttributes() {
this._attributesDict.clear();
}
protected _checkRequiredReadAttributes<T extends CoreObjectType>(object: ObjectContent<T>) {
// no need to check if there are no points in the geometry
const entitiesCount = this.entitiesCount(object);
if (entitiesCount == 0) {
return;
}
const entityClass = this.entityClass(object);
const readAttributesData = this._functionData?.attributesData.read;
if (!readAttributesData) {
return;
}
for (const attribData of readAttributesData) {
const attribute = entityClass.attribute(object, attribData.attribName);
if (!attribute) {
const message = `attribute ${attribData.attribName} is missing`;
this.states.error.set(message);
throw message;
return;
} else {
const expectedAttribSize = JsConnectionPointComponentsCountMap[attribData.attribType];
if (attribute.itemSize != expectedAttribSize) {
this.states.error.set('attribute size mismatch');
}
}
}
const attribNames: string[] = [];
const attributeByName = new Map<string, BufferAttribute>();
const attribTypeByName = new Map<string, JsConnectionPointType>();
for (const attribData of readAttributesData) {
const attribName = attribData.attribName;
const attribute = entityClass.attribute(object, attribName) as BufferAttribute;
if (attribute) {
attribNames.push(attribName);
attributeByName.set(attribName, attribute);
attribTypeByName.set(attribName, attribData.attribType);
}
}
return {attribNames, attributeByName, attribTypeByName};
}
protected _checkRequiredWriteAttributes<T extends CoreObjectType>(object: ObjectContent<T>) {
const writeAttributesData = this._functionData?.attributesData.write;
if (!writeAttributesData) {
return;
}
const entityClass = this.entityClass(object);
for (const attribData of writeAttributesData) {
let attribute = entityClass.attribute(object, attribData.attribName);
const expectedAttribSize = JsConnectionPointComponentsCountMap[attribData.attribType];
if (!attribute) {
const pointsCount = entityClass.entitiesCount(object);
const newArray: number[] = new Array(pointsCount * expectedAttribSize).fill(0);
attribute = this._createAttribute(object, attribData.attribName, newArray, expectedAttribSize);
}
if (attribute.itemSize != expectedAttribSize) {
this.states.error.set('attribute size mismatch');
}
}
const attribNames: string[] = [];
const attributeByName = new Map<string, EntityAttribute>();
const attribTypeByName = new Map<string, JsConnectionPointType>();
for (const attribData of writeAttributesData) {
const attribName = attribData.attribName;
const attribute: EntityAttribute | undefined = entityClass.attribute(object, attribName);
if (attribute) {
attribNames.push(attribName);
attributeByName.set(attribName, attribute);
attribTypeByName.set(attribName, attribData.attribType);
}
}
return {attribNames, attributeByName, attribTypeByName};
}
protected _readRequiredAttributes(
index: number,
attribNames: string[],
attributeByName: Map<string, BufferAttribute>,
attribTypeByName: Map<string, JsConnectionPointType>
) {
for (const attribName of attribNames) {
const attribute = attributeByName.get(attribName)!;
const attribType = attribTypeByName.get(attribName)!;
const variable = createVariable(attribType);
if (!variable) {
const attribValue = attribute.array[index * attribute.itemSize];
this._attributesDict.set(attribName, attribValue);
} else if (isVector(variable) || isColor(variable)) {
variable.fromBufferAttribute(attribute, index);
this._attributesDict.set(attribName, variable);
}
}
}
protected _writeRequiredAttributes(
index: number,
attribNames: string[],
attributeByName: Map<string, BufferAttribute>
) {
for (const attribName of attribNames) {
const attribute = attributeByName.get(attribName)!;
const variable = this._attributesDict.get(attribName);
if (isVector(variable) || isColor(variable)) {
variable.toArray(attribute.array, index * attribute.itemSize);
} else {
if (isNumber(variable)) {
attribute.array[index] = variable;
}
}
}
}
compileIfRequired() {
if (this.assemblerController()?.compileRequired()) {
this.compile();
}
}
protected abstract _entityContainer: EntityContainer;
private _paramConfigs: JsParamConfig<ParamType>[] = [];
private _functionData: EntityBuilderFunctionData | undefined;
private _functionCreationArgs: string[] = [];
private _functionEvalArgs: (EntityContainer | Function | RegisterableVariable | AttributesDict)[] = [];
private _function: EntityFunction | undefined;
private _attributesDict: AttributesDict = new Map();
functionData() {
return this._functionData;
}
compile() {
const assemblerController = this.assemblerController();
if (!assemblerController) {
return;
}
const outputNodes: BaseJsNodeType[] = JsNodeFinder.findOutputNodes(this);
// if (outputNodes.length == 0) {
// this.states.error.set('one output node is required');
// return;
// }
if (outputNodes.length > 1) {
this.states.error.set('only one output node allowed');
return;
}
// const outputNode = outputNodes[0];
// if (outputNode) {
const paramNodes = JsNodeFinder.findParamGeneratingNodes(this);
const attributeExportNodes = JsNodeFinder.findAttributeExportNodes(this);
const rootNodes = outputNodes.concat(paramNodes).concat(attributeExportNodes);
if (rootNodes.length == 0) {
this.states.error.set('at least one output, param or attribute node is required');
return;
}
assemblerController.assembler.set_root_nodes(rootNodes);
// main compilation
assemblerController.assembler.updateFunction();
// get functionData
const functionData = assemblerController.assembler.functionData();
if (!functionData) {
this.states.error.set('failed to compile ');
return;
}
this.updateFromFunctionData(functionData);
// }
assemblerController.post_compile();
}
updateFromFunctionData(functionData: EntityBuilderFunctionData) {
this._functionData = functionData;
const {functionBody, variableNames, variablesByName, functionNames, functionsByName, paramConfigs} =
this._functionData;
const wrappedBody = `
try {
${functionBody}
} catch(e) {
_setErrorFromError(e)
return 0;
}`;
const _setErrorFromError = (e: Error) => {
this.states.error.set(e.message);
};
const variables: RegisterableVariable[] = [];
const functions: Function[] = [];
for (const variableName of variableNames) {
const variable = variablesByName[variableName];
variables.push(variable);
}
for (const functionName of functionNames) {
const _func = functionsByName[functionName];
functions.push(_func);
}
this._paramConfigs = [...paramConfigs]; //[...paramConfigs];
const paramConfigNames: string[] = paramConfigs.map((pc) => pc.uniformName());
paramConfigs.forEach((p) => p.applyToNode(this));
this._functionCreationArgs = [
EntityBuilderAssemblerConstant.ENTITY_CONTAINER,
'_setErrorFromError',
...variableNames,
...functionNames,
EntityBuilderAssemblerConstant.ATTRIBUTES_DICT,
...paramConfigNames,
wrappedBody,
];
this._functionEvalArgs = [
this._entityContainer,
_setErrorFromError,
...variables,
...functions,
this._attributesDict,
// paramConfigs are added dynamically during cook
];
try {
this._function = new Function(...this._functionCreationArgs) as EntityFunction;
} catch (e) {
console.warn(e);
this.states.error.set('failed to compile');
}
}
functionEvalArgsWithParamConfigs() {
const list: Array<EntityContainer | Function | RegisterableVariable | number | boolean | AttributesDict> = [
...this._functionEvalArgs,
];
for (const paramConfig of this._paramConfigs) {
const paramName = paramConfig.name();
const spareParam = this.params.get(paramName);
if (spareParam && spareParam.value != null) {
if (
isBoolean(spareParam.value) ||
isNumberValid(spareParam.value) ||
isColor(spareParam.value) ||
isVector(spareParam.value)
) {
list.push(spareParam.value);
} else {
console.warn(`spareParam not found but type not yet copied to function args:'${paramName}'`);
}
} else {
console.warn(`spareParam not found:'${paramName}'`);
}
}
return list;
}
setEntity(attribClass: AvailableEntity) {
this.p.entity.set(AVAILABLE_ENTITIES.indexOf(attribClass));
}
entity(): AvailableEntity {
return AVAILABLE_ENTITIES[this.pv.entity];
}
entitiesCount(object: ObjectContent<CoreObjectType>): number {
const entity = this.entity();
switch (entity) {
case AttribClass.POINT: {
return pointsCountFromObject(object);
}
case AttribClass.VERTEX: {
return verticesCountFromObject(object);
}
case AttribClass.PRIMITIVE: {
return primitivesCountFromObject(object);
}
}
}
entityClass(
object: ObjectContent<CoreObjectType>
): typeof CorePoint<CoreObjectType> | typeof CoreVertex<CoreObjectType> | typeof CorePrimitive<CoreObjectType> {
const entity = this.entity();
switch (entity) {
case AttribClass.POINT: {
return corePointClassFactory(object);
}
case AttribClass.VERTEX: {
return coreVertexClassFactory(object);
}
case AttribClass.PRIMITIVE: {
return corePrimitiveClassFactory(object);
}
}
}
private _createAttribute(
object: ObjectContent<CoreObjectType>,
attribName: string,
values: number[],
attribSize: number
) {
const entity = this.entity();
switch (entity) {
case AttribClass.POINT: {
const attribute = new BufferAttribute(new Float32Array(values), attribSize);
corePointClassFactory(object).addAttribute(object, attribName, attribute);
return attribute;
}
case AttribClass.VERTEX: {
const attribute: BaseVertexAttribute = {
isString: false,
itemSize: attribSize,
array: values,
};
coreVertexClassFactory(object).addAttribute(object, attribName, attribute);
return attribute;
}
case AttribClass.PRIMITIVE: {
const attribute: BasePrimitiveAttribute = {
isString: false,
itemSize: attribSize,
array: values,
};
corePrimitiveClassFactory(object).addAttribute(object, attribName, attribute);
return attribute;
}
}
}
}