@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
246 lines (245 loc) • 8.11 kB
JavaScript
"use strict";
import { NodeContext } from "../../poly/NodeContext";
import { NodeParamsConfig, ParamConfig } from "../utils/params/ParamsConfig";
import { SopType } from "../../poly/registers/nodes/types/Sop";
import { AssemblerName } from "../../poly/registers/assemblers/_BaseRegister";
import { Poly } from "../../Poly";
import { JsNodeFinder } from "../js/code/utils/NodeFinder";
import { Box3, Vector3 } from "three";
import { InputCloneMode } from "../../poly/InputCloneMode";
import { SDFLoader } from "../../../core/geometry/modules/sdf/SDFLoader";
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";
const _box3 = new Box3();
const box = { min: [-1, -1, -1], max: [1, 1, 1] };
class SDFBuilderSopParamsConfig extends NodeParamsConfig {
constructor() {
super(...arguments);
/** @param stepSize */
this.stepSize = ParamConfig.FLOAT(0.1, {
range: [0.01, 1],
rangeLocked: [true, false]
});
/** @param level */
this.level = ParamConfig.FLOAT(0, {
range: [-1, 1],
rangeLocked: [false, false]
});
/** @param min bound */
this.min = ParamConfig.VECTOR3([-1, -1, -1]);
/** @param max bound */
this.max = ParamConfig.VECTOR3([1, 1, 1]);
/** @param linear Tolerance */
this.facetAngle = ParamConfig.FLOAT(45, {
range: [0.01, 180],
rangeLocked: [true, false]
});
/** @param meshes color */
this.meshesColor = ParamConfig.COLOR([1, 1, 1]);
/** @param wireframe */
this.wireframe = ParamConfig.BOOLEAN(false, {
// we need the separator for spare params
separatorAfter: true
});
}
}
const ParamsConfig = new SDFBuilderSopParamsConfig();
export class SDFBuilderSopNode extends TypedSopNode {
constructor() {
super(...arguments);
this.paramsConfig = ParamsConfig;
this.persisted_config = new SDFPersistedConfig(this);
this._assemblerController = this._createAssemblerController();
// 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);
this._childrenControllerContext = NodeContext.JS;
this._position = new Vector3();
this._paramConfigs = [];
this._functionCreationArgs = [];
this._functionEvalArgs = [];
}
static type() {
return SopType.SDF_BUILDER;
}
requiredModules() {
return [ModuleName.SDF];
}
assemblerController() {
return this._assemblerController;
}
usedAssembler() {
return AssemblerName.JS_SDF;
}
_createAssemblerController() {
return Poly.assemblersRegister.assembler(this, this.usedAssembler());
}
// private _on_create_prepare_material_bound = this._on_create_prepare_material.bind(this);
initializeNode() {
this.io.inputs.setCount(0, 1);
this.io.inputs.initInputsClonedState(InputCloneMode.NEVER);
}
createNode(node_class, options) {
return super.createNode(node_class, options);
}
children() {
return super.children();
}
nodesByType(type) {
return super.nodesByType(type);
}
childrenAllowed() {
if (this.assemblerController()) {
return super.childrenAllowed();
}
return false;
}
sceneReadonly() {
return this.assemblerController() == null;
}
async cook(inputCoreGroups) {
const manifold = await SDFLoader.core();
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);
}
this.compileIfRequired();
const _func = this._function;
if (_func) {
const args = this.functionEvalArgsWithParamConfigs();
const convertedFunction = (p) => {
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() {
var _a;
if ((_a = this.assemblerController()) == null ? void 0 : _a.compileRequired()) {
this.compile();
}
}
functionData() {
return this._functionData;
}
compile() {
const assemblerController = this.assemblerController();
if (!assemblerController) {
return;
}
const outputNodes = 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);
assemblerController.assembler.updateFunction();
const functionData = assemblerController.assembler.functionData();
if (!functionData) {
this.states.error.set("failed to compile ");
return;
}
this.updateFromFunctionData(functionData);
}
assemblerController.post_compile();
}
updateFromFunctionData(functionData) {
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) => {
this.states.error.set(e.message);
};
const variables = [];
const functions = [];
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];
const paramConfigNames = 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);
} catch (e) {
console.warn(e);
this.states.error.set("failed to compile");
}
}
functionEvalArgsWithParamConfigs() {
const list = [...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;
}
}