@openhps/core
Version:
Open Hybrid Positioning System - Core component
712 lines (650 loc) • 20.4 kB
JavaScript
"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;