UNPKG

polygonjs-engine

Version:

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

284 lines (256 loc) 8.15 kB
import {BaseNodeType} from '../engine/nodes/_Base'; import {BaseParamType} from '../engine/params/_Base'; import {DecomposedPath} from './DecomposedPath'; import {NodeContext, BaseNodeByContextMap} from '../engine/poly/NodeContext'; import {ErrorState} from '../engine/nodes/utils/states/Error'; type NodeOrParam = BaseNodeType | BaseParamType; export const NODE_PATH_DEFAULT = { NODE: { UV: '/COP/imageUv', ENV_MAP: '/COP/envMap', }, }; export class TypedNodePathParamValue { private _node: BaseNodeType | null = null; constructor(private _path: string = '') {} set_path(path: string) { this._path = path; } set_node(node: BaseNodeType | null) { this._node = node; } path() { return this._path; } node() { return this._node; } resolve(node_start: BaseNodeType) { this._node = CoreWalker.find_node(node_start, this._path); } clone() { const cloned = new TypedNodePathParamValue(this._path); cloned.set_node(this._node); return cloned; } ensure_node_context<N extends NodeContext>( context: N, error_state?: ErrorState ): BaseNodeByContextMap[N] | undefined { const found_node = this.node(); if (!found_node) { error_state?.set(`no node found at ${this.path()}`); return; } const node_context = found_node.nodeContext(); if (node_context == context) { return found_node as BaseNodeByContextMap[N]; } else { error_state?.set(`expected ${context} node, but got a ${node_context}`); return; } } } export class TypedParamPathParamValue { private _param: BaseParamType | null = null; constructor(private _path: string = '') {} set_path(path: string) { this._path = path; } set_param(param: BaseParamType | null) { this._param = param; } path() { return this._path; } param() { return this._param; } resolve(node_start: BaseNodeType) { this._param = CoreWalker.find_param(node_start, this._path); } clone() { const cloned = new TypedParamPathParamValue(this._path); cloned.set_param(this._param); return cloned; } } export class CoreWalker { public static readonly SEPARATOR = '/'; public static readonly DOT = '.'; public static readonly CURRENT = CoreWalker.DOT; public static readonly PARENT = '..'; public static readonly CURRENT_WITH_SLASH = `${CoreWalker.CURRENT}/`; public static readonly PARENT_WITH_SLASH = `${CoreWalker.PARENT}/`; public static readonly NON_LETTER_PREFIXES = [CoreWalker.SEPARATOR, CoreWalker.DOT]; static split_parent_child(path: string) { const elements: string[] = path.split(CoreWalker.SEPARATOR).filter((e) => e.length > 0); const child_path = elements.pop(); const parent_path = elements.join(CoreWalker.SEPARATOR); return {parent: parent_path, child: child_path}; } static find_node(node_src: BaseNodeType, path: string, decomposed_path?: DecomposedPath): BaseNodeType | null { if (!node_src) { return null; } const elements: string[] = path.split(CoreWalker.SEPARATOR).filter((e) => e.length > 0); const first_element = elements[0]; let next_node: BaseNodeType | null = null; if (path[0] === CoreWalker.SEPARATOR) { const path_from_root = path.substr(1); next_node = this.find_node(node_src.root(), path_from_root, decomposed_path); } else { switch (first_element) { case CoreWalker.PARENT: decomposed_path?.add_path_element(first_element); next_node = node_src.parent(); break; case CoreWalker.CURRENT: decomposed_path?.add_path_element(first_element); next_node = node_src; break; default: // TODO: What does .node means?? in which case is this not a node? (it is for nodes which cannot have children - but I'd like to unify the api) // console.error("rethink this method Walker.find_node") // if (node_src.node != null) { next_node = node_src.node(first_element); if (next_node) { decomposed_path?.add_node(first_element, next_node); } // if (next_node == null) { this.find_node_warning(node_src, first_element); } // return next_node; // break // } } if (next_node != null && elements.length > 1) { const remainder = elements.slice(1).join(CoreWalker.SEPARATOR); next_node = this.find_node(next_node, remainder, decomposed_path); } return next_node; } return next_node; } static find_param(node_src: BaseNodeType, path: string, decomposed_path?: DecomposedPath): BaseParamType | null { if (!node_src) { return null; } const elements = path.split(CoreWalker.SEPARATOR); if (elements.length === 1) { return node_src.params.get(elements[0]); } else { const node_path = elements.slice(0, +(elements.length - 2) + 1 || undefined).join(CoreWalker.SEPARATOR); const node = this.find_node(node_src, node_path, decomposed_path); if (node != null) { const param_name = elements[elements.length - 1]; const param = node.params.get(param_name); if (decomposed_path && param) { decomposed_path.add_node(param_name, param); } return param; } else { return null; // throw `no node found for path ${node_path}`; } } } static relative_path(src_graph_node: Readonly<NodeOrParam>, dest_graph_node: Readonly<NodeOrParam>): string { const parent = this.closest_common_parent(src_graph_node, dest_graph_node); if (!parent) { return dest_graph_node.fullPath(); } else { const distance = this.distance_to_parent(src_graph_node, parent); let up = ''; if (distance - 1 > 0) { let i = 0; const ups = []; while (i++ < distance - 1) { ups.push(CoreWalker.PARENT); } up = ups.join(CoreWalker.SEPARATOR) + CoreWalker.SEPARATOR; } const parent_path_elements = parent .fullPath() .split(CoreWalker.SEPARATOR) .filter((e) => e.length > 0); const dest_path_elements = dest_graph_node .fullPath() .split(CoreWalker.SEPARATOR) .filter((e) => e.length > 0); const remaining_elements = []; let cmptr = 0; for (let dest_path_element of dest_path_elements) { if (!parent_path_elements[cmptr]) { remaining_elements.push(dest_path_element); } cmptr++; } const down = remaining_elements.join(CoreWalker.SEPARATOR); return `${up}${down}`; } } static closest_common_parent( graph_node1: Readonly<NodeOrParam>, graph_node2: Readonly<NodeOrParam> ): BaseNodeType | null { const parents1 = this.parents(graph_node1).reverse(); const parents2 = this.parents(graph_node2).reverse(); const min_depth = Math.min(parents1.length, parents2.length); let found_parent = null; for (let i = 0; i < min_depth; i++) { if (parents1[i].graphNodeId() == parents2[i].graphNodeId()) { found_parent = parents1[i]; } } return found_parent; } static parents(graph_node: Readonly<NodeOrParam>): BaseNodeType[] { const parents = []; let parent = graph_node.parent(); while (parent) { parents.push(parent); parent = parent.parent(); } return parents; } static distance_to_parent(graph_node: Readonly<NodeOrParam>, dest: Readonly<BaseNodeType>): number { let distance = 0; let current: Readonly<NodeOrParam | null> = graph_node; const dest_id = dest.graphNodeId(); while (current && current.graphNodeId() != dest_id) { distance += 1; current = current.parent(); } if (current && current.graphNodeId() == dest_id) { return distance; } else { return -1; } } static make_absolute_path(node_src: BaseNodeType | BaseParamType, path: string): string | null { if (path[0] == CoreWalker.SEPARATOR) { return path; } const path_elements = path.split(CoreWalker.SEPARATOR); const first_element = path_elements.shift(); if (first_element) { switch (first_element) { case '..': { const parent = node_src.parent(); if (parent) { return this.make_absolute_path(parent, path_elements.join(CoreWalker.SEPARATOR)); } else { return null; } } case '.': { return this.make_absolute_path(node_src, path_elements.join(CoreWalker.SEPARATOR)); } default: { return [node_src.fullPath(), path].join(CoreWalker.SEPARATOR); } } } else { return node_src.fullPath(); } } }