UNPKG

@babylonjs/core

Version:

Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.

524 lines (523 loc) 21.2 kB
import { __decorate } from "../../tslib.es6.js"; import { serialize } from "../../Misc/decorators.js"; import { ParticleSystemSet } from "../particleSystemSet.js"; import { SystemBlock } from "./Blocks/systemBlock.js"; import { NodeParticleBuildState } from "./nodeParticleBuildState.js"; import { SerializationHelper } from "../../Misc/decorators.serialization.js"; import { Observable } from "../../Misc/observable.js"; import { GetClass } from "../../Misc/typeStore.js"; import { WebRequest } from "../../Misc/webRequest.js"; import { Tools } from "../../Misc/tools.js"; import { AbstractEngine } from "../../Engines/abstractEngine.js"; import { ParticleInputBlock } from "./Blocks/particleInputBlock.js"; import { ParticleTextureSourceBlock } from "./Blocks/particleSourceTextureBlock.js"; import { NodeParticleContextualSources } from "./Enums/nodeParticleContextualSources.js"; import { UpdatePositionBlock } from "./Blocks/Update/updatePositionBlock.js"; import { ParticleMathBlock, ParticleMathBlockOperations } from "./Blocks/particleMathBlock.js"; import { BoxShapeBlock } from "./Blocks/Emitters/boxShapeBlock.js"; import { CreateParticleBlock } from "./Blocks/Emitters/createParticleBlock.js"; /** * Defines a set of particle systems defined as a node graph. * NPE: #K6F1ZB#1 * PG: #ZT509U#1 */ export class NodeParticleSystemSet { /** * Gets the system blocks */ get systemBlocks() { return this._systemBlocks; } /** * Gets the list of input blocks attached to this material * @returns an array of InputBlocks */ get inputBlocks() { const blocks = []; for (const block of this.attachedBlocks) { if (block.isInput) { blocks.push(block); } } return blocks; } /** * Get a block by its name * @param name defines the name of the block to retrieve * @returns the required block or null if not found */ getBlockByName(name) { let result = null; for (const block of this.attachedBlocks) { if (block.name === name) { if (!result) { result = block; } else { Tools.Warn("More than one block was found with the name `" + name + "`"); return result; } } } return result; } /** * Get a block using a predicate * @param predicate defines the predicate used to find the good candidate * @returns the required block or null if not found */ getBlockByPredicate(predicate) { for (const block of this.attachedBlocks) { if (predicate(block)) { return block; } } return null; } /** * Get an input block using a predicate * @param predicate defines the predicate used to find the good candidate * @returns the required input block or null if not found */ getInputBlockByPredicate(predicate) { for (const block of this.attachedBlocks) { if (block.isInput && predicate(block)) { return block; } } return null; } /** * Creates a new set * @param name defines the name of the set */ constructor(name) { this._systemBlocks = []; this._buildId = 0; /** * Gets an array of blocks that needs to be serialized even if they are not yet connected */ this.attachedBlocks = []; /** * Gets or sets data used by visual editor * @see https://npe.babylonjs.com */ this.editorData = null; /** * Observable raised when the particle set is built */ this.onBuildObservable = new Observable(); this.BJSNODEPARTICLEEDITOR = this._getGlobalNodeParticleEditor(); this.name = name; } /** * Gets the current class name of the node particle set e.g. "NodeParticleSystemSet" * @returns the class name */ getClassName() { return "NodeParticleSystemSet"; } _initializeBlock(node, autoConfigure = true) { if (this.attachedBlocks.indexOf(node) === -1) { this.attachedBlocks.push(node); } for (const input of node.inputs) { const connectedPoint = input.connectedPoint; if (connectedPoint) { const block = connectedPoint.ownerBlock; if (block !== node) { this._initializeBlock(block, autoConfigure); } } } } /** Get the editor from bundle or global * @returns the global NPE */ _getGlobalNodeParticleEditor() { // UMD Global name detection from Webpack Bundle UMD Name. if (typeof NODEPARTICLEEDITOR !== "undefined") { return NODEPARTICLEEDITOR; } // In case of module let's check the global emitted from the editor entry point. if (typeof BABYLON !== "undefined" && typeof BABYLON.NodeParticleEditor !== "undefined") { return BABYLON; } return undefined; } /** Creates the node editor window. * @param additionalConfig Define the configuration of the editor */ _createNodeParticleEditor(additionalConfig) { const nodeEditorConfig = { nodeParticleSet: this, ...additionalConfig, }; this.BJSNODEPARTICLEEDITOR.NodeParticleEditor.Show(nodeEditorConfig); } /** * Launch the node particle editor * @param config Define the configuration of the editor * @returns a promise fulfilled when the node editor is visible */ async editAsync(config) { return await new Promise((resolve) => { this.BJSNODEPARTICLEEDITOR = this.BJSNODEPARTICLEEDITOR || this._getGlobalNodeParticleEditor(); if (typeof this.BJSNODEPARTICLEEDITOR == "undefined") { const editorUrl = config && config.editorURL ? config.editorURL : NodeParticleSystemSet.EditorURL; // Load editor and add it to the DOM Tools.LoadBabylonScript(editorUrl, () => { this.BJSNODEPARTICLEEDITOR = this.BJSNODEPARTICLEEDITOR || this._getGlobalNodeParticleEditor(); this._createNodeParticleEditor(config?.nodeEditorConfig); resolve(); }); } else { // Otherwise creates the editor this._createNodeParticleEditor(config?.nodeEditorConfig); resolve(); } }); } /** * Builds the particle system set from the defined blocks. * @param scene defines the hosting scene * @param verbose defines whether to log detailed information during the build process (false by default) * @returns a promise that resolves to the built particle system set */ async buildAsync(scene, verbose = false) { return await new Promise((resolve) => { const output = new ParticleSystemSet(); // Initialize all blocks for (const block of this._systemBlocks) { this._initializeBlock(block); } // Build the blocks for (const block of this.systemBlocks) { const state = new NodeParticleBuildState(); state.buildId = this._buildId++; state.scene = scene; state.verbose = verbose; const system = block.createSystem(state); system._source = this; system._blockReference = block._internalId; // Errors state.emitErrors(); output.systems.push(system); } this.onBuildObservable.notifyObservers(this); resolve(output); }); } /** * Clear the current node particle set */ clear() { this.attachedBlocks.length = 0; this._systemBlocks.length = 0; } /** * Clear the current set and restore it to a default state */ setToDefault() { this.clear(); this.editorData = null; // Main system const system = new SystemBlock("Particle system"); // Update position const updatePositionBlock = new UpdatePositionBlock("Update position"); updatePositionBlock.output.connectTo(system.particle); // Contextual inputs const positionBlock = new ParticleInputBlock("Position"); positionBlock.contextualValue = NodeParticleContextualSources.Position; const directionBlock = new ParticleInputBlock("Scaled direction"); directionBlock.contextualValue = NodeParticleContextualSources.ScaledDirection; // Add const addBlock = new ParticleMathBlock("Add"); addBlock.operation = ParticleMathBlockOperations.Add; positionBlock.output.connectTo(addBlock.left); directionBlock.output.connectTo(addBlock.right); addBlock.output.connectTo(updatePositionBlock.position); // Create particle const createParticleBlock = new CreateParticleBlock("Create particle"); // Shape const emitterShape = new BoxShapeBlock("Box shape"); createParticleBlock.particle.connectTo(emitterShape.particle); emitterShape.output.connectTo(updatePositionBlock.particle); // Texture const textureBlock = new ParticleTextureSourceBlock("Texture"); textureBlock.texture.connectTo(system.texture); textureBlock.url = "https://assets.babylonjs.com/textures/flare.png"; this._systemBlocks.push(system); } /** * Remove a block from the current system set * @param block defines the block to remove */ removeBlock(block) { const attachedBlockIndex = this.attachedBlocks.indexOf(block); if (attachedBlockIndex > -1) { this.attachedBlocks.splice(attachedBlockIndex, 1); } if (block.isSystem) { const index = this._systemBlocks.indexOf(block); if (index > -1) { this._systemBlocks.splice(index, 1); } } } /** * Clear the current graph and load a new one from a serialization object * @param source defines the JSON representation of the particle set * @param merge defines whether or not the source must be merged or replace the current content */ parseSerializedObject(source, merge = false) { if (!merge) { this.clear(); } const map = {}; // Create blocks for (const parsedBlock of source.blocks) { const blockType = GetClass(parsedBlock.customType); if (blockType) { const block = new blockType(); block._deserialize(parsedBlock); map[parsedBlock.id] = block; this.attachedBlocks.push(block); if (block.isSystem) { this._systemBlocks.push(block); } } } // Reconnect teleportation for (const block of this.attachedBlocks) { if (block.isTeleportOut) { const teleportOut = block; const id = teleportOut._tempEntryPointUniqueId; if (id) { const source = map[id]; if (source) { source.attachToEndpoint(teleportOut); } } } } // Connections - Starts with input blocks only (except if in "merge" mode where we scan all blocks) for (let blockIndex = 0; blockIndex < source.blocks.length; blockIndex++) { const parsedBlock = source.blocks[blockIndex]; const block = map[parsedBlock.id]; if (!block) { continue; } if (block.inputs.length && parsedBlock.inputs.some((i) => i.targetConnectionName) && !merge) { continue; } this._restoreConnections(block, source, map); } // UI related info if (source.locations || (source.editorData && source.editorData.locations)) { const locations = source.locations || source.editorData.locations; for (const location of locations) { if (map[location.blockId]) { location.blockId = map[location.blockId].uniqueId; } } if (merge && this.editorData && this.editorData.locations) { locations.concat(this.editorData.locations); } if (source.locations) { this.editorData = { locations: locations, }; } else { this.editorData = source.editorData; this.editorData.locations = locations; } const blockMap = {}; for (const key in map) { blockMap[key] = map[key].uniqueId; } this.editorData.map = blockMap; } this.comment = source.comment; } _restoreConnections(block, source, map) { for (const outputPoint of block.outputs) { for (const candidate of source.blocks) { const target = map[candidate.id]; if (!target) { continue; } for (const input of candidate.inputs) { if (map[input.targetBlockId] === block && input.targetConnectionName === outputPoint.name) { const inputPoint = target.getInputByName(input.inputName); if (!inputPoint || inputPoint.isConnected) { continue; } outputPoint.connectTo(inputPoint, true); this._restoreConnections(target, source, map); continue; } } } } } /** * Serializes this node particle set in a JSON representation * @param selectedBlocks defines the list of blocks to save (if null the whole node particle set will be saved) * @returns the serialized particle system set object */ serialize(selectedBlocks) { const serializationObject = selectedBlocks ? {} : SerializationHelper.Serialize(this); serializationObject.editorData = JSON.parse(JSON.stringify(this.editorData)); // Copy let blocks = []; if (selectedBlocks) { blocks = selectedBlocks; } else { serializationObject.customType = "BABYLON.NodeParticleSystemSet"; } // Blocks serializationObject.blocks = []; for (const block of blocks) { serializationObject.blocks.push(block.serialize()); } if (!selectedBlocks) { for (const block of this.attachedBlocks) { if (blocks.indexOf(block) !== -1) { continue; } serializationObject.blocks.push(block.serialize()); } } return serializationObject; } /** * Makes a duplicate of the current particle system set. * @param name defines the name to use for the new particle system set * @returns the cloned particle system set */ clone(name) { const serializationObject = this.serialize(); const clone = SerializationHelper.Clone(() => new NodeParticleSystemSet(name), this); clone.name = name; clone.snippetId = this.snippetId; clone.parseSerializedObject(serializationObject); clone._buildId = this._buildId; return clone; } /** * Disposes the resources */ dispose() { for (const block of this.attachedBlocks) { block.dispose(); } this.attachedBlocks.length = 0; this.onBuildObservable.clear(); } /** * Creates a new node particle set set to default basic configuration * @param name defines the name of the particle set * @returns a new NodeParticleSystemSet */ static CreateDefault(name) { const nodeParticleSet = new NodeParticleSystemSet(name); nodeParticleSet.setToDefault(); return nodeParticleSet; } /** * Creates a node particle set from parsed data * @param source defines the JSON representation of the particle set * @returns a new node particle set */ static Parse(source) { const nodeParticleSet = SerializationHelper.Parse(() => new NodeParticleSystemSet(source.name), source, null); nodeParticleSet.parseSerializedObject(source); return nodeParticleSet; } /** * Creates a node particle set from a snippet saved in a remote file * @param name defines the name of the node particle set to create * @param url defines the url to load from * @param nodeParticleSet defines a node particle set to update (instead of creating a new one) * @returns a promise that will resolve to the new node particle set */ // eslint-disable-next-line @typescript-eslint/promise-function-async, no-restricted-syntax static ParseFromFileAsync(name, url, nodeParticleSet) { return new Promise((resolve, reject) => { const request = new WebRequest(); request.addEventListener("readystatechange", () => { if (request.readyState == 4) { if (request.status == 200) { const serializationObject = JSON.parse(request.responseText); if (!nodeParticleSet) { nodeParticleSet = SerializationHelper.Parse(() => new NodeParticleSystemSet(name), serializationObject, null); } nodeParticleSet.parseSerializedObject(serializationObject); resolve(nodeParticleSet); } else { // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors reject("Unable to load the node particle system set"); } } }); request.open("GET", url); request.send(); }); } /** * Creates a node particle set from a snippet saved by the node particle editor * @param snippetId defines the snippet to load * @param nodeParticleSet defines a node particle set to update (instead of creating a new one) * @returns a promise that will resolve to the new node particle set */ // eslint-disable-next-line @typescript-eslint/promise-function-async, no-restricted-syntax static ParseFromSnippetAsync(snippetId, nodeParticleSet) { if (snippetId === "_BLANK") { return Promise.resolve(NodeParticleSystemSet.CreateDefault("blank")); } return new Promise((resolve, reject) => { const request = new WebRequest(); request.addEventListener("readystatechange", () => { if (request.readyState == 4) { if (request.status == 200) { const snippet = JSON.parse(JSON.parse(request.responseText).jsonPayload); const serializationObject = JSON.parse(snippet.nodeParticle); if (!nodeParticleSet) { nodeParticleSet = SerializationHelper.Parse(() => new NodeParticleSystemSet(snippetId), serializationObject, null); } nodeParticleSet.parseSerializedObject(serializationObject); nodeParticleSet.snippetId = snippetId; try { resolve(nodeParticleSet); } catch (err) { // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors reject(err); } } else { // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors reject("Unable to load the snippet " + snippetId); } } }); request.open("GET", this.SnippetUrl + "/" + snippetId.replace(/#/g, "/")); request.send(); }); } } /** Define the Url to load node editor script */ NodeParticleSystemSet.EditorURL = `${Tools._DefaultCdnUrl}/v${AbstractEngine.Version}/nodeParticleEditor/babylon.nodeParticleEditor.js`; /** Define the Url to load snippets */ NodeParticleSystemSet.SnippetUrl = `https://snippet.babylonjs.com`; __decorate([ serialize() ], NodeParticleSystemSet.prototype, "name", void 0); __decorate([ serialize("comment") ], NodeParticleSystemSet.prototype, "comment", void 0); //# sourceMappingURL=nodeParticleSystemSet.js.map