nodeMaterialBlock.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  1. import { NodeMaterialBlockConnectionPointTypes } from "./Enums/nodeMaterialBlockConnectionPointTypes.js";
  2. import { NodeMaterialConnectionPoint, NodeMaterialConnectionPointDirection } from "./nodeMaterialBlockConnectionPoint.js";
  3. import { NodeMaterialBlockTargets } from "./Enums/nodeMaterialBlockTargets.js";
  4. import { UniqueIdGenerator } from "../../Misc/uniqueIdGenerator.js";
  5. import { GetClass } from "../../Misc/typeStore.js";
  6. import { Logger } from "../../Misc/logger.js";
  7. /**
  8. * Defines a block that can be used inside a node based material
  9. */
  10. export class NodeMaterialBlock {
  11. /**
  12. * Gets the name of the block
  13. */
  14. get name() {
  15. return this._name;
  16. }
  17. /**
  18. * Sets the name of the block. Will check if the name is valid.
  19. */
  20. set name(newName) {
  21. if (!this.validateBlockName(newName)) {
  22. return;
  23. }
  24. this._name = newName;
  25. }
  26. /**
  27. * Gets a boolean indicating that this block can only be used once per NodeMaterial
  28. */
  29. get isUnique() {
  30. return this._isUnique;
  31. }
  32. /**
  33. * Gets a boolean indicating that this block is an end block (e.g. it is generating a system value)
  34. */
  35. get isFinalMerger() {
  36. return this._isFinalMerger;
  37. }
  38. /**
  39. * Gets a boolean indicating that this block is an input (e.g. it sends data to the shader)
  40. */
  41. get isInput() {
  42. return this._isInput;
  43. }
  44. /**
  45. * Gets a boolean indicating if this block is a teleport out
  46. */
  47. get isTeleportOut() {
  48. return this._isTeleportOut;
  49. }
  50. /**
  51. * Gets a boolean indicating if this block is a teleport in
  52. */
  53. get isTeleportIn() {
  54. return this._isTeleportIn;
  55. }
  56. /**
  57. * Gets or sets the build Id
  58. */
  59. get buildId() {
  60. return this._buildId;
  61. }
  62. set buildId(value) {
  63. this._buildId = value;
  64. }
  65. /**
  66. * Gets or sets the target of the block
  67. */
  68. get target() {
  69. return this._target;
  70. }
  71. set target(value) {
  72. if ((this._target & value) !== 0) {
  73. return;
  74. }
  75. this._target = value;
  76. }
  77. /**
  78. * Gets the list of input points
  79. */
  80. get inputs() {
  81. return this._inputs;
  82. }
  83. /** Gets the list of output points */
  84. get outputs() {
  85. return this._outputs;
  86. }
  87. /**
  88. * Find an input by its name
  89. * @param name defines the name of the input to look for
  90. * @returns the input or null if not found
  91. */
  92. getInputByName(name) {
  93. const filter = this._inputs.filter((e) => e.name === name);
  94. if (filter.length) {
  95. return filter[0];
  96. }
  97. return null;
  98. }
  99. /**
  100. * Find an output by its name
  101. * @param name defines the name of the output to look for
  102. * @returns the output or null if not found
  103. */
  104. getOutputByName(name) {
  105. const filter = this._outputs.filter((e) => e.name === name);
  106. if (filter.length) {
  107. return filter[0];
  108. }
  109. return null;
  110. }
  111. /**
  112. * Creates a new NodeMaterialBlock
  113. * @param name defines the block name
  114. * @param target defines the target of that block (Vertex by default)
  115. * @param isFinalMerger defines a boolean indicating that this block is an end block (e.g. it is generating a system value). Default is false
  116. */
  117. constructor(name, target = NodeMaterialBlockTargets.Vertex, isFinalMerger = false) {
  118. this._isFinalMerger = false;
  119. this._isInput = false;
  120. this._isTeleportOut = false;
  121. this._isTeleportIn = false;
  122. this._name = "";
  123. this._isUnique = false;
  124. /** Gets or sets a boolean indicating that only one input can be connected at a time */
  125. this.inputsAreExclusive = false;
  126. /** @internal */
  127. this._codeVariableName = "";
  128. /** @internal */
  129. this._inputs = new Array();
  130. /** @internal */
  131. this._outputs = new Array();
  132. /**
  133. * Gets or sets the comments associated with this block
  134. */
  135. this.comments = "";
  136. /** Gets or sets a boolean indicating that this input can be edited in the Inspector (false by default) */
  137. this.visibleInInspector = false;
  138. /** Gets or sets a boolean indicating that this input can be edited from a collapsed frame */
  139. this.visibleOnFrame = false;
  140. this._target = target;
  141. this._originalTargetIsNeutral = target === NodeMaterialBlockTargets.Neutral;
  142. this._isFinalMerger = isFinalMerger;
  143. this._isInput = this.getClassName() === "InputBlock";
  144. this._isTeleportOut = this.getClassName() === "NodeMaterialTeleportOutBlock";
  145. this._isTeleportIn = this.getClassName() === "NodeMaterialTeleportInBlock";
  146. this._name = name;
  147. this.uniqueId = UniqueIdGenerator.UniqueId;
  148. }
  149. /** @internal */
  150. _setInitialTarget(target) {
  151. this._target = target;
  152. this._originalTargetIsNeutral = target === NodeMaterialBlockTargets.Neutral;
  153. }
  154. /**
  155. * Initialize the block and prepare the context for build
  156. * @param state defines the state that will be used for the build
  157. */
  158. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  159. initialize(state) {
  160. // Do nothing
  161. }
  162. /**
  163. * Bind data to effect. Will only be called for blocks with isBindable === true
  164. * @param effect defines the effect to bind data to
  165. * @param nodeMaterial defines the hosting NodeMaterial
  166. * @param mesh defines the mesh that will be rendered
  167. * @param subMesh defines the submesh that will be rendered
  168. */
  169. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  170. bind(effect, nodeMaterial, mesh, subMesh) {
  171. // Do nothing
  172. }
  173. _declareOutput(output, state) {
  174. return `${state._getGLType(output.type)} ${output.associatedVariableName}`;
  175. }
  176. _writeVariable(currentPoint) {
  177. const connectionPoint = currentPoint.connectedPoint;
  178. if (connectionPoint) {
  179. return `${currentPoint.associatedVariableName}`;
  180. }
  181. return `0.`;
  182. }
  183. _writeFloat(value) {
  184. let stringVersion = value.toString();
  185. if (stringVersion.indexOf(".") === -1) {
  186. stringVersion += ".0";
  187. }
  188. return `${stringVersion}`;
  189. }
  190. /**
  191. * Gets the current class name e.g. "NodeMaterialBlock"
  192. * @returns the class name
  193. */
  194. getClassName() {
  195. return "NodeMaterialBlock";
  196. }
  197. /** Gets a boolean indicating that this connection will be used in the fragment shader
  198. * @returns true if connected in fragment shader
  199. */
  200. isConnectedInFragmentShader() {
  201. return this.outputs.some((o) => o.isConnectedInFragmentShader);
  202. }
  203. /**
  204. * Register a new input. Must be called inside a block constructor
  205. * @param name defines the connection point name
  206. * @param type defines the connection point type
  207. * @param isOptional defines a boolean indicating that this input can be omitted
  208. * @param target defines the target to use to limit the connection point (will be VertexAndFragment by default)
  209. * @param point an already created connection point. If not provided, create a new one
  210. * @returns the current block
  211. */
  212. registerInput(name, type, isOptional = false, target, point) {
  213. point = point ?? new NodeMaterialConnectionPoint(name, this, NodeMaterialConnectionPointDirection.Input);
  214. point.type = type;
  215. point.isOptional = isOptional;
  216. if (target) {
  217. point.target = target;
  218. }
  219. this._inputs.push(point);
  220. return this;
  221. }
  222. /**
  223. * Register a new output. Must be called inside a block constructor
  224. * @param name defines the connection point name
  225. * @param type defines the connection point type
  226. * @param target defines the target to use to limit the connection point (will be VertexAndFragment by default)
  227. * @param point an already created connection point. If not provided, create a new one
  228. * @returns the current block
  229. */
  230. registerOutput(name, type, target, point) {
  231. point = point ?? new NodeMaterialConnectionPoint(name, this, NodeMaterialConnectionPointDirection.Output);
  232. point.type = type;
  233. if (target) {
  234. point.target = target;
  235. }
  236. this._outputs.push(point);
  237. return this;
  238. }
  239. /**
  240. * Will return the first available input e.g. the first one which is not an uniform or an attribute
  241. * @param forOutput defines an optional connection point to check compatibility with
  242. * @returns the first available input or null
  243. */
  244. getFirstAvailableInput(forOutput = null) {
  245. for (const input of this._inputs) {
  246. if (!input.connectedPoint) {
  247. if (!forOutput || forOutput.type === input.type || input.type === NodeMaterialBlockConnectionPointTypes.AutoDetect) {
  248. return input;
  249. }
  250. }
  251. }
  252. return null;
  253. }
  254. /**
  255. * Will return the first available output e.g. the first one which is not yet connected and not a varying
  256. * @param forBlock defines an optional block to check compatibility with
  257. * @returns the first available input or null
  258. */
  259. getFirstAvailableOutput(forBlock = null) {
  260. for (const output of this._outputs) {
  261. if (!forBlock || !forBlock.target || forBlock.target === NodeMaterialBlockTargets.Neutral || (forBlock.target & output.target) !== 0) {
  262. return output;
  263. }
  264. }
  265. return null;
  266. }
  267. /**
  268. * Gets the sibling of the given output
  269. * @param current defines the current output
  270. * @returns the next output in the list or null
  271. */
  272. getSiblingOutput(current) {
  273. const index = this._outputs.indexOf(current);
  274. if (index === -1 || index >= this._outputs.length) {
  275. return null;
  276. }
  277. return this._outputs[index + 1];
  278. }
  279. /**
  280. * Checks if the current block is an ancestor of a given block
  281. * @param block defines the potential descendant block to check
  282. * @returns true if block is a descendant
  283. */
  284. isAnAncestorOf(block) {
  285. for (const output of this._outputs) {
  286. if (!output.hasEndpoints) {
  287. continue;
  288. }
  289. for (const endpoint of output.endpoints) {
  290. if (endpoint.ownerBlock === block) {
  291. return true;
  292. }
  293. if (endpoint.ownerBlock.isAnAncestorOf(block)) {
  294. return true;
  295. }
  296. }
  297. }
  298. return false;
  299. }
  300. /**
  301. * Connect current block with another block
  302. * @param other defines the block to connect with
  303. * @param options define the various options to help pick the right connections
  304. * @param options.input
  305. * @param options.output
  306. * @param options.outputSwizzle
  307. * @returns the current block
  308. */
  309. connectTo(other, options) {
  310. if (this._outputs.length === 0) {
  311. return;
  312. }
  313. let output = options && options.output ? this.getOutputByName(options.output) : this.getFirstAvailableOutput(other);
  314. let notFound = true;
  315. while (notFound) {
  316. const input = options && options.input ? other.getInputByName(options.input) : other.getFirstAvailableInput(output);
  317. if (output && input && output.canConnectTo(input)) {
  318. output.connectTo(input);
  319. notFound = false;
  320. }
  321. else if (!output) {
  322. // eslint-disable-next-line no-throw-literal
  323. throw "Unable to find a compatible match";
  324. }
  325. else {
  326. output = this.getSiblingOutput(output);
  327. }
  328. }
  329. return this;
  330. }
  331. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  332. _buildBlock(state) {
  333. // Empty. Must be defined by child nodes
  334. }
  335. /**
  336. * Add uniforms, samplers and uniform buffers at compilation time
  337. * @param state defines the state to update
  338. * @param nodeMaterial defines the node material requesting the update
  339. * @param defines defines the material defines to update
  340. * @param uniformBuffers defines the list of uniform buffer names
  341. */
  342. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  343. updateUniformsAndSamples(state, nodeMaterial, defines, uniformBuffers) {
  344. // Do nothing
  345. }
  346. /**
  347. * Add potential fallbacks if shader compilation fails
  348. * @param mesh defines the mesh to be rendered
  349. * @param fallbacks defines the current prioritized list of fallbacks
  350. */
  351. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  352. provideFallbacks(mesh, fallbacks) {
  353. // Do nothing
  354. }
  355. /**
  356. * Initialize defines for shader compilation
  357. * @param mesh defines the mesh to be rendered
  358. * @param nodeMaterial defines the node material requesting the update
  359. * @param defines defines the material defines to update
  360. * @param useInstances specifies that instances should be used
  361. */
  362. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  363. initializeDefines(mesh, nodeMaterial, defines, useInstances = false) { }
  364. /**
  365. * Update defines for shader compilation
  366. * @param mesh defines the mesh to be rendered
  367. * @param nodeMaterial defines the node material requesting the update
  368. * @param defines defines the material defines to update
  369. * @param useInstances specifies that instances should be used
  370. * @param subMesh defines which submesh to render
  371. */
  372. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  373. prepareDefines(mesh, nodeMaterial, defines, useInstances = false, subMesh) {
  374. // Do nothing
  375. }
  376. /**
  377. * Lets the block try to connect some inputs automatically
  378. * @param material defines the hosting NodeMaterial
  379. * @param additionalFilteringInfo optional additional filtering condition when looking for compatible blocks
  380. */
  381. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  382. autoConfigure(material, additionalFilteringInfo = () => true) {
  383. // Do nothing
  384. }
  385. /**
  386. * Function called when a block is declared as repeatable content generator
  387. * @param vertexShaderState defines the current compilation state for the vertex shader
  388. * @param fragmentShaderState defines the current compilation state for the fragment shader
  389. * @param mesh defines the mesh to be rendered
  390. * @param defines defines the material defines to update
  391. */
  392. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  393. replaceRepeatableContent(vertexShaderState, fragmentShaderState, mesh, defines) {
  394. // Do nothing
  395. }
  396. /** Gets a boolean indicating that the code of this block will be promoted to vertex shader even if connected to fragment output */
  397. get willBeGeneratedIntoVertexShaderFromFragmentShader() {
  398. if (this.isInput || this.isFinalMerger) {
  399. return false;
  400. }
  401. if (this._outputs.some((o) => o.isDirectlyConnectedToVertexOutput)) {
  402. return false;
  403. }
  404. if (this.target === NodeMaterialBlockTargets.Vertex) {
  405. return false;
  406. }
  407. if (this.target === NodeMaterialBlockTargets.VertexAndFragment || this.target === NodeMaterialBlockTargets.Neutral) {
  408. if (this._outputs.some((o) => o.isConnectedInVertexShader)) {
  409. return true;
  410. }
  411. }
  412. return false;
  413. }
  414. /**
  415. * Checks if the block is ready
  416. * @param mesh defines the mesh to be rendered
  417. * @param nodeMaterial defines the node material requesting the update
  418. * @param defines defines the material defines to update
  419. * @param useInstances specifies that instances should be used
  420. * @returns true if the block is ready
  421. */
  422. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  423. isReady(mesh, nodeMaterial, defines, useInstances = false) {
  424. return true;
  425. }
  426. _linkConnectionTypes(inputIndex0, inputIndex1, looseCoupling = false) {
  427. if (looseCoupling) {
  428. this._inputs[inputIndex1]._acceptedConnectionPointType = this._inputs[inputIndex0];
  429. }
  430. else {
  431. this._inputs[inputIndex0]._linkedConnectionSource = this._inputs[inputIndex1];
  432. }
  433. this._inputs[inputIndex1]._linkedConnectionSource = this._inputs[inputIndex0];
  434. }
  435. _processBuild(block, state, input, activeBlocks) {
  436. block.build(state, activeBlocks);
  437. const localBlockIsFragment = state._vertexState != null;
  438. const otherBlockWasGeneratedInVertexShader = block._buildTarget === NodeMaterialBlockTargets.Vertex && block.target !== NodeMaterialBlockTargets.VertexAndFragment;
  439. if (localBlockIsFragment &&
  440. ((block.target & block._buildTarget) === 0 ||
  441. (block.target & input.target) === 0 ||
  442. (this.target !== NodeMaterialBlockTargets.VertexAndFragment && otherBlockWasGeneratedInVertexShader))) {
  443. // context switch! We need a varying
  444. if ((!block.isInput && state.target !== block._buildTarget) || // block was already emitted by vertex shader
  445. (block.isInput && block.isAttribute && !block._noContextSwitch) // block is an attribute
  446. ) {
  447. const connectedPoint = input.connectedPoint;
  448. if (state._vertexState._emitVaryingFromString("v_" + connectedPoint.associatedVariableName, state._getGLType(connectedPoint.type))) {
  449. state._vertexState.compilationString += `${"v_" + connectedPoint.associatedVariableName} = ${connectedPoint.associatedVariableName};\n`;
  450. }
  451. input.associatedVariableName = "v_" + connectedPoint.associatedVariableName;
  452. input._enforceAssociatedVariableName = true;
  453. }
  454. }
  455. }
  456. /**
  457. * Validates the new name for the block node.
  458. * @param newName the new name to be given to the node.
  459. * @returns false if the name is a reserve word, else true.
  460. */
  461. validateBlockName(newName) {
  462. const reservedNames = [
  463. "position",
  464. "normal",
  465. "tangent",
  466. "particle_positionw",
  467. "uv",
  468. "uv2",
  469. "uv3",
  470. "uv4",
  471. "uv5",
  472. "uv6",
  473. "position2d",
  474. "particle_uv",
  475. "matricesIndices",
  476. "matricesWeights",
  477. "world0",
  478. "world1",
  479. "world2",
  480. "world3",
  481. "particle_color",
  482. "particle_texturemask",
  483. ];
  484. for (const reservedName of reservedNames) {
  485. if (newName === reservedName) {
  486. return false;
  487. }
  488. }
  489. return true;
  490. }
  491. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  492. _customBuildStep(state, activeBlocks) {
  493. // Must be implemented by children
  494. }
  495. /**
  496. * Compile the current node and generate the shader code
  497. * @param state defines the current compilation state (uniforms, samplers, current string)
  498. * @param activeBlocks defines the list of active blocks (i.e. blocks to compile)
  499. * @returns true if already built
  500. */
  501. build(state, activeBlocks) {
  502. if (this._buildId === state.sharedData.buildId) {
  503. return true;
  504. }
  505. if (!this.isInput) {
  506. /** Prepare outputs */
  507. for (const output of this._outputs) {
  508. if (!output.associatedVariableName) {
  509. output.associatedVariableName = state._getFreeVariableName(output.name);
  510. }
  511. }
  512. }
  513. // Check if "parent" blocks are compiled
  514. for (const input of this._inputs) {
  515. if (!input.connectedPoint) {
  516. if (!input.isOptional) {
  517. // Emit a warning
  518. state.sharedData.checks.notConnectedNonOptionalInputs.push(input);
  519. }
  520. continue;
  521. }
  522. if (this.target !== NodeMaterialBlockTargets.Neutral) {
  523. if ((input.target & this.target) === 0) {
  524. continue;
  525. }
  526. if ((input.target & state.target) === 0) {
  527. continue;
  528. }
  529. }
  530. const block = input.connectedPoint.ownerBlock;
  531. if (block && block !== this) {
  532. this._processBuild(block, state, input, activeBlocks);
  533. }
  534. }
  535. this._customBuildStep(state, activeBlocks);
  536. if (this._buildId === state.sharedData.buildId) {
  537. return true; // Need to check again as inputs can be connected multiple time to this endpoint
  538. }
  539. // Logs
  540. if (state.sharedData.verbose) {
  541. Logger.Log(`${state.target === NodeMaterialBlockTargets.Vertex ? "Vertex shader" : "Fragment shader"}: Building ${this.name} [${this.getClassName()}]`);
  542. }
  543. // Checks final outputs
  544. if (this.isFinalMerger) {
  545. switch (state.target) {
  546. case NodeMaterialBlockTargets.Vertex:
  547. state.sharedData.checks.emitVertex = true;
  548. break;
  549. case NodeMaterialBlockTargets.Fragment:
  550. state.sharedData.checks.emitFragment = true;
  551. break;
  552. }
  553. }
  554. if (!this.isInput && state.sharedData.emitComments) {
  555. state.compilationString += `\n//${this.name}\n`;
  556. }
  557. this._buildBlock(state);
  558. this._buildId = state.sharedData.buildId;
  559. this._buildTarget = state.target;
  560. // Compile connected blocks
  561. for (const output of this._outputs) {
  562. if ((output.target & state.target) === 0) {
  563. continue;
  564. }
  565. for (const endpoint of output.endpoints) {
  566. const block = endpoint.ownerBlock;
  567. if (block && (block.target & state.target) !== 0 && activeBlocks.indexOf(block) !== -1) {
  568. this._processBuild(block, state, endpoint, activeBlocks);
  569. }
  570. }
  571. }
  572. return false;
  573. }
  574. _inputRename(name) {
  575. return name;
  576. }
  577. _outputRename(name) {
  578. return name;
  579. }
  580. _dumpPropertiesCode() {
  581. const variableName = this._codeVariableName;
  582. return `${variableName}.visibleInInspector = ${this.visibleInInspector};\n${variableName}.visibleOnFrame = ${this.visibleOnFrame};\n${variableName}.target = ${this.target};\n`;
  583. }
  584. /**
  585. * @internal
  586. */
  587. _dumpCode(uniqueNames, alreadyDumped) {
  588. alreadyDumped.push(this);
  589. // Get unique name
  590. const nameAsVariableName = this.name.replace(/[^A-Za-z_]+/g, "");
  591. this._codeVariableName = nameAsVariableName || `${this.getClassName()}_${this.uniqueId}`;
  592. if (uniqueNames.indexOf(this._codeVariableName) !== -1) {
  593. let index = 0;
  594. do {
  595. index++;
  596. this._codeVariableName = nameAsVariableName + index;
  597. } while (uniqueNames.indexOf(this._codeVariableName) !== -1);
  598. }
  599. uniqueNames.push(this._codeVariableName);
  600. // Declaration
  601. let codeString = `\n// ${this.getClassName()}\n`;
  602. if (this.comments) {
  603. codeString += `// ${this.comments}\n`;
  604. }
  605. codeString += `var ${this._codeVariableName} = new BABYLON.${this.getClassName()}("${this.name}");\n`;
  606. // Properties
  607. codeString += this._dumpPropertiesCode();
  608. // Inputs
  609. for (const input of this.inputs) {
  610. if (!input.isConnected) {
  611. continue;
  612. }
  613. const connectedOutput = input.connectedPoint;
  614. const connectedBlock = connectedOutput.ownerBlock;
  615. if (alreadyDumped.indexOf(connectedBlock) === -1) {
  616. codeString += connectedBlock._dumpCode(uniqueNames, alreadyDumped);
  617. }
  618. }
  619. // Outputs
  620. for (const output of this.outputs) {
  621. if (!output.hasEndpoints) {
  622. continue;
  623. }
  624. for (const endpoint of output.endpoints) {
  625. const connectedBlock = endpoint.ownerBlock;
  626. if (connectedBlock && alreadyDumped.indexOf(connectedBlock) === -1) {
  627. codeString += connectedBlock._dumpCode(uniqueNames, alreadyDumped);
  628. }
  629. }
  630. }
  631. return codeString;
  632. }
  633. /**
  634. * @internal
  635. */
  636. _dumpCodeForOutputConnections(alreadyDumped) {
  637. let codeString = "";
  638. if (alreadyDumped.indexOf(this) !== -1) {
  639. return codeString;
  640. }
  641. alreadyDumped.push(this);
  642. for (const input of this.inputs) {
  643. if (!input.isConnected) {
  644. continue;
  645. }
  646. const connectedOutput = input.connectedPoint;
  647. const connectedBlock = connectedOutput.ownerBlock;
  648. codeString += connectedBlock._dumpCodeForOutputConnections(alreadyDumped);
  649. codeString += `${connectedBlock._codeVariableName}.${connectedBlock._outputRename(connectedOutput.name)}.connectTo(${this._codeVariableName}.${this._inputRename(input.name)});\n`;
  650. }
  651. return codeString;
  652. }
  653. /**
  654. * Clone the current block to a new identical block
  655. * @param scene defines the hosting scene
  656. * @param rootUrl defines the root URL to use to load textures and relative dependencies
  657. * @returns a copy of the current block
  658. */
  659. clone(scene, rootUrl = "") {
  660. const serializationObject = this.serialize();
  661. const blockType = GetClass(serializationObject.customType);
  662. if (blockType) {
  663. const block = new blockType();
  664. block._deserialize(serializationObject, scene, rootUrl);
  665. return block;
  666. }
  667. return null;
  668. }
  669. /**
  670. * Serializes this block in a JSON representation
  671. * @returns the serialized block object
  672. */
  673. serialize() {
  674. const serializationObject = {};
  675. serializationObject.customType = "BABYLON." + this.getClassName();
  676. serializationObject.id = this.uniqueId;
  677. serializationObject.name = this.name;
  678. serializationObject.comments = this.comments;
  679. serializationObject.visibleInInspector = this.visibleInInspector;
  680. serializationObject.visibleOnFrame = this.visibleOnFrame;
  681. serializationObject.target = this.target;
  682. serializationObject.inputs = [];
  683. serializationObject.outputs = [];
  684. for (const input of this.inputs) {
  685. serializationObject.inputs.push(input.serialize());
  686. }
  687. for (const output of this.outputs) {
  688. serializationObject.outputs.push(output.serialize(false));
  689. }
  690. return serializationObject;
  691. }
  692. /**
  693. * @internal
  694. */
  695. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  696. _deserialize(serializationObject, scene, rootUrl) {
  697. this.name = serializationObject.name;
  698. this.comments = serializationObject.comments;
  699. this.visibleInInspector = !!serializationObject.visibleInInspector;
  700. this.visibleOnFrame = !!serializationObject.visibleOnFrame;
  701. this._target = serializationObject.target ?? this.target;
  702. this._deserializePortDisplayNamesAndExposedOnFrame(serializationObject);
  703. }
  704. _deserializePortDisplayNamesAndExposedOnFrame(serializationObject) {
  705. const serializedInputs = serializationObject.inputs;
  706. const serializedOutputs = serializationObject.outputs;
  707. if (serializedInputs) {
  708. serializedInputs.forEach((port, i) => {
  709. if (port.displayName) {
  710. this.inputs[i].displayName = port.displayName;
  711. }
  712. if (port.isExposedOnFrame) {
  713. this.inputs[i].isExposedOnFrame = port.isExposedOnFrame;
  714. this.inputs[i].exposedPortPosition = port.exposedPortPosition;
  715. }
  716. });
  717. }
  718. if (serializedOutputs) {
  719. serializedOutputs.forEach((port, i) => {
  720. if (port.displayName) {
  721. this.outputs[i].displayName = port.displayName;
  722. }
  723. if (port.isExposedOnFrame) {
  724. this.outputs[i].isExposedOnFrame = port.isExposedOnFrame;
  725. this.outputs[i].exposedPortPosition = port.exposedPortPosition;
  726. }
  727. });
  728. }
  729. }
  730. /**
  731. * Release resources
  732. */
  733. dispose() {
  734. for (const input of this.inputs) {
  735. input.dispose();
  736. }
  737. for (const output of this.outputs) {
  738. output.dispose();
  739. }
  740. }
  741. }
  742. //# sourceMappingURL=nodeMaterialBlock.js.map