polygonjs-engine
Version:
node-based webgl 3D engine https://polygonjs.com
284 lines (256 loc) • 8.15 kB
text/typescript
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();
}
}
}