@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
236 lines (219 loc) • 7.1 kB
text/typescript
import {
BaseJsShaderAssembler,
INSERT_DEFINE_AFTER,
INSERT_CONSTRUCTOR_AFTER,
INSERT_BODY_AFTER,
INSERT_MEMBERS_AFTER,
SpareParamOptions,
} from '../_Base';
import {RegisterableVariable} from '../_BaseJsPersistedConfigUtils';
import {JsFunctionName} from '../../../../utils/shaders/ShaderName';
import {connectedTriggerableNodes, findTriggeringNodes, inputNodesExceptTrigger} from './ActorAssemblerUtils';
import {BaseJsNodeType} from '../../../_Base';
import {setToArray} from '../../../../../../core/SetUtils';
import {PrettierController} from '../../../../../../core/code/PrettierController';
import {NamedFunctionMap} from '../../../../../poly/registers/functions/All';
import {ActorFunctionData} from './ActorPersistedConfig';
import {EvaluatorEventData} from './ActorEvaluator';
import {ActorAssemblerConstant} from './ActorAssemblerCommon';
import {isFunction,isArray} from '../../../../../../core/Type';
import {ParamOptions} from '../../../../../params/utils/OptionsController';
import {JsConnectionPointType} from '../../../../utils/io/connections/Js';
import {ActorBuilderNode} from '../../../../../scene/utils/ActorsManager';
import {logBlue as _logBlue} from '../../../../../../core/logger/Console';
import {ParamType} from '../../../../../poly/ParamType';
import {JsShaderConfig} from '../../configs/ShaderConfig';
const TEMPLATE = `
${INSERT_DEFINE_AFTER}
class CustomActorEvaluator extends ActorEvaluator {
${INSERT_MEMBERS_AFTER}
constructor(node, object3D){
super(node, object3D);
${INSERT_CONSTRUCTOR_AFTER}
}
${INSERT_BODY_AFTER}
`;
const CLOSE_CLASS_DEFINITION = `};
return CustomActorEvaluator;`;
const _tmp: BaseJsNodeType[] = [];
export class JsAssemblerActor extends BaseJsShaderAssembler {
makeFunctionNodeDirtyOnChange() {
return false;
}
override templateShader() {
return {
main: TEMPLATE,
};
}
override inputNamesForShaderName(rootNode: BaseJsNodeType, shaderName: JsFunctionName) {
return (rootNode.io.inputs.namedInputConnectionPoints() || [])
.filter((cp) => cp.type() != JsConnectionPointType.TRIGGER)
.map((cp) => cp.name());
}
override computedVariablesAllowed(): boolean {
return true;
}
override spareParamsOptions(options: SpareParamOptions) {
const {type} = options;
const _options: ParamOptions = {
spare: true,
computeOnDirty: type != ParamType.PARAM_PATH,
cook: false,
// dependentOnFoundNode: true,
// there is no point in setting the callback option here,
// as it would then not be present when reloading the scene
// callback: (node, param) => {
// touchParamRef(node, param.name());
// },
};
return _options;
}
defaultObjectVariable(): string {
return ActorAssemblerConstant.OBJECT_3D;
}
defaultObject3DMaterialVariable(): string {
return ActorAssemblerConstant.MATERIAL;
}
defaultPrimitiveGraph(): string {
return ActorAssemblerConstant.PRIMITIVE_GRAPH;
}
createFunctionData(additionalRootNodes: BaseJsNodeType[]): ActorFunctionData | undefined {
const node = this.currentJsParentNode() as ActorBuilderNode;
this._reset();
//
const triggeringNodes = findTriggeringNodes(node);
const triggerableNodes = new Set<BaseJsNodeType>();
connectedTriggerableNodes({triggeringNodes, triggerableNodes, recursive: true});
const shaderNames = this.shaderNames();
const functionData = this._createFunctionData(
additionalRootNodes,
triggeringNodes,
triggerableNodes,
shaderNames
);
return functionData;
}
private _createFunctionData(
additionalRootNodes: BaseJsNodeType[],
triggeringNodes: Set<BaseJsNodeType>,
triggerableNodes: Set<BaseJsNodeType>,
shaderNames: JsFunctionName[]
): ActorFunctionData | undefined {
const functionNode = this.currentJsParentNode() as ActorBuilderNode;
//
//
// create computed props
//
//
const _addComputedProps = () => {
const rootNodesSet: Set<BaseJsNodeType> = new Set();
triggerableNodes.forEach((trigerrableNode) => {
const rootNodes = inputNodesExceptTrigger(trigerrableNode, _tmp);
for (const rootNode of rootNodes) {
rootNodesSet.add(rootNode);
}
});
setToArray(rootNodesSet, _tmp);
const rootNodes = _tmp.concat(additionalRootNodes);
this.set_root_nodes(rootNodes);
this.buildCodeFromNodes(this._root_nodes, {
actor: {
functionNode,
triggeringNodes: triggeringNodes,
triggerableNodes: triggerableNodes,
},
});
this._buildLines();
for (const shaderName of shaderNames) {
const lines = this._lines.get(shaderName);
if (lines) {
this._shaders_by_name.set(shaderName, lines.join('\n'));
}
}
};
_addComputedProps();
//
//
// ASSEMBLE FUNCTION BODY
//
//
const _buildFunctionBody = () => {
const bodyLines = this._shaders_by_name.get(JsFunctionName.MAIN) || TEMPLATE;
const functionBodyElements = [bodyLines, CLOSE_CLASS_DEFINITION];
const functionBody = PrettierController.formatJs(functionBodyElements.join('\n'));
return functionBody;
};
const functionBody = _buildFunctionBody();
//
//
// gather function data
//
//
const variableNames: string[] = [];
const functionNames: Array<keyof NamedFunctionMap> = [];
const variablesByName: Record<string, RegisterableVariable> = {};
const functionsByName: Record<string, Function> = {};
this.traverseRegisteredVariables((variable, varName) => {
variableNames.push(varName);
variablesByName[varName] = variable;
});
this.traverseRegisteredFunctions((namedFunction) => {
functionNames.push(namedFunction.type() as keyof NamedFunctionMap);
functionsByName[namedFunction.type()] = namedFunction.func.bind(namedFunction);
});
const paramConfigs = this.param_configs();
//
//
// create evaluator
//
//
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);
}
const eventDatas: EvaluatorEventData[] = [];
this.currentJsParentNode().childrenController?.traverseChildren((child) => {
const eventDataFunction = (child as BaseJsNodeType).eventData;
if (eventDataFunction && isFunction(eventDataFunction)) {
const eventData = (child as BaseJsNodeType).eventData();
if (eventData) {
if (isArray(eventData)) {
eventDatas.push(...eventData);
} else {
eventDatas.push(eventData);
}
}
}
});
const functionData: ActorFunctionData = {
functionBody,
variableNames,
variablesByName,
functionNames,
functionsByName,
paramConfigs: [...paramConfigs],
eventDatas,
};
return functionData;
}
override rootNodesByShaderName(shaderName: JsFunctionName, rootNodes: BaseJsNodeType[]): BaseJsNodeType[] {
return rootNodes;
}
//
//
// CONFIGS
//
//
override create_shader_configs() {
return [new JsShaderConfig(JsFunctionName.MAIN, [], [])];
}
override create_variable_configs() {
return [];
}
}