@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
321 lines (290 loc) • 11 kB
text/typescript
import {BaseGlNodeType} from '../../_Base';
import {TypedNodeTraverser} from '../../../utils/shaders/NodeTraverser';
import {pushOnArrayAtEntry} from '../../../../../core/MapUtils';
import {ShaderName} from '../../../utils/shaders/ShaderName';
import {GLDefinitionType, BaseGLDefinition} from '../../utils/GLDefinition';
import {TypedGLDefinitionCollection} from '../../utils/GLDefinitionCollection';
import {ParamConfigsController} from '../../../../nodes/utils/code/controllers/ParamConfigsController';
import {ShadersCollectionController} from './ShadersCollectionController';
import {CodeFormatter} from './CodeFormatter';
import {LineType} from './LineType';
import {GlParamConfig} from './GLParamConfig';
import {ParamType} from '../../../../poly/ParamType';
import {NodeContext} from '../../../../poly/NodeContext';
import {CoreGraphNodeId} from '../../../../../core/graph/CoreGraph';
import {arrayUniq} from '../../../../../core/ArrayUtils';
import {TypedAssembler} from '../../../utils/shaders/BaseAssembler';
type RootNodesForShaderMethod = (shader_name: ShaderName, rootNodes: BaseGlNodeType[]) => BaseGlNodeType[];
// let nextId = 1;
export interface CodeBuilderSetCodeLinesOptions {
otherFragmentShaderCollectionController?: ShadersCollectionController;
}
export class CodeBuilder {
// private _id = (nextId += 1);
private _param_configs_controller: ParamConfigsController<GlParamConfig<ParamType>> = new ParamConfigsController();
private _param_configs_set_allowed: boolean = true;
private _shadersCollectionController: ShadersCollectionController | undefined;
private _lines: Map<ShaderName, Map<LineType, string[]>> = new Map();
// _function_declared: Map<ShaderName, Map<string, boolean>> = new Map();
constructor(
private _nodeTraverser: TypedNodeTraverser<NodeContext.GL>,
private _root_nodes_for_shader_method: RootNodesForShaderMethod,
private _assembler: TypedAssembler<NodeContext.GL>
) {}
nodeTraverser() {
return this._nodeTraverser;
}
shaderNames() {
return this._nodeTraverser.shaderNames();
}
buildFromNodes(
rootNodes: BaseGlNodeType[],
paramNodes: BaseGlNodeType[],
setCodeLinesOptions?: CodeBuilderSetCodeLinesOptions
) {
this._nodeTraverser.traverse(rootNodes);
const nodesByShaderName: Map<ShaderName, BaseGlNodeType[]> = new Map();
for (const shaderName of this.shaderNames()) {
const nodes = this._nodeTraverser.nodesForShaderName(shaderName);
nodesByShaderName.set(shaderName, nodes);
}
const sortedNodes = this._nodeTraverser.sortedNodes();
for (const shaderName of this.shaderNames()) {
const rootNodesForShader = this._root_nodes_for_shader_method(shaderName, rootNodes);
for (const rootNode of rootNodesForShader) {
pushOnArrayAtEntry(nodesByShaderName, shaderName, rootNode);
}
}
// ensure nodes are not added if already present
const sorted_node_ids: Map<CoreGraphNodeId, boolean> = new Map();
for (const node of sortedNodes) {
sorted_node_ids.set(node.graphNodeId(), true);
}
for (const rootNode of rootNodes) {
if (!sorted_node_ids.get(rootNode.graphNodeId())) {
sortedNodes.push(rootNode);
sorted_node_ids.set(rootNode.graphNodeId(), true);
}
}
for (const node of sortedNodes) {
node.reset_code();
}
for (const node of paramNodes) {
node.reset_code();
}
// for (let node of sorted_nodes) {
// await node.params.eval_all();
// }
// const param_promises = sorted_nodes.map(node=>{
// return node.eval_all_params()
// })
// await Promise.all(param_promises)
this._shadersCollectionController = new ShadersCollectionController(
this.shaderNames(),
this.shaderNames()[0],
this._assembler
);
this.reset();
for (const shaderName of this.shaderNames()) {
const nodes: BaseGlNodeType[] = [];
const nonUniqNodes = nodesByShaderName.get(shaderName);
if (nonUniqNodes) {
arrayUniq(nonUniqNodes, nodes);
}
this._shadersCollectionController.setCurrentShaderName(shaderName);
if (nodes) {
for (const node of nodes) {
node.setLines(this._shadersCollectionController);
}
}
}
// set param configs
if (this._param_configs_set_allowed) {
for (const param_node of paramNodes) {
try {
param_node.states.error.clear();
param_node.setParamConfigs();
} catch (err) {
const message: string = (err as any).message || 'failed to create spare param';
param_node.states.error.set(message);
throw new Error(`${param_node.name()} cannot create spare parameter`);
}
}
this.setParamConfigs(paramNodes);
}
// finalize
this._setCodeLines(sortedNodes, setCodeLinesOptions);
}
shadersCollectionController() {
return this._shadersCollectionController;
}
disallow_new_param_configs() {
this._param_configs_set_allowed = false;
}
allow_new_param_configs() {
this._param_configs_set_allowed = true;
}
private reset() {
for (const shader_name of this.shaderNames()) {
const lines_map = new Map();
// for (let line_type of LINE_TYPES) {
// lines_map.set(line_type, []);
// }
this._lines.set(shader_name, lines_map);
// this._function_declared.set(shader_name, new Map());
}
}
param_configs() {
return this._param_configs_controller.list() || [];
}
lines(shader_name: ShaderName, line_type: LineType) {
return this._lines.get(shader_name)?.get(line_type) || [];
}
all_lines() {
return this._lines;
}
setParamConfigs(nodes: BaseGlNodeType[]) {
this._param_configs_controller.reset();
for (const node of nodes) {
const param_configs = node.param_configs();
if (param_configs) {
for (const param_config of param_configs) {
this._param_configs_controller.push(param_config);
}
}
}
}
private _setCodeLines(nodes: BaseGlNodeType[], options?: CodeBuilderSetCodeLinesOptions) {
for (const shaderName of this.shaderNames()) {
const additionalDefinitions: BaseGLDefinition[] = [];
if (shaderName == ShaderName.FRAGMENT) {
if (this._shadersCollectionController && options && options.otherFragmentShaderCollectionController) {
// for (let shaderName of options.otherShadersCollectionController.shaderNames()) {
// this._linesControllerByShaderName.set(shaderName, new LinesController(shaderName));
options.otherFragmentShaderCollectionController.traverseDefinitions(
ShaderName.FRAGMENT,
(definition: BaseGLDefinition) => {
additionalDefinitions.push(definition);
}
);
// }
}
}
this._addCodeLines(nodes, shaderName, additionalDefinitions);
}
}
private _addCodeLines(nodes: BaseGlNodeType[], shaderName: ShaderName, additionalDefinitions?: BaseGLDefinition[]) {
this.addDefinitions(nodes, shaderName, GLDefinitionType.PRECISION, LineType.DEFINE, additionalDefinitions);
this.addDefinitions(
nodes,
shaderName,
GLDefinitionType.FUNCTION,
LineType.FUNCTION_DECLARATION,
additionalDefinitions
);
this.addDefinitions(nodes, shaderName, GLDefinitionType.UNIFORM, LineType.DEFINE, additionalDefinitions);
this.addDefinitions(nodes, shaderName, GLDefinitionType.VARYING, LineType.DEFINE, additionalDefinitions);
this.addDefinitions(nodes, shaderName, GLDefinitionType.ATTRIBUTE, LineType.DEFINE, additionalDefinitions);
this.add_code_line_for_nodes_and_line_type(nodes, shaderName, LineType.BODY);
}
private addDefinitions(
nodes: BaseGlNodeType[],
shaderName: ShaderName,
definitionType: GLDefinitionType,
lineType: LineType,
additionalDefinitions?: BaseGLDefinition[]
) {
if (!this._shadersCollectionController) {
return;
}
const definitions: BaseGLDefinition[] = [];
for (const node of nodes) {
let nodeDefinitions = this._shadersCollectionController.definitions(shaderName, node);
if (nodeDefinitions) {
nodeDefinitions = nodeDefinitions.filter((d) => d.definition_type == definitionType);
for (const definition of nodeDefinitions) {
definitions.push(definition);
}
}
}
if (additionalDefinitions) {
const filteredAdditionalDefinitions = additionalDefinitions.filter(
(d) => d.definition_type == definitionType
);
for (const definition of filteredAdditionalDefinitions) {
definitions.push(definition);
}
}
if (definitions.length > 0) {
const collection = new TypedGLDefinitionCollection<GLDefinitionType>(definitions);
const uniqDefinitions = collection.uniq();
if (collection.errored) {
// TODO: handle error
throw `code builder error: ${collection.error_message}`;
}
const definitions_by_node_id: Map<CoreGraphNodeId, BaseGLDefinition[]> = new Map();
const nodeIds: Map<CoreGraphNodeId, boolean> = new Map();
for (const definition of uniqDefinitions) {
const nodeId = definition.node.graphNodeId();
if (!nodeIds.has(nodeId)) {
nodeIds.set(nodeId, true);
}
pushOnArrayAtEntry(definitions_by_node_id, nodeId, definition);
}
const lines_for_shader = this._lines.get(shaderName)!;
nodeIds.forEach((_, nodeId) => {
const definitions = definitions_by_node_id.get(nodeId);
if (definitions) {
const first_definition = definitions[0];
if (first_definition) {
const comment = CodeFormatter.nodeComment(first_definition.node, lineType);
pushOnArrayAtEntry(lines_for_shader, lineType, comment);
for (const definition of definitions) {
const line = CodeFormatter.lineWrap(first_definition.node, definition.line, lineType);
pushOnArrayAtEntry(lines_for_shader, lineType, line);
}
const separator = CodeFormatter.post_line_separator(lineType);
pushOnArrayAtEntry(lines_for_shader, lineType, separator);
}
}
});
}
}
add_code_line_for_nodes_and_line_type(nodes: BaseGlNodeType[], shader_name: ShaderName, line_type: LineType) {
nodes = nodes.filter((node) => {
if (this._shadersCollectionController) {
const lines = this._shadersCollectionController.bodyLines(shader_name, node);
return lines && lines.length > 0;
}
});
var nodes_count = nodes.length;
for (let i = 0; i < nodes_count; i++) {
const is_last = i == nodes.length - 1;
this.add_code_line_for_node_and_line_type(nodes[i], shader_name, line_type, is_last);
}
}
add_code_line_for_node_and_line_type(
node: BaseGlNodeType,
shader_name: ShaderName,
line_type: LineType,
is_last: boolean
): void {
if (!this._shadersCollectionController) {
return;
}
const lines = this._shadersCollectionController.bodyLines(shader_name, node);
if (lines && lines.length > 0) {
const lines_for_shader = this._lines.get(shader_name)!;
const comment = CodeFormatter.nodeComment(node, line_type);
pushOnArrayAtEntry(lines_for_shader, line_type, comment);
lines.forEach((line) => {
line = CodeFormatter.lineWrap(node, line, line_type);
pushOnArrayAtEntry(lines_for_shader, line_type, line);
});
if (!(line_type == LineType.BODY && is_last)) {
const separator = CodeFormatter.post_line_separator(line_type);
pushOnArrayAtEntry(lines_for_shader, line_type, separator);
}
}
}
}