UNPKG

@polygonjs/polygonjs

Version:

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

305 lines (286 loc) 9.97 kB
/** * Creates a mesh from an SDF function. * * */ import {Constructor, Number3, valueof} from '../../../types/GlobalTypes'; import {NodeContext} from '../../poly/NodeContext'; import {CoreGroup} from '../../../core/geometry/Group'; import {JsNodeChildrenMap} from '../../poly/registers/nodes/Js'; import {BaseJsNodeType} from '../js/_Base'; import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig'; import {NodeCreateOptions} from '../utils/hierarchy/ChildrenController'; import {SopType} from '../../poly/registers/nodes/types/Sop'; import {AssemblerName} from '../../poly/registers/assemblers/_BaseRegister'; import {Poly} from '../../Poly'; import {JsAssemblerController} from '../js/code/Controller'; import {JsAssemblerSDF} from '../js/code/assemblers/sdf/SDF'; import {JsNodeFinder} from '../js/code/utils/NodeFinder'; import {Box3, Vector3} from 'three'; import {SingleBodyFunctionData} from '../js/code/assemblers/_Base'; import {RegisterableVariable} from '../js/code/assemblers/_BaseJsPersistedConfigUtils'; import {InputCloneMode} from '../../poly/InputCloneMode'; import {SDFLoader} from '../../../core/geometry/modules/sdf/SDFLoader'; import {Box} from '../../../core/geometry/modules/sdf/SDFCommon'; import {TypedSopNode} from './_Base'; import {ModuleName} from '../../poly/registers/modules/Common'; import {SDFObject} from '../../../core/geometry/modules/sdf/SDFObject'; import {isArray, isBoolean, isNumberValid, isColor, isVector} from '../../../core/Type'; import {SDFPersistedConfig} from '../js/code/assemblers/sdf/SDFPersistedConfig'; import {ParamType} from '../../poly/ParamType'; import {JsParamConfig} from '../js/code/utils/JsParamConfig'; const _box3 = new Box3(); const box: Box = {min: [-1, -1, -1], max: [1, 1, 1]}; type SDFFunction = Function; //(p: any) => number; class SDFBuilderSopParamsConfig extends NodeParamsConfig { /** @param stepSize */ stepSize = ParamConfig.FLOAT(0.1, { range: [0.01, 1], rangeLocked: [true, false], }); /** @param level */ level = ParamConfig.FLOAT(0, { range: [-1, 1], rangeLocked: [false, false], }); /** @param min bound */ min = ParamConfig.VECTOR3([-1, -1, -1]); /** @param max bound */ max = ParamConfig.VECTOR3([1, 1, 1]); /** @param linear Tolerance */ facetAngle = ParamConfig.FLOAT(45, { range: [0.01, 180], rangeLocked: [true, false], }); /** @param meshes color */ meshesColor = ParamConfig.COLOR([1, 1, 1]); /** @param wireframe */ wireframe = ParamConfig.BOOLEAN(false, { // we need the separator for spare params separatorAfter: true, }); } const ParamsConfig = new SDFBuilderSopParamsConfig(); export class SDFBuilderSopNode extends TypedSopNode<SDFBuilderSopParamsConfig> { override paramsConfig = ParamsConfig; static override type() { return SopType.SDF_BUILDER; } override requiredModules() { return [ModuleName.SDF]; } override readonly persisted_config: SDFPersistedConfig = new SDFPersistedConfig(this); assemblerController() { return this._assemblerController; } public override usedAssembler(): Readonly<AssemblerName.JS_SDF> { return AssemblerName.JS_SDF; } protected _assemblerController = this._createAssemblerController(); private _createAssemblerController(): JsAssemblerController<JsAssemblerSDF> | undefined { return Poly.assemblersRegister.assembler(this, this.usedAssembler()); } // static PARAM_CALLBACK_reset(node: ParticlesSystemGpuSopNode) { // node.PARAM_CALLBACK_reset(); // } // PARAM_CALLBACK_reset() { // // this.gpu_controller.reset_gpu_compute_and_set_dirty(); // } // private _reset_material_if_dirty_bound = this._reset_material_if_dirty.bind(this); protected override _childrenControllerContext = NodeContext.JS; // private _on_create_prepare_material_bound = this._on_create_prepare_material.bind(this); override initializeNode() { this.io.inputs.setCount(0, 1); // set to never at the moment // otherwise the input is cloned on every frame inside cook_main() this.io.inputs.initInputsClonedState(InputCloneMode.NEVER); // this.addPostDirtyHook('_reset_material_if_dirty', this._reset_material_if_dirty_bound); // this.lifecycle.onCreated(this.assembler_controller.on_create.bind(this.assembler_controller)); // this.lifecycle.onCreated(this._on_create_prepare_material_bound); // this.children_controller?.init({dependent: false}); } 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 async cook(inputCoreGroups: CoreGroup[]) { const manifold = await SDFLoader.core(); // bbox const coreGroup = inputCoreGroups[0]; if (coreGroup) { coreGroup.boundingBox(_box3); _box3.min.toArray(box.min); _box3.max.toArray(box.max); } else { this.pv.min.toArray(box.min); this.pv.max.toArray(box.max); } // compile this.compileIfRequired(); // eval const _func = this._function; if (_func) { const args = this.functionEvalArgsWithParamConfigs(); const convertedFunction = (p: Number3) => { this._position.fromArray(p); return -1 * _func(...args); }; const geometry = manifold.Manifold.levelSet(convertedFunction, box, this.pv.stepSize, this.pv.level); const sdfObject = new SDFObject(geometry); const results = sdfObject.toObject3D(this.pv); if (results) { if (isArray(results)) { this.setObjects(results); } else { this.setObjects([results]); } } else { this.setObjects([]); } } else { this.setObjects([]); } } compileIfRequired() { if (this.assemblerController()?.compileRequired()) { this.compile(); } } private _position = new Vector3(); private _paramConfigs: JsParamConfig<ParamType>[] = []; // private _paramConfigNames: string[] = []; private _functionData: SingleBodyFunctionData | undefined; private _functionCreationArgs: string[] = []; private _functionEvalArgs: (Function | RegisterableVariable)[] = []; private _function: SDFFunction | undefined; 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 rootNodes = outputNodes.concat(paramNodes); 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: SingleBodyFunctionData) { 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 = [ 'position', '_setErrorFromError', ...variableNames, ...functionNames, ...paramConfigNames, wrappedBody, ]; this._functionEvalArgs = [ this._position, _setErrorFromError, ...variables, ...functions, // paramConfigs are added dynamically during cook ]; try { this._function = new Function(...this._functionCreationArgs) as SDFFunction; } catch (e) { console.warn(e); this.states.error.set('failed to compile'); } } functionEvalArgsWithParamConfigs() { const list: Array<Function | RegisterableVariable | number | boolean> = [...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; } }