UNPKG

@polygonjs/polygonjs

Version:

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

482 lines (447 loc) 16.1 kB
/** * a subnet can contain many nodes and is very useful to organise other nodes * * * */ import {Constructor, valueof} from '../../../types/GlobalTypes'; import {TypedJsNode, BaseJsNodeType} from './_Base'; import {JsConnectionPointType, JS_CONNECTION_POINT_TYPES} from '../utils/io/connections/Js'; import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig'; import {NetworkChildNodeType, NetworkNodeType, NodeContext} from '../../poly/NodeContext'; import {JsNodeChildrenMap} from '../../poly/registers/nodes/Js'; import {SubnetInputJsNode} from './SubnetInput'; import {SubnetOutputJsNode} from './SubnetOutput'; import {NodeCreateOptions} from '../utils/hierarchy/ChildrenController'; import {rangeStartEnd} from '../../../core/ArrayUtils'; import {IntegerParam} from '../../params/Integer'; import {StringParam} from '../../params/String'; import type {JsLinesCollectionController} from './code/utils/JsLinesCollectionController'; import {LineType} from './code/utils/LineType'; import {TypedNodeTraverser} from '../utils/shaders/NodeTraverser'; import {JsCodeBuilder} from './code/utils/CodeBuilder'; import {BaseJsDefinition} from './utils/JsDefinition'; import {AddBodyLinesOptions} from './code/utils/LinesController'; export const ADD_BODY_LINES_OPTIONS: AddBodyLinesOptions = { makeUniq: false, }; function visibleIfInputsCountAtLeast(index: number) { return { visibleIf: rangeStartEnd(index + 1, 10).map((i) => ({inputsCount: i})), }; } function inputTypeParam(index: number) { return ParamConfig.INTEGER(JS_CONNECTION_POINT_TYPES.indexOf(JsConnectionPointType.FLOAT), { menu: { entries: JS_CONNECTION_POINT_TYPES.map((name, i) => { return {name: name, value: i}; }), }, separatorBefore: true, ...visibleIfInputsCountAtLeast(index), }); } function inputNameParam(index: number) { return ParamConfig.STRING(`input${index}`, { ...visibleIfInputsCountAtLeast(index), }); } export function TypedSubnetJsParamsConfigMixin<TBase extends Constructor>(Base: TBase) { return class Mixin extends Base { main = ParamConfig.FOLDER(); time = ParamConfig.FLOAT(0, { step: 0.001, }); inputs = ParamConfig.FOLDER(); inputsCount = ParamConfig.INTEGER(1, { range: [0, 10], rangeLocked: [true, true], }); inputType0 = inputTypeParam(0); inputName0 = inputNameParam(0); inputType1 = inputTypeParam(1); inputName1 = inputNameParam(1); inputType2 = inputTypeParam(2); inputName2 = inputNameParam(2); inputType3 = inputTypeParam(3); inputName3 = inputNameParam(3); inputType4 = inputTypeParam(4); inputName4 = inputNameParam(4); inputType5 = inputTypeParam(5); inputName5 = inputNameParam(5); inputType6 = inputTypeParam(6); inputName6 = inputNameParam(6); inputType7 = inputTypeParam(7); inputName7 = inputNameParam(7); inputType8 = inputTypeParam(8); inputName8 = inputNameParam(8); inputType9 = inputTypeParam(9); inputName9 = inputNameParam(9); spare = ParamConfig.FOLDER(); }; } class TypedSubnetJsParamsConfig extends TypedSubnetJsParamsConfigMixin(NodeParamsConfig) {} export class AbstractTypedSubnetJsNode<K extends NodeParamsConfig> extends TypedJsNode<K> { protected override _childrenControllerContext = NodeContext.JS; override initializeNode() { this.childrenController?.setOutputNodeFindMethod(() => { return this.nodesByType(SubnetOutputJsNode.type())[0]; }); this.io.connection_points.set_input_name_function(this._expectedInputName.bind(this)); this.io.connection_points.set_expected_input_types_function(this._expectedInputTypes.bind(this)); this.io.connection_points.set_expected_output_types_function(this._expectedOutputTypes.bind(this)); this.io.connection_points.set_output_name_function(this._expectedOutputName.bind(this)); } protected _expectedInputTypes(): JsConnectionPointType[] { return []; } protected _expectedInputName(index: number) { return 'default'; } protected _expectedOutputTypes() { return this._expectedInputTypes(); } protected _expectedOutputName(index: number) { return this._expectedInputName(index); } // // // defines the outputs for the child subnet input // // childExpectedInputConnectionPointTypes() { return this._expectedInputTypes(); } childExpectedOutputConnectionPointTypes() { return this._expectedOutputTypes(); } childExpectedInputConnectionPointName(index: number) { return this._expectedInputName(index); } childExpectedOutputConnectionPointName(index: number) { return this._expectedOutputName(index); } // // // CHILDREN // // 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][]; } } export class TypedSubnetJsNode<K extends TypedSubnetJsParamsConfig> extends AbstractTypedSubnetJsNode<K> { override initializeNode() { super.initializeNode(); this.io.connection_points.set_input_name_function(this._expectedInputName.bind(this)); this.io.connection_points.set_expected_input_types_function(this._expectedInputTypes.bind(this)); this.io.connection_points.set_expected_output_types_function(this._expectedOutputTypes.bind(this)); this.io.connection_points.set_output_name_function(this._expectedOutputName.bind(this)); } protected _inputTypeParams(): IntegerParam[] { return [ this.p.inputType0, this.p.inputType1, this.p.inputType2, this.p.inputType3, this.p.inputType4, this.p.inputType5, this.p.inputType6, this.p.inputType7, this.p.inputType8, this.p.inputType9, ]; } protected _inputNameParams(): StringParam[] { return [ this.p.inputName0, this.p.inputName1, this.p.inputName2, this.p.inputName3, this.p.inputName4, this.p.inputName5, this.p.inputName6, this.p.inputName7, this.p.inputName8, this.p.inputName9, ]; } setInputType(index: number, type: JsConnectionPointType) { const param = this._inputTypeParams()[index]; if (!param) { return; } param.set(JS_CONNECTION_POINT_TYPES.indexOf(type)); } setInputName(index: number, inputName: string) { const param = this._inputNameParams()[index]; if (!param) { return; } param.set(inputName); } protected _expectedInputsCount(): number { return this.pv.inputsCount; } protected override _expectedInputTypes(): JsConnectionPointType[] { const count = this.pv.inputsCount; const params: IntegerParam[] = this._inputTypeParams(); return rangeStartEnd(0, count).map((value, i) => JS_CONNECTION_POINT_TYPES[params[i].value]); } protected override _expectedInputName(index: number) { const params: StringParam[] = this._inputNameParams(); const param = params[index]; return param ? param.value : JsConnectionPointType.FLOAT; } protected override _expectedOutputTypes() { const count = this.pv.inputsCount; const params: IntegerParam[] = this._inputTypeParams(); return rangeStartEnd(0, count).map((value, i) => JS_CONNECTION_POINT_TYPES[params[i].value]); } protected override _expectedOutputName(index: number) { // return this._expected_input_name(index); const params: StringParam[] = this._inputNameParams(); return params[index].value; } override setLines(linesController: JsLinesCollectionController) { this._setLinesPreBlock(linesController); this.setLinesBlockStart(linesController); this._setLinesBlockContent(linesController); this.setLinesBlockEnd(linesController); } protected linesBlockContent(linesController: JsLinesCollectionController) { const codeBuilder = this._runCodeBuilder(linesController); if (!codeBuilder) { return; } const shadername = linesController.currentShaderName(); const bodyLines = codeBuilder.lines(shadername, LineType.BODY); return this._sanitizeBodyLines(bodyLines); } protected _setLinesPreBlock(linesController: JsLinesCollectionController) { if (this._traverseChildren(linesController)) { return; } const bodyLines: string[] = []; const connection_points = this.io.inputs.namedInputConnectionPoints(); if (!connection_points) { return; } for (let i = 0; i < connection_points.length; i++) { const connection_point = connection_points[i]; // const gl_type = connection_point.type(); const out = this.jsVarName(connection_point.name()); const in_value = this.variableForInput(linesController, connection_point.name()); const body_line = `let ${out} = ${in_value}`; bodyLines.push(body_line); } linesController._addBodyLines(this, bodyLines); } protected setLinesBlockStart(linesController: JsLinesCollectionController) { if (this._traverseChildren(linesController)) { return; } linesController._addBodyLines(this, [`if(true){`]); } protected _setLinesBlockContent(linesController: JsLinesCollectionController) { const bodyLines = this.linesBlockContent(linesController); if (!bodyLines) { return; } linesController._addBodyLines(this, bodyLines, undefined, ADD_BODY_LINES_OPTIONS); } protected setLinesBlockEnd(linesController: JsLinesCollectionController) { if (this._traverseChildren(linesController)) { return; } linesController._addBodyLines(this, [`}`]); } protected _runCodeBuilder(linesController: JsLinesCollectionController) { // I potentially could look for attribute nodes to use as output, // but for now, I'll enforce a rule that attribute nodes must be at the top level const outputNodes: SubnetOutputJsNode[] = this.nodesByType(NetworkChildNodeType.OUTPUT); const functionNode = this.functionNode(); if (!functionNode) { return; } if (outputNodes.length == 0) { functionNode.states.error.set(`${this.path()}:one output node is required`); } if (outputNodes.length > 1) { functionNode.states.error.set(`${this.path()}:only one output node allowed`); } const subnetOutput = outputNodes[0]; const subnetOutputInputConnectionPoints = subnetOutput.io.inputs.namedInputConnectionPoints(); const subnetOutputInputNames = subnetOutputInputConnectionPoints ? subnetOutputInputConnectionPoints.map((cp) => cp.name()) : []; const assembler = linesController.assembler(); const nodeTraverser = new TypedNodeTraverser<NodeContext.JS>( this, linesController.shaderNames(), (rootNode, shaderName) => { return subnetOutputInputNames; } ); const codeBuilder = new JsCodeBuilder( nodeTraverser, (shaderName, rootNodes) => { // return [subnetOutput]; return assembler.rootNodesByShaderName(shaderName, rootNodes); }, assembler ); const paramNodes: BaseJsNodeType[] = []; codeBuilder.buildFromNodes(outputNodes, paramNodes); this._addCodeBuilderDefinition(codeBuilder, linesController); return codeBuilder; } private _addCodeBuilderDefinition(codeBuilder: JsCodeBuilder, linesController: JsLinesCollectionController) { const internalShadersCollectionController = codeBuilder.shadersCollectionController(); if (!internalShadersCollectionController) { return; } const currentShaderName = linesController.currentShaderName(); internalShadersCollectionController.setCurrentShaderName(currentShaderName); // 1- add all definitions for each shaderName const shaderNames = linesController.shaderNames(); for (const shaderName of shaderNames) { const definitions: BaseJsDefinition[] = []; internalShadersCollectionController.traverseDefinitions(shaderName, (definition) => { // only add function if it is for the current shader const isNotFunction = true; //!(definition instanceof FunctionGLDefinition); const isCurrentShader = shaderName == currentShaderName; if (isNotFunction || isCurrentShader) { definitions.push(definition); } }); linesController.addDefinitions(this, definitions, shaderName); } // 2- add vertex body lines if current shader name is fragment // if (currentShaderName != JsFunctionName.VERTEX) { // const attribNodes = this.nodesByType(GlType.ATTRIBUTE); // const bodyLines: string[] = []; // for (const attribNode of attribNodes) { // const linesForNode = internalShadersCollectionController.bodyLines(ShaderName.VERTEX, attribNode); // if (linesForNode) { // bodyLines.push(...linesForNode); // } // } // linesController.addBodyLines(this, bodyLines, ShaderName.VERTEX, ADD_BODY_LINES_OPTIONS); // } } setSubnetInputLines(linesController: JsLinesCollectionController, childNode: SubnetInputJsNode) { const outputTypes = childNode.expectedOutputTypes(); let i = 0; for (const _ of outputTypes) { const inputName = this.inputNameForSubnetInput(i); const inputValue = this.variableForInput(linesController, inputName); const dataType = outputTypes[0]; const varName = childNode.jsVarName(inputName); linesController.addBodyOrComputed(childNode, [ { dataType, varName, value: inputValue, }, ]); i++; } } setSubnetOutputLines(linesController: JsLinesCollectionController, childNode: SubnetOutputJsNode) { // const bodyLines: string[] = this.subnetOutputLines(childNode); // shadersCollectionController.addBodyLines(childNode, bodyLines, undefined, ADD_BODY_LINES_OPTIONS); const inputTypes = childNode.expectedInputTypes(); let i = 0; const addLineIfNotConnected = this._traverseChildren(linesController); for (const _ of inputTypes) { const inputName = childNode.expectedInputName(i); const inputValue = childNode.variableForInput(linesController, inputName); const dataType = inputTypes[0]; const varName = this.jsVarName(this.outputNameForSubnetOutput(i) || ''); const isInputConnected = childNode.io.inputs.named_input(inputName); const addLine = isInputConnected || addLineIfNotConnected; if (addLine) { linesController.addBodyOrComputed( childNode, [ { dataType, varName, value: inputValue, }, ], { constPrefix: false, } ); } i++; } } private _traverseChildren(linesController: JsLinesCollectionController) { return linesController.assembler().computedVariablesAllowed(); } inputNameForSubnetInput(index: number) { return this._expectedInputName(index); } outputNameForSubnetOutput(index: number) { return this._expectedOutputName(index); } // align with the right number of tabs protected _sanitizeBodyLines(lines: string[]): string[] { return lines; // const level = CodeFormatter.nodeDistanceToMaterial(this); // const prefix = `\t`.repeat(level); // return lines.map((line) => { // const trimmed = line.trim(); // if (trimmed.length == 0) { // return ''; // } else { // return `${prefix}${trimmed}`; // } // }); } } class SubnetJsParamsConfig extends TypedSubnetJsParamsConfigMixin(NodeParamsConfig) {} const ParamsConfig = new SubnetJsParamsConfig(); export class SubnetJsNode extends TypedSubnetJsNode<SubnetJsParamsConfig> { override paramsConfig = ParamsConfig; static override type() { return NetworkNodeType.SUBNET; } // public override outputValue(context: JsNodeTriggerContext, outputName: string) { // const subnetOutput = this.nodesByType(NetworkChildNodeType.OUTPUT)[0]; // if (subnetOutput) { // return subnetOutput.outputValue(context, outputName); // } else { // return 0; // } // } // inputValueForSubnetInput(context: JsNodeTriggerContext, outputName: string) { // return this._inputValue<JsConnectionPointType>(outputName, context) || 0; // } // override inputNameForSubnetInput(index: number) { // return this._expectedInputName(index); // } // override outputNameForSubnetOutput(index: number) { // return this._expectedOutputName(index); // } }