@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
448 lines (407 loc) • 14.4 kB
text/typescript
import {TypedNode} from '../../../nodes/_Base';
import {Vector2} from 'three';
import type {JsonImportDispatcher} from './Dispatcher';
import {ParamType} from '../../../poly/ParamType';
import {ParamsUpdateOptions} from '../../../nodes/utils/params/ParamsController';
import {SceneJsonImporter} from '../../../io/json/import/Scene';
import {NodeContext} from '../../../poly/NodeContext';
import {NodeJsonExporterData, NodeJsonExporterUIData, InputData, IoConnectionPointsData} from '../export/Node';
import {
ParamJsonExporterData,
SimpleParamJsonExporterData,
ComplexParamJsonExporterData,
} from '../../../nodes/utils/io/IOController';
import type {NodesJsonImporter} from './Nodes';
import {Poly} from '../../../Poly';
import { isString,isNumber,isArray,isBoolean,isObject} from '../../../../core/Type';
// import {CoreString} from '../../../../core/String';
import {PolyDictionary} from '../../../../types/GlobalTypes';
import {VelocityColliderFunctionBody} from '../../../nodes/js/code/assemblers/_Base';
const COMPLEX_PARAM_DATA_KEYS: Readonly<string[]> = ['overriden_options', 'type'];
type BaseNodeTypeWithIO = TypedNode<NodeContext, any>;
export class NodeJsonImporter<T extends BaseNodeTypeWithIO> {
constructor(
protected _node: T,
private dispatcher: JsonImportDispatcher,
protected nodesImporter: NodesJsonImporter<any>
) {}
process_data(scene_importer: SceneJsonImporter, data: NodeJsonExporterData) {
if (this._node.sceneReadonly()) {
scene_importer.report.markAsLoadedWithoutAssemblers();
}
this.set_connection_points(data['connection_points']);
// rather than having the children creation dependent on the persisted config and player mode, use the childrenAllowed() method
// const skip_create_children = Poly.playerMode() && data.persisted_config;
if (this._node.childrenAllowed()) {
this.create_nodes(scene_importer, data['nodes'], data);
}
// inputs clone
if (this._node.io.inputs.overrideClonedStateAllowed()) {
const override = data['cloned_state_overriden'];
if (override) {
this._node.io.inputs.overrideClonedState(override);
}
}
this.set_flags(data);
// params
// const spare_params_data = ParamJsonImporter.spare_params_data(data['params']);
// this.set_params(spare_params_data);
this.set_params(data);
if (data.persisted_config) {
const shadersData = scene_importer.shadersData();
const jsFunctionBodiesData = scene_importer.jsFunctionBodiesData();
if (shadersData) {
let shaders = shadersData[this._node.path()];
// make sure this is never undefined
if (!shaders) {
shaders = {};
}
data.persisted_config.shaders = shaders;
}
if (jsFunctionBodiesData) {
const jsFunctionBodyData = jsFunctionBodiesData[this._node.path()];
if (jsFunctionBodyData) {
if (isString(jsFunctionBodyData)) {
data.persisted_config.functionBody = jsFunctionBodyData;
} else {
data.persisted_config.functionBody = jsFunctionBodyData as any as VelocityColliderFunctionBody;
}
}
}
this.set_persisted_config(data.persisted_config);
}
this.setCustomData(data);
// already called in create_node()
// this._node.lifecycle.set_creation_completed();
}
process_inputs_data(scene_importer: SceneJsonImporter, data: NodeJsonExporterData) {
const maxInputsCount = data.maxInputsCount;
if (maxInputsCount != null) {
const minCount = this._node.io.inputs.minCount();
this._node.io.inputs.setCount(minCount, maxInputsCount);
}
try {
this.setInputs(data['inputs']);
} catch (err) {
const message = (err as ErrorEvent).message || `failed connecting inputs of node ${data['type']}`;
scene_importer.report.addWarning(message);
console.warn(data['inputs']);
}
}
process_ui_data(scene_importer: SceneJsonImporter, data: NodeJsonExporterUIData) {
if (!data) {
return;
}
if (Poly.playerMode()) {
return;
}
const ui_data = this._node.uiData;
const pos = data['pos'];
if (pos) {
const vector = new Vector2().fromArray(pos);
ui_data.setPosition(vector);
}
const comment = data['comment'];
if (comment) {
ui_data.setComment(comment);
}
const selection = data['selection'];
if (selection) {
this.set_selection(selection);
}
if (this._node.childrenAllowed()) {
const nodesData = data['nodes'];
if (nodesData) {
this.processNodesUiData(scene_importer, nodesData);
}
}
}
create_nodes(
scene_importer: SceneJsonImporter,
data: PolyDictionary<NodeJsonExporterData> | undefined,
nodeData: NodeJsonExporterData
) {
if (!data) {
return;
}
// const nodes_importer = new NodesJsonImporter(this._node);
this.nodesImporter.process_data(scene_importer, data);
}
set_selection(data?: string[]) {
if (this._node.childrenAllowed() && this._node.childrenController) {
if (data && data.length > 0) {
const selected_nodes: BaseNodeTypeWithIO[] = [];
data.forEach((node_name) => {
const node = this._node.node(node_name);
if (node) {
selected_nodes.push(node);
}
});
this._node.childrenController.selection.set(selected_nodes);
}
}
}
set_flags(data: NodeJsonExporterData) {
const flags = data['flags'];
if (flags) {
const bypass = flags['bypass'];
if (bypass != null) {
this._node.flags?.bypass?.set(bypass);
}
const display = flags['display'];
if (display != null) {
this._node.flags?.display?.set(display);
}
const optimize = flags['optimize'];
if (optimize != null) {
this._node.flags?.optimize?.set(optimize);
}
}
}
set_connection_points(connection_points_data: IoConnectionPointsData | undefined) {
if (!connection_points_data) {
return;
}
if (connection_points_data['in']) {
this._node.io.saved_connection_points_data.set_in(connection_points_data['in']);
}
if (connection_points_data['out']) {
this._node.io.saved_connection_points_data.set_out(connection_points_data['out']);
}
if (this._node.io.has_connection_points_controller) {
this._node.io.connection_points.update_signature_if_required();
}
}
private setInputs(inputsData?: InputData[]) {
if (!inputsData) {
return;
}
let inputData: InputData;
for (let i = 0; i < inputsData.length; i++) {
inputData = inputsData[i];
if (inputData && this._node.parent()) {
if (isString(inputData)) {
const inputNodeName = inputData;
const inputNode = this._node.nodeSibling(inputNodeName);
this._node.setInput(i, inputNode);
} else {
const inputNode = this._node.nodeSibling(inputData['node']);
let inputIndex = inputData['index'];
const inputName = inputData['inputName'];
let outputName = inputData['output'];
if (inputName != null) {
// If we have inputName, try and find the input index matching it.
// If we find nothing, we use inputIndex
const connectionPoints = this._node.io.inputs.namedInputConnectionPoints();
if (connectionPoints) {
const connectionPointIndex = connectionPoints
.map((point) => point?.name().toLowerCase())
.indexOf(inputName.toLowerCase());
if (connectionPointIndex >= 0) {
inputIndex = connectionPointIndex;
}
}
}
if (inputNode != null && outputName != null) {
// If we have outtputName, try and find the input index matching it.
const connectionPoints = inputNode.io.outputs.namedOutputConnectionPoints();
if (connectionPoints) {
for (let connectionPoint of connectionPoints) {
if (connectionPoint) {
if (connectionPoint.name().toLowerCase() == outputName.toLowerCase()) {
outputName = connectionPoint.name();
}
}
}
}
}
if (inputIndex != null) {
this._node.setInput(inputIndex, inputNode, outputName);
}
}
}
}
}
processNodesUiData(scene_importer: SceneJsonImporter, data: PolyDictionary<NodeJsonExporterUIData>) {
if (!data) {
return;
}
if (Poly.playerMode()) {
return;
}
const node_names = Object.keys(data);
for (let node_name of node_names) {
const node = this._node.node(node_name);
if (node) {
const node_data = data[node_name];
this.dispatcher.dispatchNode(node).process_ui_data(scene_importer, node_data);
// node.visit(JsonImporterVisitor).process_ui_data(node_data);
}
}
}
//
//
// PARAMS
//
//
set_params(nodeData: NodeJsonExporterData) {
const data = nodeData['params'];
if (!data) {
return;
}
const param_names = Object.keys(data);
const params_update_options: ParamsUpdateOptions = {};
for (let param_name of param_names) {
const param_data = data[param_name] as ComplexParamJsonExporterData<ParamType>;
if (param_data != null) {
const options = param_data['options'];
// const is_spare = options && options['spare'] === true;
// make camelCase if required
// if (false && param_name.includes('_')) {
// param_name = CoreString.camelCase(param_name);
// }
let param_type = param_data['type']!;
const has_param = this._node.params.has_param(param_name);
let has_param_and_same_type = false;
let param;
if (has_param) {
param = this._node.params.get(param_name);
// we can safely consider same type if param_type is not mentioned
if ((param && param.type() == param_type) || param_type == null) {
has_param_and_same_type = true;
}
}
if (has_param_and_same_type) {
if (this._is_param_data_complex(param_data)) {
this._process_param_data_complex(param_name, param_data);
} else {
this._process_param_data_simple(
param_name,
param_data as SimpleParamJsonExporterData<ParamType>
);
}
} else {
// it the param is a spare one,
// we check if it is currently exists with same type first.
// - if it is, we only update the value
// - if it's not, we delete it and add it again
params_update_options.namesToDelete = params_update_options.namesToDelete || [];
params_update_options.namesToDelete.push(param_name);
params_update_options.toAdd = params_update_options.toAdd || [];
params_update_options.toAdd.push({
name: param_name,
type: param_type,
initValue: param_data['default_value'] as any,
rawInput: param_data['raw_input'] as any,
options: options,
});
// if (options && param_type) {
// if (param_data['default_value']) {
// if (has_param) {
// this._node.params.delete_param(param_name);
// }
// param = this._node.add_param(param_type, param_name, param_data['default_value'], options);
// if (param) {
// JsonImportDispatcher.dispatch_param(param).process_data(param_data);
// }
// }
// }
}
}
}
// delete and create the spare params we need to
const params_delete_required =
params_update_options.namesToDelete && params_update_options.namesToDelete.length > 0;
const params_add_required = params_update_options.toAdd && params_update_options.toAdd.length > 0;
if (params_delete_required || params_add_required) {
this._node.params.updateParams(params_update_options);
// update them based on the imported data
for (let spare_param of this._node.params.spare) {
const param_data = data[spare_param.name()] as ComplexParamJsonExporterData<ParamType>;
// JsonImportDispatcher.dispatch_param(spare_param).process_data(param_data);
if (!spare_param.parentParam() && param_data) {
if (this._is_param_data_complex(param_data)) {
this._process_param_data_complex(spare_param.name(), param_data);
} else {
this._process_param_data_simple(
spare_param.name(),
param_data as SimpleParamJsonExporterData<ParamType>
);
}
}
}
}
// those hooks are useful for some gl nodes,
// such as the constant, which needs to update its connections
// based on another parameter, which will be set just before
this._node.params.runOnSceneLoadHooks();
}
private _process_param_data_simple(param_name: string, param_data: SimpleParamJsonExporterData<ParamType>) {
this._node.params.get(param_name)?.set(param_data);
}
private _process_param_data_complex(param_name: string, param_data: ComplexParamJsonExporterData<ParamType>) {
const param = this._node.params.get(param_name);
if (param) {
this.dispatcher.dispatchParam(param).process_data(param_data);
}
// return
// const has_param = this._node.params.has_param(param_name);
// const param_type = param_data['type']!;
// let has_param_and_same_type = false;
// let param;
// if (has_param) {
// param = this._node.params.get(param_name);
// // we can safely consider same type if param_type is not mentioned
// if ((param && param.type == param_type) || param_type == null) {
// has_param_and_same_type = true;
// }
// }
// if (has_param_and_same_type) {
// param = this._node.params.get(param_name);
// if (param) {
// JsonImportDispatcher.dispatch_param(param).process_data(param_data);
// // param.visit(JsonImporterVisitor).process_data(param_data);
// }
// } else {
// const options = param_data['options'];
// if (options && param_type) {
// const is_spare = options['spare'] === true;
// if (is_spare && param_data['default_value']) {
// if (has_param) {
// this._node.params.delete_param(param_name);
// }
// param = this._node.add_param(param_type, param_name, param_data['default_value'], options);
// if (param) {
// JsonImportDispatcher.dispatch_param(param).process_data(param_data);
// }
// }
// }
// }
}
private _is_param_data_complex(param_data: ParamJsonExporterData<ParamType>): boolean {
// we can test here most param value serialized, except for ramp
if (
isString(param_data) ||
isNumber(param_data) ||
isArray(param_data) ||
isBoolean(param_data)
) {
return false;
}
if (isObject(param_data)) {
const keys = Object.keys(param_data);
for (let complex_key of COMPLEX_PARAM_DATA_KEYS) {
if (keys.includes(complex_key)) {
return true;
}
}
}
return false;
}
set_persisted_config(persisted_config_data: object) {
if (this._node.persisted_config) {
this._node.persisted_config.load(persisted_config_data);
}
}
setCustomData(data: NodeJsonExporterData) {}
}