UNPKG

@polygonjs/polygonjs

Version:

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

317 lines (290 loc) 9.41 kB
import {Constructor} from './../types/GlobalTypes'; import {CoreGraphNode} from './graph/CoreGraphNode'; import {BaseNodeType} from '../engine/nodes/_Base'; import {BaseParamType} from '../engine/params/_Base'; import {DecomposedPath} from './DecomposedPath'; import {NodeContext, BaseNodeByContextMap} from '../engine/poly/NodeContext'; import {NodeErrorState} from '../engine/nodes/utils/states/Error'; import {ParamType} from '../engine/poly/ParamType'; import {ParamErrorState} from '../engine/params/utils/states/Error'; import {ParamConstructorMap} from '../engine/params/types/ParamConstructorMap'; type NodeOrParam = BaseNodeType | BaseParamType; export const NODE_PATH_DEFAULT = { NODE: { EMPTY: '', UV: '/COP/imageUv', ENV_MAP: '/COP/envMap', CUBE_MAP: '/COP/cubeCamera', }, }; const _remainingElements: string[] = []; const _ups: string[] = []; abstract class GraphNodePathParamValue<T extends CoreGraphNode> { protected _graphNode: T | null = null; constructor(protected _path: string = '') {} graphNode() { return this._graphNode; } private _setGraphNode(graphNode: T | null) { this._graphNode = graphNode; } abstract graphNodePath(): string | undefined; path() { return this._path; } setPath(path: string) { this._path = path; } clone(): this { const cloned = new (this.constructor as Constructor<GraphNodePathParamValue<T>>)(this._path); cloned._setGraphNode(this._graphNode); return cloned as this; } } export class TypedNodePathParamValue extends GraphNodePathParamValue<BaseNodeType> { setNode(node: BaseNodeType | null) { this._graphNode = node; } node() { return this._graphNode; } graphNodePath() { return this.node()?.path(); } resolve(nodeStart: BaseNodeType, decomposedPath?: DecomposedPath) { this._graphNode = CoreWalker.findNode(nodeStart, this._path, decomposedPath); } nodeWithContext<N extends NodeContext, K extends NodeContext>( context: N, errorState?: NodeErrorState<K> ): BaseNodeByContextMap[N] | undefined { const foundNode = this.node(); if (!foundNode) { errorState?.set(`no node found at ${this.path()}`); return; } const nodeContext = foundNode.context(); if (nodeContext == context) { return foundNode as BaseNodeByContextMap[N]; } else { errorState?.set(`expected ${context} node, but got a ${nodeContext}`); return; } } } export class TypedParamPathParamValue extends GraphNodePathParamValue<BaseParamType> { setParam(param: BaseParamType | null) { this._graphNode = param; } param() { return this._graphNode; } graphNodePath() { return this.param()?.path(); } resolve(nodeStart: BaseNodeType, decomposedPath?: DecomposedPath) { this._graphNode = CoreWalker.findParam(nodeStart, this._path, decomposedPath); } paramWithType<T extends ParamType>( paramType: T, error_state?: ParamErrorState ): ParamConstructorMap[T] | undefined { const foundParam = this.param(); if (!foundParam) { error_state?.set(`no param found at ${this.path()}`); return; } if (foundParam.type() == paramType) { return foundParam as ParamConstructorMap[T]; } else { error_state?.set(`expected ${paramType} node, but got a ${foundParam.type()}`); return; } } } 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 splitParentChild(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 findNode(nodeSrc: BaseNodeType, path: string, decomposedPath?: DecomposedPath): BaseNodeType | null { if (!nodeSrc) { return null; } const elements: string[] = path.split(CoreWalker.SEPARATOR).filter((e) => e.length > 0); const firstElement = elements[0]; let nextNode: BaseNodeType | null = null; if (path[0] === CoreWalker.SEPARATOR) { const pathFromRoot = path.substring(1); nextNode = this.findNode(nodeSrc.root(), pathFromRoot, decomposedPath); } else { switch (firstElement) { case CoreWalker.PARENT: nextNode = nodeSrc.parent(); if (nextNode) { decomposedPath?.addPathElement({path: firstElement, node: nextNode}); } break; case CoreWalker.CURRENT: nextNode = nodeSrc; decomposedPath?.addPathElement({path: firstElement, node: nextNode}); break; default: nextNode = nodeSrc.node(firstElement); if (nextNode) { decomposedPath?.addNamedNode({name: firstElement, node: nextNode}); } } if (nextNode != null && elements.length > 1) { const remainder = elements.slice(1).join(CoreWalker.SEPARATOR); nextNode = this.findNode(nextNode, remainder, decomposedPath); } return nextNode; } return nextNode; } static findParam(nodeSrc: BaseNodeType, path: string, decomposedPath?: DecomposedPath): BaseParamType | null { if (!nodeSrc) { return null; } const elements = path.split(CoreWalker.SEPARATOR); if (elements.length === 1) { return nodeSrc.params.get(elements[0]); } else { let node: BaseNodeType | null = null; if (path[0] === CoreWalker.SEPARATOR && elements.length == 2) { node = nodeSrc.root(); } else { const nodePath = elements.slice(0, +(elements.length - 2) + 1 || undefined).join(CoreWalker.SEPARATOR); node = this.findNode(nodeSrc, nodePath, decomposedPath); } if (node != null) { const paramName = elements[elements.length - 1]; const param = node.params.get(paramName); if (decomposedPath && param) { decomposedPath.addNamedNode({name: paramName, node: param}); } return param; } else { return null; // throw `no node found for path ${node_path}`; } } } static relativePath(srcGraphNode: Readonly<BaseNodeType>, destGraphNode: Readonly<BaseNodeType>): string { const parent = this.closestCommonParent(srcGraphNode, destGraphNode); if (!parent) { return destGraphNode.path(); } else { const distance = this.distanceToParent(srcGraphNode, parent); let up = ''; if (distance > 0) { let i = 0; _ups.length = 0; while (i++ < distance) { _ups.push(CoreWalker.PARENT); } up = _ups.join(CoreWalker.SEPARATOR) + CoreWalker.SEPARATOR; } const parent_path_elements = parent .path() .split(CoreWalker.SEPARATOR) .filter((e) => e.length > 0); const dest_path_elements = destGraphNode .path() .split(CoreWalker.SEPARATOR) .filter((e) => e.length > 0); _remainingElements.length = 0; let cmptr = 0; for (const dest_path_element of dest_path_elements) { if (!parent_path_elements[cmptr]) { _remainingElements.push(dest_path_element); } cmptr++; } const down = _remainingElements.join(CoreWalker.SEPARATOR); return this.sanitizePath(`${up}${down}`); } } static sanitizePath(path: string) { return path.replace(/\/\//g, '/'); } static closestCommonParent( graphNode1: Readonly<BaseNodeType>, graphNode2: Readonly<BaseNodeType> ): Readonly<BaseNodeType> | null { const parents1 = this.parents(graphNode1).reverse().concat([graphNode1]); const parents2 = this.parents(graphNode2).reverse().concat([graphNode2]); const minDepth = Math.min(parents1.length, parents2.length); let foundParent = null; for (let i = 0; i < minDepth; i++) { if (parents1[i].graphNodeId() == parents2[i].graphNodeId()) { foundParent = parents1[i]; } } return foundParent; } static parents(graphNode: Readonly<NodeOrParam>): Readonly<BaseNodeType>[] { const parents = []; let parent = graphNode.parent(); while (parent) { parents.push(parent); parent = parent.parent(); } return parents; } static distanceToParent(graphNode: Readonly<NodeOrParam>, dest: Readonly<BaseNodeType>): number { let distance = 0; let current: Readonly<NodeOrParam | null> = graphNode; const destId = dest.graphNodeId(); while (current && current.graphNodeId() != destId) { distance += 1; current = current.parent(); } if (current && current.graphNodeId() == destId) { return distance; } else { return -1; } } static makeAbsolutePath(nodeSrc: BaseNodeType | BaseParamType, path: string): string | null { if (path[0] == CoreWalker.SEPARATOR) { return path; } const pathElements = path.split(CoreWalker.SEPARATOR); const firstElement = pathElements.shift(); if (firstElement) { switch (firstElement) { case '..': { const parent = nodeSrc.parent(); if (parent) { if (parent == nodeSrc.scene().root()) { return CoreWalker.SEPARATOR + pathElements.join(CoreWalker.SEPARATOR); } else { return this.makeAbsolutePath(parent, pathElements.join(CoreWalker.SEPARATOR)); } } else { return null; } } case '.': { return this.makeAbsolutePath(nodeSrc, pathElements.join(CoreWalker.SEPARATOR)); } default: { return [nodeSrc.path(), path].join(CoreWalker.SEPARATOR); } } } else { return nodeSrc.path(); } } }