UNPKG

@openhps/core

Version:

Open Hybrid Positioning System - Core component

712 lines (650 loc) 20.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _constants = require("./constants.js"); var _NodeUtils = require("./NodeUtils.js"); var _EventDispatcher = require("../../core/EventDispatcher.js"); var _MathUtils = require("../../math/MathUtils.js"); let _nodeId = 0; /** * Base class for all nodes. * * @augments EventDispatcher */ class Node extends _EventDispatcher.EventDispatcher { static get type() { return 'Node'; } /** * Constructs a new node. * * @param {?string} nodeType - The node type. */ constructor(nodeType = null) { super(); /** * The node type. This represents the result type of the node (e.g. `float` or `vec3`). * * @type {?string} * @default null */ this.nodeType = nodeType; /** * The update type of the node's {@link Node#update} method. Possible values are listed in {@link NodeUpdateType}. * * @type {string} * @default 'none' */ this.updateType = _constants.NodeUpdateType.NONE; /** * The update type of the node's {@link Node#updateBefore} method. Possible values are listed in {@link NodeUpdateType}. * * @type {string} * @default 'none' */ this.updateBeforeType = _constants.NodeUpdateType.NONE; /** * The update type of the node's {@link Node#updateAfter} method. Possible values are listed in {@link NodeUpdateType}. * * @type {string} * @default 'none' */ this.updateAfterType = _constants.NodeUpdateType.NONE; /** * The UUID of the node. * * @type {string} * @readonly */ this.uuid = _MathUtils.MathUtils.generateUUID(); /** * The version of the node. The version automatically is increased when {@link Node#needsUpdate} is set to `true`. * * @type {number} * @readonly * @default 0 */ this.version = 0; /** * Whether this node is global or not. This property is relevant for the internal * node caching system. All nodes which should be declared just once should * set this flag to `true` (a typical example is {@link AttributeNode}). * * @type {boolean} * @default false */ this.global = false; /** * This flag can be used for type testing. * * @type {boolean} * @readonly * @default true */ this.isNode = true; // private /** * The cache key of this node. * * @private * @type {?number} * @default null */ this._cacheKey = null; /** * The cache key 's version. * * @private * @type {number} * @default 0 */ this._cacheKeyVersion = 0; Object.defineProperty(this, 'id', { value: _nodeId++ }); } /** * Set this property to `true` when the node should be regenerated. * * @type {boolean} * @default false * @param {boolean} value */ set needsUpdate(value) { if (value === true) { this.version++; } } /** * The type of the class. The value is usually the constructor name. * * @type {string} * @readonly */ get type() { return this.constructor.type; } /** * Convenient method for defining {@link Node#update}. * * @param {Function} callback - The update method. * @param {string} updateType - The update type. * @return {Node} A reference to this node. */ onUpdate(callback, updateType) { this.updateType = updateType; this.update = callback.bind(this.getSelf()); return this; } /** * Convenient method for defining {@link Node#update}. Similar to {@link Node#onUpdate}, but * this method automatically sets the update type to `FRAME`. * * @param {Function} callback - The update method. * @return {Node} A reference to this node. */ onFrameUpdate(callback) { return this.onUpdate(callback, _constants.NodeUpdateType.FRAME); } /** * Convenient method for defining {@link Node#update}. Similar to {@link Node#onUpdate}, but * this method automatically sets the update type to `RENDER`. * * @param {Function} callback - The update method. * @return {Node} A reference to this node. */ onRenderUpdate(callback) { return this.onUpdate(callback, _constants.NodeUpdateType.RENDER); } /** * Convenient method for defining {@link Node#update}. Similar to {@link Node#onUpdate}, but * this method automatically sets the update type to `OBJECT`. * * @param {Function} callback - The update method. * @return {Node} A reference to this node. */ onObjectUpdate(callback) { return this.onUpdate(callback, _constants.NodeUpdateType.OBJECT); } /** * Convenient method for defining {@link Node#updateReference}. * * @param {Function} callback - The update method. * @return {Node} A reference to this node. */ onReference(callback) { this.updateReference = callback.bind(this.getSelf()); return this; } /** * The `this` reference might point to a Proxy so this method can be used * to get the reference to the actual node instance. * * @return {Node} A reference to the node. */ getSelf() { // Returns non-node object. return this.self || this; } /** * Nodes might refer to other objects like materials. This method allows to dynamically update the reference * to such objects based on a given state (e.g. the current node frame or builder). * * @param {any} state - This method can be invocated in different contexts so `state` can refer to any object type. * @return {any} The updated reference. */ updateReference( /*state*/ ) { return this; } /** * By default this method returns the value of the {@link Node#global} flag. This method * can be overwritten in derived classes if an analytical way is required to determine the * global status. * * @param {NodeBuilder} builder - The current node builder. * @return {boolean} Whether this node is global or not. */ isGlobal( /*builder*/ ) { return this.global; } /** * Generator function that can be used to iterate over the child nodes. * * @generator * @yields {Node} A child node. */ *getChildren() { for (const { childNode } of (0, _NodeUtils.getNodeChildren)(this)) { yield childNode; } } /** * Calling this method dispatches the `dispose` event. This event can be used * to register event listeners for clean up tasks. */ dispose() { this.dispatchEvent({ type: 'dispose' }); } /** * Callback for {@link Node#traverse}. * * @callback traverseCallback * @param {Node} node - The current node. */ /** * Can be used to traverse through the node's hierarchy. * * @param {traverseCallback} callback - A callback that is executed per node. */ traverse(callback) { callback(this); for (const childNode of this.getChildren()) { childNode.traverse(callback); } } /** * Returns the cache key for this node. * * @param {boolean} [force=false] - When set to `true`, a recomputation of the cache key is forced. * @return {number} The cache key of the node. */ getCacheKey(force = false) { force = force || this.version !== this._cacheKeyVersion; if (force === true || this._cacheKey === null) { this._cacheKey = (0, _NodeUtils.hash)((0, _NodeUtils.getCacheKey)(this, force), this.customCacheKey()); this._cacheKeyVersion = this.version; } return this._cacheKey; } /** * Generate a custom cache key for this node. * * @return {number} The cache key of the node. */ customCacheKey() { return 0; } /** * Returns the references to this node which is by default `this`. * * @return {Node} A reference to this node. */ getScope() { return this; } /** * Returns the hash of the node which is used to identify the node. By default it's * the {@link Node#uuid} however derived node classes might have to overwrite this method * depending on their implementation. * * @param {NodeBuilder} builder - The current node builder. * @return {string} The hash. */ getHash( /*builder*/ ) { return this.uuid; } /** * Returns the update type of {@link Node#update}. * * @return {NodeUpdateType} The update type. */ getUpdateType() { return this.updateType; } /** * Returns the update type of {@link Node#updateBefore}. * * @return {NodeUpdateType} The update type. */ getUpdateBeforeType() { return this.updateBeforeType; } /** * Returns the update type of {@link Node#updateAfter}. * * @return {NodeUpdateType} The update type. */ getUpdateAfterType() { return this.updateAfterType; } /** * Certain types are composed of multiple elements. For example a `vec3` * is composed of three `float` values. This method returns the type of * these elements. * * @param {NodeBuilder} builder - The current node builder. * @return {string} The type of the node. */ getElementType(builder) { const type = this.getNodeType(builder); const elementType = builder.getElementType(type); return elementType; } /** * Returns the node member type for the given name. * * @param {NodeBuilder} builder - The current node builder. * @param {string} name - The name of the member. * @return {string} The type of the node. */ getMemberType( /*builder, name*/ ) { return 'void'; } /** * Returns the node's type. * * @param {NodeBuilder} builder - The current node builder. * @return {string} The type of the node. */ getNodeType(builder) { const nodeProperties = builder.getNodeProperties(this); if (nodeProperties.outputNode) { return nodeProperties.outputNode.getNodeType(builder); } return this.nodeType; } /** * This method is used during the build process of a node and ensures * equal nodes are not built multiple times but just once. For example if * `attribute( 'uv' )` is used multiple times by the user, the build * process makes sure to process just the first node. * * @param {NodeBuilder} builder - The current node builder. * @return {Node} The shared node if possible. Otherwise `this` is returned. */ getShared(builder) { const hash = this.getHash(builder); const nodeFromHash = builder.getNodeFromHash(hash); return nodeFromHash || this; } /** * Represents the setup stage which is the first step of the build process, see {@link Node#build} method. * This method is often overwritten in derived modules to prepare the node which is used as the output/result. * The output node must be returned in the `return` statement. * * @param {NodeBuilder} builder - The current node builder. * @return {?Node} The output node. */ setup(builder) { const nodeProperties = builder.getNodeProperties(this); let index = 0; for (const childNode of this.getChildren()) { nodeProperties['node' + index++] = childNode; } // return a outputNode if exists or null return nodeProperties.outputNode || null; } /** * Represents the analyze stage which is the second step of the build process, see {@link Node#build} method. * This stage analyzes the node hierarchy and ensures descendent nodes are built. * * @param {NodeBuilder} builder - The current node builder. */ analyze(builder) { const usageCount = builder.increaseUsage(this); if (usageCount === 1) { // node flow children const nodeProperties = builder.getNodeProperties(this); for (const childNode of Object.values(nodeProperties)) { if (childNode && childNode.isNode === true) { childNode.build(builder); } } } } /** * Represents the generate stage which is the third step of the build process, see {@link Node#build} method. * This state builds the output node and returns the resulting shader string. * * @param {NodeBuilder} builder - The current node builder. * @param {?string} output - Can be used to define the output type. * @return {?string} The generated shader string. */ generate(builder, output) { const { outputNode } = builder.getNodeProperties(this); if (outputNode && outputNode.isNode === true) { return outputNode.build(builder, output); } } /** * The method can be implemented to update the node's internal state before it is used to render an object. * The {@link Node#updateBeforeType} property defines how often the update is executed. * * @abstract * @param {NodeFrame} frame - A reference to the current node frame. * @return {?boolean} An optional bool that indicates whether the implementation actually performed an update or not (e.g. due to caching). */ updateBefore( /*frame*/ ) { console.warn('Abstract function.'); } /** * The method can be implemented to update the node's internal state after it was used to render an object. * The {@link Node#updateAfterType} property defines how often the update is executed. * * @abstract * @param {NodeFrame} frame - A reference to the current node frame. * @return {?boolean} An optional bool that indicates whether the implementation actually performed an update or not (e.g. due to caching). */ updateAfter( /*frame*/ ) { console.warn('Abstract function.'); } /** * The method can be implemented to update the node's internal state when it is used to render an object. * The {@link Node#updateType} property defines how often the update is executed. * * @abstract * @param {NodeFrame} frame - A reference to the current node frame. * @return {?boolean} An optional bool that indicates whether the implementation actually performed an update or not (e.g. due to caching). */ update( /*frame*/ ) { console.warn('Abstract function.'); } /** * This method performs the build of a node. The behavior of this method as well as its return value depend * on the current build stage (setup, analyze or generate). * * @param {NodeBuilder} builder - The current node builder. * @param {?string} output - Can be used to define the output type. * @return {?string} When this method is executed in the setup or analyze stage, `null` is returned. In the generate stage, the generated shader string. */ build(builder, output = null) { const refNode = this.getShared(builder); if (this !== refNode) { return refNode.build(builder, output); } builder.addNode(this); builder.addChain(this); /* Build stages expected results: - "setup" -> Node - "analyze" -> null - "generate" -> String */ let result = null; const buildStage = builder.getBuildStage(); if (buildStage === 'setup') { this.updateReference(builder); const properties = builder.getNodeProperties(this); if (properties.initialized !== true) { //const stackNodesBeforeSetup = builder.stack.nodes.length; properties.initialized = true; const outputNode = this.setup(builder); // return a node or null const isNodeOutput = outputNode && outputNode.isNode === true; /*if ( isNodeOutput && builder.stack.nodes.length !== stackNodesBeforeSetup ) { // !! no outputNode !! //outputNode = builder.stack; }*/ for (const childNode of Object.values(properties)) { if (childNode && childNode.isNode === true) { childNode.build(builder); } } if (isNodeOutput) { outputNode.build(builder); } properties.outputNode = outputNode; } } else if (buildStage === 'analyze') { this.analyze(builder); } else if (buildStage === 'generate') { const isGenerateOnce = this.generate.length === 1; if (isGenerateOnce) { const type = this.getNodeType(builder); const nodeData = builder.getDataFromNode(this); result = nodeData.snippet; if (result === undefined) { if (nodeData.generated === undefined) { nodeData.generated = true; result = this.generate(builder) || ''; nodeData.snippet = result; } else { console.warn('THREE.Node: Recursion detected.', this); result = ''; } } else if (nodeData.flowCodes !== undefined && builder.context.nodeBlock !== undefined) { builder.addFlowCodeHierarchy(this, builder.context.nodeBlock); } result = builder.format(result, type, output); } else { result = this.generate(builder, output) || ''; } } builder.removeChain(this); builder.addSequentialNode(this); return result; } /** * Returns the child nodes as a JSON object. * * @return {Array<Object>} An iterable list of serialized child objects as JSON. */ getSerializeChildren() { return (0, _NodeUtils.getNodeChildren)(this); } /** * Serializes the node to JSON. * * @param {Object} json - The output JSON object. */ serialize(json) { const nodeChildren = this.getSerializeChildren(); const inputNodes = {}; for (const { property, index, childNode } of nodeChildren) { if (index !== undefined) { if (inputNodes[property] === undefined) { inputNodes[property] = Number.isInteger(index) ? [] : {}; } inputNodes[property][index] = childNode.toJSON(json.meta).uuid; } else { inputNodes[property] = childNode.toJSON(json.meta).uuid; } } if (Object.keys(inputNodes).length > 0) { json.inputNodes = inputNodes; } } /** * Deserializes the node from the given JSON. * * @param {Object} json - The JSON object. */ deserialize(json) { if (json.inputNodes !== undefined) { const nodes = json.meta.nodes; for (const property in json.inputNodes) { if (Array.isArray(json.inputNodes[property])) { const inputArray = []; for (const uuid of json.inputNodes[property]) { inputArray.push(nodes[uuid]); } this[property] = inputArray; } else if (typeof json.inputNodes[property] === 'object') { const inputObject = {}; for (const subProperty in json.inputNodes[property]) { const uuid = json.inputNodes[property][subProperty]; inputObject[subProperty] = nodes[uuid]; } this[property] = inputObject; } else { const uuid = json.inputNodes[property]; this[property] = nodes[uuid]; } } } } /** * Serializes the node into the three.js JSON Object/Scene format. * * @param {?Object} meta - An optional JSON object that already holds serialized data from other scene objects. * @return {Object} The serialized node. */ toJSON(meta) { const { uuid, type } = this; const isRoot = meta === undefined || typeof meta === 'string'; if (isRoot) { meta = { textures: {}, images: {}, nodes: {} }; } // serialize let data = meta.nodes[uuid]; if (data === undefined) { data = { uuid, type, meta, metadata: { version: 4.6, type: 'Node', generator: 'Node.toJSON' } }; if (isRoot !== true) meta.nodes[data.uuid] = data; this.serialize(data); delete data.meta; } // TODO: Copied from Object3D.toJSON function extractFromCache(cache) { const values = []; for (const key in cache) { const data = cache[key]; delete data.metadata; values.push(data); } return values; } if (isRoot) { const textures = extractFromCache(meta.textures); const images = extractFromCache(meta.images); const nodes = extractFromCache(meta.nodes); if (textures.length > 0) data.textures = textures; if (images.length > 0) data.images = images; if (nodes.length > 0) data.nodes = nodes; } return data; } } var _default = exports.default = Node;