nodeMaterial.js 79 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887
  1. import { __decorate } from "../../tslib.es6.js";
  2. import { PushMaterial } from "../pushMaterial.js";
  3. import { AbstractMesh } from "../../Meshes/abstractMesh.js";
  4. import { Matrix, Vector2 } from "../../Maths/math.vector.js";
  5. import { Color3, Color4 } from "../../Maths/math.color.js";
  6. import { Engine } from "../../Engines/engine.js";
  7. import { NodeMaterialBuildState } from "./nodeMaterialBuildState.js";
  8. import { Effect } from "../effect.js";
  9. import { Observable } from "../../Misc/observable.js";
  10. import { NodeMaterialBlockTargets } from "./Enums/nodeMaterialBlockTargets.js";
  11. import { NodeMaterialBuildStateSharedData } from "./nodeMaterialBuildStateSharedData.js";
  12. import { MaterialDefines } from "../../Materials/materialDefines.js";
  13. import { VertexBuffer } from "../../Buffers/buffer.js";
  14. import { Tools } from "../../Misc/tools.js";
  15. import { TransformBlock } from "./Blocks/transformBlock.js";
  16. import { VertexOutputBlock } from "./Blocks/Vertex/vertexOutputBlock.js";
  17. import { FragmentOutputBlock } from "./Blocks/Fragment/fragmentOutputBlock.js";
  18. import { InputBlock } from "./Blocks/Input/inputBlock.js";
  19. import { GetClass, RegisterClass } from "../../Misc/typeStore.js";
  20. import { serialize } from "../../Misc/decorators.js";
  21. import { SerializationHelper } from "../../Misc/decorators.serialization.js";
  22. import { CurrentScreenBlock } from "./Blocks/Dual/currentScreenBlock.js";
  23. import { ParticleTextureBlock } from "./Blocks/Particle/particleTextureBlock.js";
  24. import { ParticleRampGradientBlock } from "./Blocks/Particle/particleRampGradientBlock.js";
  25. import { ParticleBlendMultiplyBlock } from "./Blocks/Particle/particleBlendMultiplyBlock.js";
  26. import { EffectFallbacks } from "../effectFallbacks.js";
  27. import { WebRequest } from "../../Misc/webRequest.js";
  28. import { PostProcess } from "../../PostProcesses/postProcess.js";
  29. import { VectorMergerBlock } from "./Blocks/vectorMergerBlock.js";
  30. import { RemapBlock } from "./Blocks/remapBlock.js";
  31. import { MultiplyBlock } from "./Blocks/multiplyBlock.js";
  32. import { NodeMaterialModes } from "./Enums/nodeMaterialModes.js";
  33. import { Texture } from "../Textures/texture.js";
  34. import { BaseParticleSystem } from "../../Particles/baseParticleSystem.js";
  35. import { ColorSplitterBlock } from "./Blocks/colorSplitterBlock.js";
  36. import { TimingTools } from "../../Misc/timingTools.js";
  37. import { ProceduralTexture } from "../Textures/Procedurals/proceduralTexture.js";
  38. import { AnimatedInputBlockTypes } from "./Blocks/Input/animatedInputBlockTypes.js";
  39. import { TrigonometryBlock, TrigonometryBlockOperations } from "./Blocks/trigonometryBlock.js";
  40. import { NodeMaterialSystemValues } from "./Enums/nodeMaterialSystemValues.js";
  41. import { EngineStore } from "../../Engines/engineStore.js";
  42. import { Logger } from "../../Misc/logger.js";
  43. import { PrepareDefinesForCamera, PrepareDefinesForPrePass } from "../materialHelper.functions.js";
  44. const onCreatedEffectParameters = { effect: null, subMesh: null };
  45. /** @internal */
  46. export class NodeMaterialDefines extends MaterialDefines {
  47. /**
  48. * Creates a new NodeMaterialDefines
  49. */
  50. constructor() {
  51. super();
  52. /** Normal */
  53. this.NORMAL = false;
  54. /** Tangent */
  55. this.TANGENT = false;
  56. /** Vertex color */
  57. this.VERTEXCOLOR_NME = false;
  58. /** Uv1 **/
  59. this.UV1 = false;
  60. /** Uv2 **/
  61. this.UV2 = false;
  62. /** Uv3 **/
  63. this.UV3 = false;
  64. /** Uv4 **/
  65. this.UV4 = false;
  66. /** Uv5 **/
  67. this.UV5 = false;
  68. /** Uv6 **/
  69. this.UV6 = false;
  70. /** Prepass **/
  71. this.PREPASS = false;
  72. /** Prepass normal */
  73. this.PREPASS_NORMAL = false;
  74. /** Prepass normal index */
  75. this.PREPASS_NORMAL_INDEX = -1;
  76. /** Prepass position */
  77. this.PREPASS_POSITION = false;
  78. /** Prepass position index */
  79. this.PREPASS_POSITION_INDEX = -1;
  80. /** Prepass depth */
  81. this.PREPASS_DEPTH = false;
  82. /** Prepass depth index */
  83. this.PREPASS_DEPTH_INDEX = -1;
  84. /** Scene MRT count */
  85. this.SCENE_MRT_COUNT = 0;
  86. /** BONES */
  87. this.NUM_BONE_INFLUENCERS = 0;
  88. /** Bones per mesh */
  89. this.BonesPerMesh = 0;
  90. /** Using texture for bone storage */
  91. this.BONETEXTURE = false;
  92. /** MORPH TARGETS */
  93. this.MORPHTARGETS = false;
  94. /** Morph target normal */
  95. this.MORPHTARGETS_NORMAL = false;
  96. /** Morph target tangent */
  97. this.MORPHTARGETS_TANGENT = false;
  98. /** Morph target uv */
  99. this.MORPHTARGETS_UV = false;
  100. /** Number of morph influencers */
  101. this.NUM_MORPH_INFLUENCERS = 0;
  102. /** Using a texture to store morph target data */
  103. this.MORPHTARGETS_TEXTURE = false;
  104. /** IMAGE PROCESSING */
  105. this.IMAGEPROCESSING = false;
  106. /** Vignette */
  107. this.VIGNETTE = false;
  108. /** Multiply blend mode for vignette */
  109. this.VIGNETTEBLENDMODEMULTIPLY = false;
  110. /** Opaque blend mode for vignette */
  111. this.VIGNETTEBLENDMODEOPAQUE = false;
  112. /** Tone mapping */
  113. this.TONEMAPPING = false;
  114. /** ACES tone mapping mode */
  115. this.TONEMAPPING_ACES = false;
  116. /** Contrast */
  117. this.CONTRAST = false;
  118. /** Exposure */
  119. this.EXPOSURE = false;
  120. /** Color curves */
  121. this.COLORCURVES = false;
  122. /** Color grading */
  123. this.COLORGRADING = false;
  124. /** 3D color grading */
  125. this.COLORGRADING3D = false;
  126. /** Sampler green depth */
  127. this.SAMPLER3DGREENDEPTH = false;
  128. /** Sampler for BGR map */
  129. this.SAMPLER3DBGRMAP = false;
  130. /** Dithering */
  131. this.DITHER = false;
  132. /** Using post process for image processing */
  133. this.IMAGEPROCESSINGPOSTPROCESS = false;
  134. /** Skip color clamp */
  135. this.SKIPFINALCOLORCLAMP = false;
  136. /** MISC. */
  137. this.BUMPDIRECTUV = 0;
  138. /** Camera is orthographic */
  139. this.CAMERA_ORTHOGRAPHIC = false;
  140. /** Camera is perspective */
  141. this.CAMERA_PERSPECTIVE = false;
  142. this.rebuild();
  143. }
  144. /**
  145. * Set the value of a specific key
  146. * @param name defines the name of the key to set
  147. * @param value defines the value to set
  148. * @param markAsUnprocessedIfDirty Flag to indicate to the cache that this value needs processing
  149. */
  150. setValue(name, value, markAsUnprocessedIfDirty = false) {
  151. if (this[name] === undefined) {
  152. this._keys.push(name);
  153. }
  154. if (markAsUnprocessedIfDirty && this[name] !== value) {
  155. this.markAsUnprocessed();
  156. }
  157. this[name] = value;
  158. }
  159. }
  160. /**
  161. * Class used to create a node based material built by assembling shader blocks
  162. */
  163. export class NodeMaterial extends PushMaterial {
  164. /**
  165. * Checks if a block is a texture block
  166. * @param block The block to check
  167. * @returns True if the block is a texture block
  168. */
  169. static _BlockIsTextureBlock(block) {
  170. return (block.getClassName() === "TextureBlock" ||
  171. block.getClassName() === "ReflectionTextureBaseBlock" ||
  172. block.getClassName() === "ReflectionTextureBlock" ||
  173. block.getClassName() === "ReflectionBlock" ||
  174. block.getClassName() === "RefractionBlock" ||
  175. block.getClassName() === "CurrentScreenBlock" ||
  176. block.getClassName() === "ParticleTextureBlock" ||
  177. block.getClassName() === "ImageSourceBlock" ||
  178. block.getClassName() === "TriPlanarBlock" ||
  179. block.getClassName() === "BiPlanarBlock" ||
  180. block.getClassName() === "PrePassTextureBlock");
  181. }
  182. /** Get the inspector from bundle or global
  183. * @returns the global NME
  184. */
  185. _getGlobalNodeMaterialEditor() {
  186. // UMD Global name detection from Webpack Bundle UMD Name.
  187. if (typeof NODEEDITOR !== "undefined") {
  188. return NODEEDITOR;
  189. }
  190. // In case of module let's check the global emitted from the editor entry point.
  191. if (typeof BABYLON !== "undefined" && typeof BABYLON.NodeEditor !== "undefined") {
  192. return BABYLON;
  193. }
  194. return undefined;
  195. }
  196. /** Gets or sets options to control the node material overall behavior */
  197. get options() {
  198. return this._options;
  199. }
  200. set options(options) {
  201. this._options = options;
  202. }
  203. /**
  204. * Gets the image processing configuration used either in this material.
  205. */
  206. get imageProcessingConfiguration() {
  207. return this._imageProcessingConfiguration;
  208. }
  209. /**
  210. * Sets the Default image processing configuration used either in the this material.
  211. *
  212. * If sets to null, the scene one is in use.
  213. */
  214. set imageProcessingConfiguration(value) {
  215. this._attachImageProcessingConfiguration(value);
  216. // Ensure the effect will be rebuilt.
  217. this._markAllSubMeshesAsTexturesDirty();
  218. }
  219. /**
  220. * Gets or sets the mode property
  221. */
  222. get mode() {
  223. return this._mode;
  224. }
  225. set mode(value) {
  226. this._mode = value;
  227. }
  228. /** Gets or sets the unique identifier used to identified the effect associated with the material */
  229. get buildId() {
  230. return this._buildId;
  231. }
  232. set buildId(value) {
  233. this._buildId = value;
  234. }
  235. /**
  236. * Create a new node based material
  237. * @param name defines the material name
  238. * @param scene defines the hosting scene
  239. * @param options defines creation option
  240. */
  241. constructor(name, scene, options = {}) {
  242. super(name, scene || EngineStore.LastCreatedScene);
  243. this._buildId = NodeMaterial._BuildIdGenerator++;
  244. this._buildWasSuccessful = false;
  245. this._cachedWorldViewMatrix = new Matrix();
  246. this._cachedWorldViewProjectionMatrix = new Matrix();
  247. this._optimizers = new Array();
  248. this._animationFrame = -1;
  249. this.BJSNODEMATERIALEDITOR = this._getGlobalNodeMaterialEditor();
  250. /**
  251. * Gets or sets data used by visual editor
  252. * @see https://nme.babylonjs.com
  253. */
  254. this.editorData = null;
  255. /**
  256. * Gets or sets a boolean indicating that alpha value must be ignored (This will turn alpha blending off even if an alpha value is produced by the material)
  257. */
  258. this.ignoreAlpha = false;
  259. /**
  260. * Defines the maximum number of lights that can be used in the material
  261. */
  262. this.maxSimultaneousLights = 4;
  263. /**
  264. * Observable raised when the material is built
  265. */
  266. this.onBuildObservable = new Observable();
  267. /**
  268. * Gets or sets the root nodes of the material vertex shader
  269. */
  270. this._vertexOutputNodes = new Array();
  271. /**
  272. * Gets or sets the root nodes of the material fragment (pixel) shader
  273. */
  274. this._fragmentOutputNodes = new Array();
  275. /**
  276. * Gets an array of blocks that needs to be serialized even if they are not yet connected
  277. */
  278. this.attachedBlocks = [];
  279. /**
  280. * Specifies the mode of the node material
  281. * @internal
  282. */
  283. this._mode = NodeMaterialModes.Material;
  284. /**
  285. * Gets or sets a boolean indicating that alpha blending must be enabled no matter what alpha value or alpha channel of the FragmentBlock are
  286. */
  287. this.forceAlphaBlending = false;
  288. this._options = {
  289. emitComments: false,
  290. ...options,
  291. };
  292. // Setup the default processing configuration to the scene.
  293. this._attachImageProcessingConfiguration(null);
  294. }
  295. /**
  296. * Gets the current class name of the material e.g. "NodeMaterial"
  297. * @returns the class name
  298. */
  299. getClassName() {
  300. return "NodeMaterial";
  301. }
  302. /**
  303. * Attaches a new image processing configuration to the Standard Material.
  304. * @param configuration
  305. */
  306. _attachImageProcessingConfiguration(configuration) {
  307. if (configuration === this._imageProcessingConfiguration) {
  308. return;
  309. }
  310. // Detaches observer.
  311. if (this._imageProcessingConfiguration && this._imageProcessingObserver) {
  312. this._imageProcessingConfiguration.onUpdateParameters.remove(this._imageProcessingObserver);
  313. }
  314. // Pick the scene configuration if needed.
  315. if (!configuration) {
  316. this._imageProcessingConfiguration = this.getScene().imageProcessingConfiguration;
  317. }
  318. else {
  319. this._imageProcessingConfiguration = configuration;
  320. }
  321. // Attaches observer.
  322. if (this._imageProcessingConfiguration) {
  323. this._imageProcessingObserver = this._imageProcessingConfiguration.onUpdateParameters.add(() => {
  324. this._markAllSubMeshesAsImageProcessingDirty();
  325. });
  326. }
  327. }
  328. /**
  329. * Get a block by its name
  330. * @param name defines the name of the block to retrieve
  331. * @returns the required block or null if not found
  332. */
  333. getBlockByName(name) {
  334. let result = null;
  335. for (const block of this.attachedBlocks) {
  336. if (block.name === name) {
  337. if (!result) {
  338. result = block;
  339. }
  340. else {
  341. Tools.Warn("More than one block was found with the name `" + name + "`");
  342. return result;
  343. }
  344. }
  345. }
  346. return result;
  347. }
  348. /**
  349. * Get a block using a predicate
  350. * @param predicate defines the predicate used to find the good candidate
  351. * @returns the required block or null if not found
  352. */
  353. getBlockByPredicate(predicate) {
  354. for (const block of this.attachedBlocks) {
  355. if (predicate(block)) {
  356. return block;
  357. }
  358. }
  359. return null;
  360. }
  361. /**
  362. * Get an input block using a predicate
  363. * @param predicate defines the predicate used to find the good candidate
  364. * @returns the required input block or null if not found
  365. */
  366. getInputBlockByPredicate(predicate) {
  367. for (const block of this.attachedBlocks) {
  368. if (block.isInput && predicate(block)) {
  369. return block;
  370. }
  371. }
  372. return null;
  373. }
  374. /**
  375. * Gets the list of input blocks attached to this material
  376. * @returns an array of InputBlocks
  377. */
  378. getInputBlocks() {
  379. const blocks = [];
  380. for (const block of this.attachedBlocks) {
  381. if (block.isInput) {
  382. blocks.push(block);
  383. }
  384. }
  385. return blocks;
  386. }
  387. /**
  388. * Adds a new optimizer to the list of optimizers
  389. * @param optimizer defines the optimizers to add
  390. * @returns the current material
  391. */
  392. registerOptimizer(optimizer) {
  393. const index = this._optimizers.indexOf(optimizer);
  394. if (index > -1) {
  395. return;
  396. }
  397. this._optimizers.push(optimizer);
  398. return this;
  399. }
  400. /**
  401. * Remove an optimizer from the list of optimizers
  402. * @param optimizer defines the optimizers to remove
  403. * @returns the current material
  404. */
  405. unregisterOptimizer(optimizer) {
  406. const index = this._optimizers.indexOf(optimizer);
  407. if (index === -1) {
  408. return;
  409. }
  410. this._optimizers.splice(index, 1);
  411. return this;
  412. }
  413. /**
  414. * Add a new block to the list of output nodes
  415. * @param node defines the node to add
  416. * @returns the current material
  417. */
  418. addOutputNode(node) {
  419. if (node.target === null) {
  420. // eslint-disable-next-line no-throw-literal
  421. throw "This node is not meant to be an output node. You may want to explicitly set its target value.";
  422. }
  423. if ((node.target & NodeMaterialBlockTargets.Vertex) !== 0) {
  424. this._addVertexOutputNode(node);
  425. }
  426. if ((node.target & NodeMaterialBlockTargets.Fragment) !== 0) {
  427. this._addFragmentOutputNode(node);
  428. }
  429. return this;
  430. }
  431. /**
  432. * Remove a block from the list of root nodes
  433. * @param node defines the node to remove
  434. * @returns the current material
  435. */
  436. removeOutputNode(node) {
  437. if (node.target === null) {
  438. return this;
  439. }
  440. if ((node.target & NodeMaterialBlockTargets.Vertex) !== 0) {
  441. this._removeVertexOutputNode(node);
  442. }
  443. if ((node.target & NodeMaterialBlockTargets.Fragment) !== 0) {
  444. this._removeFragmentOutputNode(node);
  445. }
  446. return this;
  447. }
  448. _addVertexOutputNode(node) {
  449. if (this._vertexOutputNodes.indexOf(node) !== -1) {
  450. return;
  451. }
  452. node.target = NodeMaterialBlockTargets.Vertex;
  453. this._vertexOutputNodes.push(node);
  454. return this;
  455. }
  456. _removeVertexOutputNode(node) {
  457. const index = this._vertexOutputNodes.indexOf(node);
  458. if (index === -1) {
  459. return;
  460. }
  461. this._vertexOutputNodes.splice(index, 1);
  462. return this;
  463. }
  464. _addFragmentOutputNode(node) {
  465. if (this._fragmentOutputNodes.indexOf(node) !== -1) {
  466. return;
  467. }
  468. node.target = NodeMaterialBlockTargets.Fragment;
  469. this._fragmentOutputNodes.push(node);
  470. return this;
  471. }
  472. _removeFragmentOutputNode(node) {
  473. const index = this._fragmentOutputNodes.indexOf(node);
  474. if (index === -1) {
  475. return;
  476. }
  477. this._fragmentOutputNodes.splice(index, 1);
  478. return this;
  479. }
  480. /**
  481. * Specifies if the material will require alpha blending
  482. * @returns a boolean specifying if alpha blending is needed
  483. */
  484. needAlphaBlending() {
  485. if (this.ignoreAlpha) {
  486. return false;
  487. }
  488. return this.forceAlphaBlending || this.alpha < 1.0 || (this._sharedData && this._sharedData.hints.needAlphaBlending);
  489. }
  490. /**
  491. * Specifies if this material should be rendered in alpha test mode
  492. * @returns a boolean specifying if an alpha test is needed.
  493. */
  494. needAlphaTesting() {
  495. return this._sharedData && this._sharedData.hints.needAlphaTesting;
  496. }
  497. _processInitializeOnLink(block, state, nodesToProcessForOtherBuildState, autoConfigure = true) {
  498. if (block.target === NodeMaterialBlockTargets.VertexAndFragment) {
  499. nodesToProcessForOtherBuildState.push(block);
  500. }
  501. else if (state.target === NodeMaterialBlockTargets.Fragment && block.target === NodeMaterialBlockTargets.Vertex && block._preparationId !== this._buildId) {
  502. nodesToProcessForOtherBuildState.push(block);
  503. }
  504. this._initializeBlock(block, state, nodesToProcessForOtherBuildState, autoConfigure);
  505. }
  506. _initializeBlock(node, state, nodesToProcessForOtherBuildState, autoConfigure = true) {
  507. node.initialize(state);
  508. if (autoConfigure) {
  509. node.autoConfigure(this);
  510. }
  511. node._preparationId = this._buildId;
  512. if (this.attachedBlocks.indexOf(node) === -1) {
  513. if (node.isUnique) {
  514. const className = node.getClassName();
  515. for (const other of this.attachedBlocks) {
  516. if (other.getClassName() === className) {
  517. // eslint-disable-next-line no-throw-literal
  518. throw `Cannot have multiple blocks of type ${className} in the same NodeMaterial`;
  519. }
  520. }
  521. }
  522. this.attachedBlocks.push(node);
  523. }
  524. for (const input of node.inputs) {
  525. input.associatedVariableName = "";
  526. const connectedPoint = input.connectedPoint;
  527. if (connectedPoint) {
  528. const block = connectedPoint.ownerBlock;
  529. if (block !== node) {
  530. this._processInitializeOnLink(block, state, nodesToProcessForOtherBuildState, autoConfigure);
  531. }
  532. }
  533. }
  534. // Teleportation
  535. if (node.isTeleportOut) {
  536. const teleport = node;
  537. if (teleport.entryPoint) {
  538. this._processInitializeOnLink(teleport.entryPoint, state, nodesToProcessForOtherBuildState, autoConfigure);
  539. }
  540. }
  541. for (const output of node.outputs) {
  542. output.associatedVariableName = "";
  543. }
  544. }
  545. _resetDualBlocks(node, id) {
  546. if (node.target === NodeMaterialBlockTargets.VertexAndFragment) {
  547. node.buildId = id;
  548. }
  549. for (const inputs of node.inputs) {
  550. const connectedPoint = inputs.connectedPoint;
  551. if (connectedPoint) {
  552. const block = connectedPoint.ownerBlock;
  553. if (block !== node) {
  554. this._resetDualBlocks(block, id);
  555. }
  556. }
  557. }
  558. // If this is a teleport out, we need to reset the connected block
  559. if (node.isTeleportOut) {
  560. const teleportOut = node;
  561. if (teleportOut.entryPoint) {
  562. this._resetDualBlocks(teleportOut.entryPoint, id);
  563. }
  564. }
  565. }
  566. /**
  567. * Remove a block from the current node material
  568. * @param block defines the block to remove
  569. */
  570. removeBlock(block) {
  571. const attachedBlockIndex = this.attachedBlocks.indexOf(block);
  572. if (attachedBlockIndex > -1) {
  573. this.attachedBlocks.splice(attachedBlockIndex, 1);
  574. }
  575. if (block.isFinalMerger) {
  576. this.removeOutputNode(block);
  577. }
  578. }
  579. /**
  580. * Build the material and generates the inner effect
  581. * @param verbose defines if the build should log activity
  582. * @param updateBuildId defines if the internal build Id should be updated (default is true)
  583. * @param autoConfigure defines if the autoConfigure method should be called when initializing blocks (default is false)
  584. */
  585. build(verbose = false, updateBuildId = true, autoConfigure = false) {
  586. // First time?
  587. if (!this._vertexCompilationState && !autoConfigure) {
  588. autoConfigure = true;
  589. }
  590. this._buildWasSuccessful = false;
  591. const engine = this.getScene().getEngine();
  592. const allowEmptyVertexProgram = this._mode === NodeMaterialModes.Particle;
  593. if (this._vertexOutputNodes.length === 0 && !allowEmptyVertexProgram) {
  594. // eslint-disable-next-line no-throw-literal
  595. throw "You must define at least one vertexOutputNode";
  596. }
  597. if (this._fragmentOutputNodes.length === 0) {
  598. // eslint-disable-next-line no-throw-literal
  599. throw "You must define at least one fragmentOutputNode";
  600. }
  601. // Compilation state
  602. this._vertexCompilationState = new NodeMaterialBuildState();
  603. this._vertexCompilationState.supportUniformBuffers = engine.supportsUniformBuffers;
  604. this._vertexCompilationState.target = NodeMaterialBlockTargets.Vertex;
  605. this._fragmentCompilationState = new NodeMaterialBuildState();
  606. this._fragmentCompilationState.supportUniformBuffers = engine.supportsUniformBuffers;
  607. this._fragmentCompilationState.target = NodeMaterialBlockTargets.Fragment;
  608. // Shared data
  609. this._sharedData = new NodeMaterialBuildStateSharedData();
  610. this._sharedData.nodeMaterial = this;
  611. this._sharedData.fragmentOutputNodes = this._fragmentOutputNodes;
  612. this._vertexCompilationState.sharedData = this._sharedData;
  613. this._fragmentCompilationState.sharedData = this._sharedData;
  614. this._sharedData.buildId = this._buildId;
  615. this._sharedData.emitComments = this._options.emitComments;
  616. this._sharedData.verbose = verbose;
  617. this._sharedData.scene = this.getScene();
  618. this._sharedData.allowEmptyVertexProgram = allowEmptyVertexProgram;
  619. // Initialize blocks
  620. const vertexNodes = [];
  621. const fragmentNodes = [];
  622. for (const vertexOutputNode of this._vertexOutputNodes) {
  623. vertexNodes.push(vertexOutputNode);
  624. this._initializeBlock(vertexOutputNode, this._vertexCompilationState, fragmentNodes, autoConfigure);
  625. }
  626. for (const fragmentOutputNode of this._fragmentOutputNodes) {
  627. fragmentNodes.push(fragmentOutputNode);
  628. this._initializeBlock(fragmentOutputNode, this._fragmentCompilationState, vertexNodes, autoConfigure);
  629. }
  630. // Optimize
  631. this.optimize();
  632. // Vertex
  633. for (const vertexOutputNode of vertexNodes) {
  634. vertexOutputNode.build(this._vertexCompilationState, vertexNodes);
  635. }
  636. // Fragment
  637. this._fragmentCompilationState.uniforms = this._vertexCompilationState.uniforms.slice(0);
  638. this._fragmentCompilationState._uniformDeclaration = this._vertexCompilationState._uniformDeclaration;
  639. this._fragmentCompilationState._constantDeclaration = this._vertexCompilationState._constantDeclaration;
  640. this._fragmentCompilationState._vertexState = this._vertexCompilationState;
  641. for (const fragmentOutputNode of fragmentNodes) {
  642. this._resetDualBlocks(fragmentOutputNode, this._buildId - 1);
  643. }
  644. for (const fragmentOutputNode of fragmentNodes) {
  645. fragmentOutputNode.build(this._fragmentCompilationState, fragmentNodes);
  646. }
  647. // Finalize
  648. this._vertexCompilationState.finalize(this._vertexCompilationState);
  649. this._fragmentCompilationState.finalize(this._fragmentCompilationState);
  650. if (updateBuildId) {
  651. this._buildId = NodeMaterial._BuildIdGenerator++;
  652. }
  653. // Errors
  654. this._sharedData.emitErrors();
  655. if (verbose) {
  656. Logger.Log("Vertex shader:");
  657. Logger.Log(this._vertexCompilationState.compilationString);
  658. Logger.Log("Fragment shader:");
  659. Logger.Log(this._fragmentCompilationState.compilationString);
  660. }
  661. this._buildWasSuccessful = true;
  662. this.onBuildObservable.notifyObservers(this);
  663. // Wipe defines
  664. const meshes = this.getScene().meshes;
  665. for (const mesh of meshes) {
  666. if (!mesh.subMeshes) {
  667. continue;
  668. }
  669. for (const subMesh of mesh.subMeshes) {
  670. if (subMesh.getMaterial() !== this) {
  671. continue;
  672. }
  673. if (!subMesh.materialDefines) {
  674. continue;
  675. }
  676. const defines = subMesh.materialDefines;
  677. defines.markAllAsDirty();
  678. defines.reset();
  679. }
  680. }
  681. if (this.prePassTextureInputs.length) {
  682. this.getScene().enablePrePassRenderer();
  683. }
  684. const prePassRenderer = this.getScene().prePassRenderer;
  685. if (prePassRenderer) {
  686. prePassRenderer.markAsDirty();
  687. }
  688. }
  689. /**
  690. * Runs an otpimization phase to try to improve the shader code
  691. */
  692. optimize() {
  693. for (const optimizer of this._optimizers) {
  694. optimizer.optimize(this._vertexOutputNodes, this._fragmentOutputNodes);
  695. }
  696. }
  697. _prepareDefinesForAttributes(mesh, defines) {
  698. const oldNormal = defines["NORMAL"];
  699. const oldTangent = defines["TANGENT"];
  700. const oldColor = defines["VERTEXCOLOR_NME"];
  701. defines["NORMAL"] = mesh.isVerticesDataPresent(VertexBuffer.NormalKind);
  702. defines["TANGENT"] = mesh.isVerticesDataPresent(VertexBuffer.TangentKind);
  703. const hasVertexColors = mesh.useVertexColors && mesh.isVerticesDataPresent(VertexBuffer.ColorKind);
  704. defines["VERTEXCOLOR_NME"] = hasVertexColors;
  705. let uvChanged = false;
  706. for (let i = 1; i <= 6; ++i) {
  707. const oldUV = defines["UV" + i];
  708. defines["UV" + i] = mesh.isVerticesDataPresent(`uv${i === 1 ? "" : i}`);
  709. uvChanged = uvChanged || defines["UV" + i] !== oldUV;
  710. }
  711. // PrePass
  712. const oit = this.needAlphaBlendingForMesh(mesh) && this.getScene().useOrderIndependentTransparency;
  713. PrepareDefinesForPrePass(this.getScene(), defines, !oit);
  714. if (oldNormal !== defines["NORMAL"] || oldTangent !== defines["TANGENT"] || oldColor !== defines["VERTEXCOLOR_NME"] || uvChanged) {
  715. defines.markAsAttributesDirty();
  716. }
  717. }
  718. /**
  719. * Can this material render to prepass
  720. */
  721. get isPrePassCapable() {
  722. return true;
  723. }
  724. /**
  725. * Outputs written to the prepass
  726. */
  727. get prePassTextureOutputs() {
  728. const prePassOutputBlock = this.getBlockByPredicate((block) => block.getClassName() === "PrePassOutputBlock");
  729. const result = [4];
  730. if (!prePassOutputBlock) {
  731. return result;
  732. }
  733. // Cannot write to prepass if we alread read from prepass
  734. if (this.prePassTextureInputs.length) {
  735. return result;
  736. }
  737. if (prePassOutputBlock.viewDepth.isConnected) {
  738. result.push(5);
  739. }
  740. if (prePassOutputBlock.viewNormal.isConnected) {
  741. result.push(6);
  742. }
  743. if (prePassOutputBlock.worldPosition.isConnected) {
  744. result.push(1);
  745. }
  746. return result;
  747. }
  748. /**
  749. * Gets the list of prepass texture required
  750. */
  751. get prePassTextureInputs() {
  752. const prePassTextureBlocks = this.getAllTextureBlocks().filter((block) => block.getClassName() === "PrePassTextureBlock");
  753. const result = [];
  754. for (const block of prePassTextureBlocks) {
  755. if (block.position.isConnected && !result.includes(1)) {
  756. result.push(1);
  757. }
  758. if (block.depth.isConnected && !result.includes(5)) {
  759. result.push(5);
  760. }
  761. if (block.normal.isConnected && !result.includes(6)) {
  762. result.push(6);
  763. }
  764. }
  765. return result;
  766. }
  767. /**
  768. * Sets the required values to the prepass renderer.
  769. * @param prePassRenderer defines the prepass renderer to set
  770. * @returns true if the pre pass is needed
  771. */
  772. setPrePassRenderer(prePassRenderer) {
  773. const prePassTexturesRequired = this.prePassTextureInputs.concat(this.prePassTextureOutputs);
  774. if (prePassRenderer && prePassTexturesRequired.length > 1) {
  775. let cfg = prePassRenderer.getEffectConfiguration("nodeMaterial");
  776. if (!cfg) {
  777. cfg = prePassRenderer.addEffectConfiguration({
  778. enabled: true,
  779. needsImageProcessing: false,
  780. name: "nodeMaterial",
  781. texturesRequired: [],
  782. });
  783. }
  784. for (const prePassTexture of prePassTexturesRequired) {
  785. if (!cfg.texturesRequired.includes(prePassTexture)) {
  786. cfg.texturesRequired.push(prePassTexture);
  787. }
  788. }
  789. cfg.enabled = true;
  790. }
  791. // COLOR_TEXTURE is always required for prepass, length > 1 means
  792. // we actually need to write to special prepass textures
  793. return prePassTexturesRequired.length > 1;
  794. }
  795. /**
  796. * Create a post process from the material
  797. * @param camera The camera to apply the render pass to.
  798. * @param options The required width/height ratio to downsize to before computing the render pass. (Use 1.0 for full size)
  799. * @param samplingMode The sampling mode to be used when computing the pass. (default: 0)
  800. * @param engine The engine which the post process will be applied. (default: current engine)
  801. * @param reusable If the post process can be reused on the same frame. (default: false)
  802. * @param textureType Type of textures used when performing the post process. (default: 0)
  803. * @param textureFormat Format of textures used when performing the post process. (default: TEXTUREFORMAT_RGBA)
  804. * @returns the post process created
  805. */
  806. createPostProcess(camera, options = 1, samplingMode = 1, engine, reusable, textureType = 0, textureFormat = 5) {
  807. if (this.mode !== NodeMaterialModes.PostProcess) {
  808. Logger.Log("Incompatible material mode");
  809. return null;
  810. }
  811. return this._createEffectForPostProcess(null, camera, options, samplingMode, engine, reusable, textureType, textureFormat);
  812. }
  813. /**
  814. * Create the post process effect from the material
  815. * @param postProcess The post process to create the effect for
  816. */
  817. createEffectForPostProcess(postProcess) {
  818. this._createEffectForPostProcess(postProcess);
  819. }
  820. _createEffectForPostProcess(postProcess, camera, options = 1, samplingMode = 1, engine, reusable, textureType = 0, textureFormat = 5) {
  821. let tempName = this.name + this._buildId;
  822. const defines = new NodeMaterialDefines();
  823. const dummyMesh = new AbstractMesh(tempName + "PostProcess", this.getScene());
  824. let buildId = this._buildId;
  825. this._processDefines(dummyMesh, defines);
  826. Effect.RegisterShader(tempName, this._fragmentCompilationState._builtCompilationString, this._vertexCompilationState._builtCompilationString);
  827. if (!postProcess) {
  828. postProcess = new PostProcess(this.name + "PostProcess", tempName, this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers, options, camera, samplingMode, engine, reusable, defines.toString(), textureType, tempName, { maxSimultaneousLights: this.maxSimultaneousLights }, false, textureFormat);
  829. }
  830. else {
  831. postProcess.updateEffect(defines.toString(), this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers, { maxSimultaneousLights: this.maxSimultaneousLights }, undefined, undefined, tempName, tempName);
  832. }
  833. postProcess.nodeMaterialSource = this;
  834. postProcess.onApplyObservable.add((effect) => {
  835. if (buildId !== this._buildId) {
  836. delete Effect.ShadersStore[tempName + "VertexShader"];
  837. delete Effect.ShadersStore[tempName + "PixelShader"];
  838. tempName = this.name + this._buildId;
  839. defines.markAllAsDirty();
  840. buildId = this._buildId;
  841. }
  842. const result = this._processDefines(dummyMesh, defines);
  843. if (result) {
  844. Effect.RegisterShader(tempName, this._fragmentCompilationState._builtCompilationString, this._vertexCompilationState._builtCompilationString);
  845. TimingTools.SetImmediate(() => postProcess.updateEffect(defines.toString(), this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers, { maxSimultaneousLights: this.maxSimultaneousLights }, undefined, undefined, tempName, tempName));
  846. }
  847. this._checkInternals(effect);
  848. });
  849. return postProcess;
  850. }
  851. /**
  852. * Create a new procedural texture based on this node material
  853. * @param size defines the size of the texture
  854. * @param scene defines the hosting scene
  855. * @returns the new procedural texture attached to this node material
  856. */
  857. createProceduralTexture(size, scene) {
  858. if (this.mode !== NodeMaterialModes.ProceduralTexture) {
  859. Logger.Log("Incompatible material mode");
  860. return null;
  861. }
  862. let tempName = this.name + this._buildId;
  863. const proceduralTexture = new ProceduralTexture(tempName, size, null, scene);
  864. const dummyMesh = new AbstractMesh(tempName + "Procedural", this.getScene());
  865. dummyMesh.reservedDataStore = {
  866. hidden: true,
  867. };
  868. const defines = new NodeMaterialDefines();
  869. const result = this._processDefines(dummyMesh, defines);
  870. Effect.RegisterShader(tempName, this._fragmentCompilationState._builtCompilationString, this._vertexCompilationState._builtCompilationString);
  871. let effect = this.getScene().getEngine().createEffect({
  872. vertexElement: tempName,
  873. fragmentElement: tempName,
  874. }, [VertexBuffer.PositionKind], this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers, defines.toString(), result?.fallbacks, undefined);
  875. proceduralTexture.nodeMaterialSource = this;
  876. proceduralTexture._setEffect(effect);
  877. let buildId = this._buildId;
  878. proceduralTexture.onBeforeGenerationObservable.add(() => {
  879. if (buildId !== this._buildId) {
  880. delete Effect.ShadersStore[tempName + "VertexShader"];
  881. delete Effect.ShadersStore[tempName + "PixelShader"];
  882. tempName = this.name + this._buildId;
  883. defines.markAllAsDirty();
  884. buildId = this._buildId;
  885. }
  886. const result = this._processDefines(dummyMesh, defines);
  887. if (result) {
  888. Effect.RegisterShader(tempName, this._fragmentCompilationState._builtCompilationString, this._vertexCompilationState._builtCompilationString);
  889. TimingTools.SetImmediate(() => {
  890. effect = this.getScene().getEngine().createEffect({
  891. vertexElement: tempName,
  892. fragmentElement: tempName,
  893. }, [VertexBuffer.PositionKind], this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers, defines.toString(), result?.fallbacks, undefined);
  894. proceduralTexture._setEffect(effect);
  895. });
  896. }
  897. this._checkInternals(effect);
  898. });
  899. return proceduralTexture;
  900. }
  901. _createEffectForParticles(particleSystem, blendMode, onCompiled, onError, effect, defines, dummyMesh, particleSystemDefinesJoined = "") {
  902. let tempName = this.name + this._buildId + "_" + blendMode;
  903. if (!defines) {
  904. defines = new NodeMaterialDefines();
  905. }
  906. if (!dummyMesh) {
  907. dummyMesh = this.getScene().getMeshByName(this.name + "Particle");
  908. if (!dummyMesh) {
  909. dummyMesh = new AbstractMesh(this.name + "Particle", this.getScene());
  910. dummyMesh.reservedDataStore = {
  911. hidden: true,
  912. };
  913. }
  914. }
  915. let buildId = this._buildId;
  916. const particleSystemDefines = [];
  917. let join = particleSystemDefinesJoined;
  918. if (!effect) {
  919. const result = this._processDefines(dummyMesh, defines);
  920. Effect.RegisterShader(tempName, this._fragmentCompilationState._builtCompilationString);
  921. particleSystem.fillDefines(particleSystemDefines, blendMode);
  922. join = particleSystemDefines.join("\n");
  923. effect = this.getScene()
  924. .getEngine()
  925. .createEffectForParticles(tempName, this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers, defines.toString() + "\n" + join, result?.fallbacks, onCompiled, onError, particleSystem);
  926. particleSystem.setCustomEffect(effect, blendMode);
  927. }
  928. effect.onBindObservable.add((effect) => {
  929. if (buildId !== this._buildId) {
  930. delete Effect.ShadersStore[tempName + "PixelShader"];
  931. tempName = this.name + this._buildId + "_" + blendMode;
  932. defines.markAllAsDirty();
  933. buildId = this._buildId;
  934. }
  935. particleSystemDefines.length = 0;
  936. particleSystem.fillDefines(particleSystemDefines, blendMode);
  937. const particleSystemDefinesJoinedCurrent = particleSystemDefines.join("\n");
  938. if (particleSystemDefinesJoinedCurrent !== join) {
  939. defines.markAllAsDirty();
  940. join = particleSystemDefinesJoinedCurrent;
  941. }
  942. const result = this._processDefines(dummyMesh, defines);
  943. if (result) {
  944. Effect.RegisterShader(tempName, this._fragmentCompilationState._builtCompilationString);
  945. effect = this.getScene()
  946. .getEngine()
  947. .createEffectForParticles(tempName, this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers, defines.toString() + "\n" + join, result?.fallbacks, onCompiled, onError, particleSystem);
  948. particleSystem.setCustomEffect(effect, blendMode);
  949. this._createEffectForParticles(particleSystem, blendMode, onCompiled, onError, effect, defines, dummyMesh, particleSystemDefinesJoined); // add the effect.onBindObservable observer
  950. return;
  951. }
  952. this._checkInternals(effect);
  953. });
  954. }
  955. _checkInternals(effect) {
  956. // Animated blocks
  957. if (this._sharedData.animatedInputs) {
  958. const scene = this.getScene();
  959. const frameId = scene.getFrameId();
  960. if (this._animationFrame !== frameId) {
  961. for (const input of this._sharedData.animatedInputs) {
  962. input.animate(scene);
  963. }
  964. this._animationFrame = frameId;
  965. }
  966. }
  967. // Bindable blocks
  968. for (const block of this._sharedData.bindableBlocks) {
  969. block.bind(effect, this);
  970. }
  971. // Connection points
  972. for (const inputBlock of this._sharedData.inputBlocks) {
  973. inputBlock._transmit(effect, this.getScene(), this);
  974. }
  975. }
  976. /**
  977. * Create the effect to be used as the custom effect for a particle system
  978. * @param particleSystem Particle system to create the effect for
  979. * @param onCompiled defines a function to call when the effect creation is successful
  980. * @param onError defines a function to call when the effect creation has failed
  981. */
  982. createEffectForParticles(particleSystem, onCompiled, onError) {
  983. if (this.mode !== NodeMaterialModes.Particle) {
  984. Logger.Log("Incompatible material mode");
  985. return;
  986. }
  987. this._createEffectForParticles(particleSystem, BaseParticleSystem.BLENDMODE_ONEONE, onCompiled, onError);
  988. this._createEffectForParticles(particleSystem, BaseParticleSystem.BLENDMODE_MULTIPLY, onCompiled, onError);
  989. }
  990. /**
  991. * Use this material as the shadow depth wrapper of a target material
  992. * @param targetMaterial defines the target material
  993. */
  994. createAsShadowDepthWrapper(targetMaterial) {
  995. if (this.mode !== NodeMaterialModes.Material) {
  996. Logger.Log("Incompatible material mode");
  997. return;
  998. }
  999. targetMaterial.shadowDepthWrapper = new BABYLON.ShadowDepthWrapper(this, this.getScene());
  1000. }
  1001. _processDefines(mesh, defines, useInstances = false, subMesh) {
  1002. let result = null;
  1003. // Global defines
  1004. const scene = this.getScene();
  1005. if (PrepareDefinesForCamera(scene, defines)) {
  1006. defines.markAsMiscDirty();
  1007. }
  1008. // Shared defines
  1009. this._sharedData.blocksWithDefines.forEach((b) => {
  1010. b.initializeDefines(mesh, this, defines, useInstances);
  1011. });
  1012. this._sharedData.blocksWithDefines.forEach((b) => {
  1013. b.prepareDefines(mesh, this, defines, useInstances, subMesh);
  1014. });
  1015. // Need to recompile?
  1016. if (defines.isDirty) {
  1017. const lightDisposed = defines._areLightsDisposed;
  1018. defines.markAsProcessed();
  1019. // Repeatable content generators
  1020. this._vertexCompilationState.compilationString = this._vertexCompilationState._builtCompilationString;
  1021. this._fragmentCompilationState.compilationString = this._fragmentCompilationState._builtCompilationString;
  1022. this._sharedData.repeatableContentBlocks.forEach((b) => {
  1023. b.replaceRepeatableContent(this._vertexCompilationState, this._fragmentCompilationState, mesh, defines);
  1024. });
  1025. // Uniforms
  1026. const uniformBuffers = [];
  1027. this._sharedData.dynamicUniformBlocks.forEach((b) => {
  1028. b.updateUniformsAndSamples(this._vertexCompilationState, this, defines, uniformBuffers);
  1029. });
  1030. const mergedUniforms = this._vertexCompilationState.uniforms;
  1031. this._fragmentCompilationState.uniforms.forEach((u) => {
  1032. const index = mergedUniforms.indexOf(u);
  1033. if (index === -1) {
  1034. mergedUniforms.push(u);
  1035. }
  1036. });
  1037. // Samplers
  1038. const mergedSamplers = this._vertexCompilationState.samplers;
  1039. this._fragmentCompilationState.samplers.forEach((s) => {
  1040. const index = mergedSamplers.indexOf(s);
  1041. if (index === -1) {
  1042. mergedSamplers.push(s);
  1043. }
  1044. });
  1045. const fallbacks = new EffectFallbacks();
  1046. this._sharedData.blocksWithFallbacks.forEach((b) => {
  1047. b.provideFallbacks(mesh, fallbacks);
  1048. });
  1049. result = {
  1050. lightDisposed,
  1051. uniformBuffers,
  1052. mergedUniforms,
  1053. mergedSamplers,
  1054. fallbacks,
  1055. };
  1056. }
  1057. return result;
  1058. }
  1059. /**
  1060. * Get if the submesh is ready to be used and all its information available.
  1061. * Child classes can use it to update shaders
  1062. * @param mesh defines the mesh to check
  1063. * @param subMesh defines which submesh to check
  1064. * @param useInstances specifies that instances should be used
  1065. * @returns a boolean indicating that the submesh is ready or not
  1066. */
  1067. isReadyForSubMesh(mesh, subMesh, useInstances = false) {
  1068. if (!this._buildWasSuccessful) {
  1069. return false;
  1070. }
  1071. const scene = this.getScene();
  1072. if (this._sharedData.animatedInputs) {
  1073. const frameId = scene.getFrameId();
  1074. if (this._animationFrame !== frameId) {
  1075. for (const input of this._sharedData.animatedInputs) {
  1076. input.animate(scene);
  1077. }
  1078. this._animationFrame = frameId;
  1079. }
  1080. }
  1081. const drawWrapper = subMesh._drawWrapper;
  1082. if (drawWrapper.effect && this.isFrozen) {
  1083. if (drawWrapper._wasPreviouslyReady && drawWrapper._wasPreviouslyUsingInstances === useInstances) {
  1084. return true;
  1085. }
  1086. }
  1087. if (!subMesh.materialDefines) {
  1088. subMesh.materialDefines = new NodeMaterialDefines();
  1089. }
  1090. const defines = subMesh.materialDefines;
  1091. if (this._isReadyForSubMesh(subMesh)) {
  1092. return true;
  1093. }
  1094. const engine = scene.getEngine();
  1095. this._prepareDefinesForAttributes(mesh, defines);
  1096. // Check if blocks are ready
  1097. if (this._sharedData.blockingBlocks.some((b) => !b.isReady(mesh, this, defines, useInstances))) {
  1098. return false;
  1099. }
  1100. const result = this._processDefines(mesh, defines, useInstances, subMesh);
  1101. if (result) {
  1102. const previousEffect = subMesh.effect;
  1103. // Compilation
  1104. const join = defines.toString();
  1105. let effect = engine.createEffect({
  1106. vertex: "nodeMaterial" + this._buildId,
  1107. fragment: "nodeMaterial" + this._buildId,
  1108. vertexSource: this._vertexCompilationState.compilationString,
  1109. fragmentSource: this._fragmentCompilationState.compilationString,
  1110. }, {
  1111. attributes: this._vertexCompilationState.attributes,
  1112. uniformsNames: result.mergedUniforms,
  1113. uniformBuffersNames: result.uniformBuffers,
  1114. samplers: result.mergedSamplers,
  1115. defines: join,
  1116. fallbacks: result.fallbacks,
  1117. onCompiled: this.onCompiled,
  1118. onError: this.onError,
  1119. multiTarget: defines.PREPASS,
  1120. indexParameters: { maxSimultaneousLights: this.maxSimultaneousLights, maxSimultaneousMorphTargets: defines.NUM_MORPH_INFLUENCERS },
  1121. }, engine);
  1122. if (effect) {
  1123. if (this._onEffectCreatedObservable) {
  1124. onCreatedEffectParameters.effect = effect;
  1125. onCreatedEffectParameters.subMesh = subMesh;
  1126. this._onEffectCreatedObservable.notifyObservers(onCreatedEffectParameters);
  1127. }
  1128. // Use previous effect while new one is compiling
  1129. if (this.allowShaderHotSwapping && previousEffect && !effect.isReady()) {
  1130. effect = previousEffect;
  1131. defines.markAsUnprocessed();
  1132. if (result.lightDisposed) {
  1133. // re register in case it takes more than one frame.
  1134. defines._areLightsDisposed = true;
  1135. return false;
  1136. }
  1137. }
  1138. else {
  1139. scene.resetCachedMaterial();
  1140. subMesh.setEffect(effect, defines, this._materialContext);
  1141. }
  1142. }
  1143. }
  1144. if (!subMesh.effect || !subMesh.effect.isReady()) {
  1145. return false;
  1146. }
  1147. defines._renderId = scene.getRenderId();
  1148. drawWrapper._wasPreviouslyReady = true;
  1149. drawWrapper._wasPreviouslyUsingInstances = useInstances;
  1150. this._checkScenePerformancePriority();
  1151. return true;
  1152. }
  1153. /**
  1154. * Get a string representing the shaders built by the current node graph
  1155. */
  1156. get compiledShaders() {
  1157. return `// Vertex shader\n${this._vertexCompilationState.compilationString}\n\n// Fragment shader\n${this._fragmentCompilationState.compilationString}`;
  1158. }
  1159. /**
  1160. * Binds the world matrix to the material
  1161. * @param world defines the world transformation matrix
  1162. */
  1163. bindOnlyWorldMatrix(world) {
  1164. const scene = this.getScene();
  1165. if (!this._activeEffect) {
  1166. return;
  1167. }
  1168. const hints = this._sharedData.hints;
  1169. if (hints.needWorldViewMatrix) {
  1170. world.multiplyToRef(scene.getViewMatrix(), this._cachedWorldViewMatrix);
  1171. }
  1172. if (hints.needWorldViewProjectionMatrix) {
  1173. world.multiplyToRef(scene.getTransformMatrix(), this._cachedWorldViewProjectionMatrix);
  1174. }
  1175. // Connection points
  1176. for (const inputBlock of this._sharedData.inputBlocks) {
  1177. inputBlock._transmitWorld(this._activeEffect, world, this._cachedWorldViewMatrix, this._cachedWorldViewProjectionMatrix);
  1178. }
  1179. }
  1180. /**
  1181. * Binds the submesh to this material by preparing the effect and shader to draw
  1182. * @param world defines the world transformation matrix
  1183. * @param mesh defines the mesh containing the submesh
  1184. * @param subMesh defines the submesh to bind the material to
  1185. */
  1186. bindForSubMesh(world, mesh, subMesh) {
  1187. const scene = this.getScene();
  1188. const effect = subMesh.effect;
  1189. if (!effect) {
  1190. return;
  1191. }
  1192. this._activeEffect = effect;
  1193. // Matrices
  1194. this.bindOnlyWorldMatrix(world);
  1195. const mustRebind = this._mustRebind(scene, effect, subMesh, mesh.visibility);
  1196. const sharedData = this._sharedData;
  1197. if (mustRebind) {
  1198. // Bindable blocks
  1199. for (const block of sharedData.bindableBlocks) {
  1200. block.bind(effect, this, mesh, subMesh);
  1201. }
  1202. for (const block of sharedData.forcedBindableBlocks) {
  1203. block.bind(effect, this, mesh, subMesh);
  1204. }
  1205. // Connection points
  1206. for (const inputBlock of sharedData.inputBlocks) {
  1207. inputBlock._transmit(effect, scene, this);
  1208. }
  1209. }
  1210. else if (!this.isFrozen) {
  1211. for (const block of sharedData.forcedBindableBlocks) {
  1212. block.bind(effect, this, mesh, subMesh);
  1213. }
  1214. }
  1215. this._afterBind(mesh, this._activeEffect, subMesh);
  1216. }
  1217. /**
  1218. * Gets the active textures from the material
  1219. * @returns an array of textures
  1220. */
  1221. getActiveTextures() {
  1222. const activeTextures = super.getActiveTextures();
  1223. if (this._sharedData) {
  1224. activeTextures.push(...this._sharedData.textureBlocks.filter((tb) => tb.texture).map((tb) => tb.texture));
  1225. }
  1226. return activeTextures;
  1227. }
  1228. /**
  1229. * Gets the list of texture blocks
  1230. * Note that this method will only return blocks that are reachable from the final block(s) and only after the material has been built!
  1231. * @returns an array of texture blocks
  1232. */
  1233. getTextureBlocks() {
  1234. if (!this._sharedData) {
  1235. return [];
  1236. }
  1237. return this._sharedData.textureBlocks;
  1238. }
  1239. /**
  1240. * Gets the list of all texture blocks
  1241. * Note that this method will scan all attachedBlocks and return blocks that are texture blocks
  1242. * @returns
  1243. */
  1244. getAllTextureBlocks() {
  1245. const textureBlocks = [];
  1246. for (const block of this.attachedBlocks) {
  1247. if (NodeMaterial._BlockIsTextureBlock(block)) {
  1248. textureBlocks.push(block);
  1249. }
  1250. }
  1251. return textureBlocks;
  1252. }
  1253. /**
  1254. * Specifies if the material uses a texture
  1255. * @param texture defines the texture to check against the material
  1256. * @returns a boolean specifying if the material uses the texture
  1257. */
  1258. hasTexture(texture) {
  1259. if (super.hasTexture(texture)) {
  1260. return true;
  1261. }
  1262. if (!this._sharedData) {
  1263. return false;
  1264. }
  1265. for (const t of this._sharedData.textureBlocks) {
  1266. if (t.texture === texture) {
  1267. return true;
  1268. }
  1269. }
  1270. return false;
  1271. }
  1272. /**
  1273. * Disposes the material
  1274. * @param forceDisposeEffect specifies if effects should be forcefully disposed
  1275. * @param forceDisposeTextures specifies if textures should be forcefully disposed
  1276. * @param notBoundToMesh specifies if the material that is being disposed is known to be not bound to any mesh
  1277. */
  1278. dispose(forceDisposeEffect, forceDisposeTextures, notBoundToMesh) {
  1279. if (forceDisposeTextures) {
  1280. for (const texture of this.getTextureBlocks()
  1281. .filter((tb) => tb.texture)
  1282. .map((tb) => tb.texture)) {
  1283. texture.dispose();
  1284. }
  1285. }
  1286. for (const block of this.attachedBlocks) {
  1287. block.dispose();
  1288. }
  1289. this.attachedBlocks.length = 0;
  1290. this._sharedData = null;
  1291. this._vertexCompilationState = null;
  1292. this._fragmentCompilationState = null;
  1293. this.onBuildObservable.clear();
  1294. if (this._imageProcessingObserver) {
  1295. this._imageProcessingConfiguration.onUpdateParameters.remove(this._imageProcessingObserver);
  1296. this._imageProcessingObserver = null;
  1297. }
  1298. super.dispose(forceDisposeEffect, forceDisposeTextures, notBoundToMesh);
  1299. }
  1300. /** Creates the node editor window.
  1301. * @param additionalConfig Define the configuration of the editor
  1302. */
  1303. _createNodeEditor(additionalConfig) {
  1304. const nodeEditorConfig = {
  1305. nodeMaterial: this,
  1306. ...additionalConfig,
  1307. };
  1308. this.BJSNODEMATERIALEDITOR.NodeEditor.Show(nodeEditorConfig);
  1309. }
  1310. /**
  1311. * Launch the node material editor
  1312. * @param config Define the configuration of the editor
  1313. * @returns a promise fulfilled when the node editor is visible
  1314. */
  1315. edit(config) {
  1316. return new Promise((resolve) => {
  1317. this.BJSNODEMATERIALEDITOR = this.BJSNODEMATERIALEDITOR || this._getGlobalNodeMaterialEditor();
  1318. if (typeof this.BJSNODEMATERIALEDITOR == "undefined") {
  1319. const editorUrl = config && config.editorURL ? config.editorURL : NodeMaterial.EditorURL;
  1320. // Load editor and add it to the DOM
  1321. Tools.LoadBabylonScript(editorUrl, () => {
  1322. this.BJSNODEMATERIALEDITOR = this.BJSNODEMATERIALEDITOR || this._getGlobalNodeMaterialEditor();
  1323. this._createNodeEditor(config?.nodeEditorConfig);
  1324. resolve();
  1325. });
  1326. }
  1327. else {
  1328. // Otherwise creates the editor
  1329. this._createNodeEditor(config?.nodeEditorConfig);
  1330. resolve();
  1331. }
  1332. });
  1333. }
  1334. /**
  1335. * Clear the current material
  1336. */
  1337. clear() {
  1338. this._vertexOutputNodes.length = 0;
  1339. this._fragmentOutputNodes.length = 0;
  1340. this.attachedBlocks.length = 0;
  1341. }
  1342. /**
  1343. * Clear the current material and set it to a default state
  1344. */
  1345. setToDefault() {
  1346. this.clear();
  1347. this.editorData = null;
  1348. const positionInput = new InputBlock("Position");
  1349. positionInput.setAsAttribute("position");
  1350. const worldInput = new InputBlock("World");
  1351. worldInput.setAsSystemValue(NodeMaterialSystemValues.World);
  1352. const worldPos = new TransformBlock("WorldPos");
  1353. positionInput.connectTo(worldPos);
  1354. worldInput.connectTo(worldPos);
  1355. const viewProjectionInput = new InputBlock("ViewProjection");
  1356. viewProjectionInput.setAsSystemValue(NodeMaterialSystemValues.ViewProjection);
  1357. const worldPosdMultipliedByViewProjection = new TransformBlock("WorldPos * ViewProjectionTransform");
  1358. worldPos.connectTo(worldPosdMultipliedByViewProjection);
  1359. viewProjectionInput.connectTo(worldPosdMultipliedByViewProjection);
  1360. const vertexOutput = new VertexOutputBlock("VertexOutput");
  1361. worldPosdMultipliedByViewProjection.connectTo(vertexOutput);
  1362. // Pixel
  1363. const pixelColor = new InputBlock("color");
  1364. pixelColor.value = new Color4(0.8, 0.8, 0.8, 1);
  1365. const fragmentOutput = new FragmentOutputBlock("FragmentOutput");
  1366. pixelColor.connectTo(fragmentOutput);
  1367. // Add to nodes
  1368. this.addOutputNode(vertexOutput);
  1369. this.addOutputNode(fragmentOutput);
  1370. this._mode = NodeMaterialModes.Material;
  1371. }
  1372. /**
  1373. * Clear the current material and set it to a default state for post process
  1374. */
  1375. setToDefaultPostProcess() {
  1376. this.clear();
  1377. this.editorData = null;
  1378. const position = new InputBlock("Position");
  1379. position.setAsAttribute("position2d");
  1380. const const1 = new InputBlock("Constant1");
  1381. const1.isConstant = true;
  1382. const1.value = 1;
  1383. const vmerger = new VectorMergerBlock("Position3D");
  1384. position.connectTo(vmerger);
  1385. const1.connectTo(vmerger, { input: "w" });
  1386. const vertexOutput = new VertexOutputBlock("VertexOutput");
  1387. vmerger.connectTo(vertexOutput);
  1388. // Pixel
  1389. const scale = new InputBlock("Scale");
  1390. scale.visibleInInspector = true;
  1391. scale.value = new Vector2(1, 1);
  1392. const uv0 = new RemapBlock("uv0");
  1393. position.connectTo(uv0);
  1394. const uv = new MultiplyBlock("UV scale");
  1395. uv0.connectTo(uv);
  1396. scale.connectTo(uv);
  1397. const currentScreen = new CurrentScreenBlock("CurrentScreen");
  1398. uv.connectTo(currentScreen);
  1399. currentScreen.texture = new Texture("https://assets.babylonjs.com/nme/currentScreenPostProcess.png", this.getScene());
  1400. const fragmentOutput = new FragmentOutputBlock("FragmentOutput");
  1401. currentScreen.connectTo(fragmentOutput, { output: "rgba" });
  1402. // Add to nodes
  1403. this.addOutputNode(vertexOutput);
  1404. this.addOutputNode(fragmentOutput);
  1405. this._mode = NodeMaterialModes.PostProcess;
  1406. }
  1407. /**
  1408. * Clear the current material and set it to a default state for procedural texture
  1409. */
  1410. setToDefaultProceduralTexture() {
  1411. this.clear();
  1412. this.editorData = null;
  1413. const position = new InputBlock("Position");
  1414. position.setAsAttribute("position2d");
  1415. const const1 = new InputBlock("Constant1");
  1416. const1.isConstant = true;
  1417. const1.value = 1;
  1418. const vmerger = new VectorMergerBlock("Position3D");
  1419. position.connectTo(vmerger);
  1420. const1.connectTo(vmerger, { input: "w" });
  1421. const vertexOutput = new VertexOutputBlock("VertexOutput");
  1422. vmerger.connectTo(vertexOutput);
  1423. // Pixel
  1424. const time = new InputBlock("Time");
  1425. time.value = 0;
  1426. time.min = 0;
  1427. time.max = 0;
  1428. time.isBoolean = false;
  1429. time.matrixMode = 0;
  1430. time.animationType = AnimatedInputBlockTypes.Time;
  1431. time.isConstant = false;
  1432. const color = new InputBlock("Color3");
  1433. color.value = new Color3(1, 1, 1);
  1434. color.isConstant = false;
  1435. const fragmentOutput = new FragmentOutputBlock("FragmentOutput");
  1436. const vectorMerger = new VectorMergerBlock("VectorMerger");
  1437. vectorMerger.visibleInInspector = false;
  1438. const cos = new TrigonometryBlock("Cos");
  1439. cos.operation = TrigonometryBlockOperations.Cos;
  1440. position.connectTo(vectorMerger);
  1441. time.output.connectTo(cos.input);
  1442. cos.output.connectTo(vectorMerger.z);
  1443. vectorMerger.xyzOut.connectTo(fragmentOutput.rgb);
  1444. // Add to nodes
  1445. this.addOutputNode(vertexOutput);
  1446. this.addOutputNode(fragmentOutput);
  1447. this._mode = NodeMaterialModes.ProceduralTexture;
  1448. }
  1449. /**
  1450. * Clear the current material and set it to a default state for particle
  1451. */
  1452. setToDefaultParticle() {
  1453. this.clear();
  1454. this.editorData = null;
  1455. // Pixel
  1456. const uv = new InputBlock("uv");
  1457. uv.setAsAttribute("particle_uv");
  1458. const texture = new ParticleTextureBlock("ParticleTexture");
  1459. uv.connectTo(texture);
  1460. const color = new InputBlock("Color");
  1461. color.setAsAttribute("particle_color");
  1462. const multiply = new MultiplyBlock("Texture * Color");
  1463. texture.connectTo(multiply);
  1464. color.connectTo(multiply);
  1465. const rampGradient = new ParticleRampGradientBlock("ParticleRampGradient");
  1466. multiply.connectTo(rampGradient);
  1467. const cSplitter = new ColorSplitterBlock("ColorSplitter");
  1468. color.connectTo(cSplitter);
  1469. const blendMultiply = new ParticleBlendMultiplyBlock("ParticleBlendMultiply");
  1470. rampGradient.connectTo(blendMultiply);
  1471. texture.connectTo(blendMultiply, { output: "a" });
  1472. cSplitter.connectTo(blendMultiply, { output: "a" });
  1473. const fragmentOutput = new FragmentOutputBlock("FragmentOutput");
  1474. blendMultiply.connectTo(fragmentOutput);
  1475. // Add to nodes
  1476. this.addOutputNode(fragmentOutput);
  1477. this._mode = NodeMaterialModes.Particle;
  1478. }
  1479. /**
  1480. * Loads the current Node Material from a url pointing to a file save by the Node Material Editor
  1481. * @deprecated Please use NodeMaterial.ParseFromFileAsync instead
  1482. * @param url defines the url to load from
  1483. * @param rootUrl defines the root URL for nested url in the node material
  1484. * @returns a promise that will fulfil when the material is fully loaded
  1485. */
  1486. async loadAsync(url, rootUrl = "") {
  1487. return NodeMaterial.ParseFromFileAsync("", url, this.getScene(), rootUrl, true, this);
  1488. }
  1489. _gatherBlocks(rootNode, list) {
  1490. if (list.indexOf(rootNode) !== -1) {
  1491. return;
  1492. }
  1493. list.push(rootNode);
  1494. for (const input of rootNode.inputs) {
  1495. const connectedPoint = input.connectedPoint;
  1496. if (connectedPoint) {
  1497. const block = connectedPoint.ownerBlock;
  1498. if (block !== rootNode) {
  1499. this._gatherBlocks(block, list);
  1500. }
  1501. }
  1502. }
  1503. // Teleportation
  1504. if (rootNode.isTeleportOut) {
  1505. const block = rootNode;
  1506. if (block.entryPoint) {
  1507. this._gatherBlocks(block.entryPoint, list);
  1508. }
  1509. }
  1510. }
  1511. /**
  1512. * Generate a string containing the code declaration required to create an equivalent of this material
  1513. * @returns a string
  1514. */
  1515. generateCode() {
  1516. let alreadyDumped = [];
  1517. const vertexBlocks = [];
  1518. const uniqueNames = ["const", "var", "let"];
  1519. // Gets active blocks
  1520. for (const outputNode of this._vertexOutputNodes) {
  1521. this._gatherBlocks(outputNode, vertexBlocks);
  1522. }
  1523. const fragmentBlocks = [];
  1524. for (const outputNode of this._fragmentOutputNodes) {
  1525. this._gatherBlocks(outputNode, fragmentBlocks);
  1526. }
  1527. // Generate vertex shader
  1528. let codeString = `var nodeMaterial = new BABYLON.NodeMaterial("${this.name || "node material"}");\n`;
  1529. codeString += `nodeMaterial.mode = BABYLON.NodeMaterialModes.${NodeMaterialModes[this.mode]};\n`;
  1530. for (const node of vertexBlocks) {
  1531. if (node.isInput && alreadyDumped.indexOf(node) === -1) {
  1532. codeString += node._dumpCode(uniqueNames, alreadyDumped);
  1533. }
  1534. }
  1535. // Generate fragment shader
  1536. for (const node of fragmentBlocks) {
  1537. if (node.isInput && alreadyDumped.indexOf(node) === -1) {
  1538. codeString += node._dumpCode(uniqueNames, alreadyDumped);
  1539. }
  1540. }
  1541. // Connections
  1542. alreadyDumped = [];
  1543. codeString += "\n// Connections\n";
  1544. for (const node of this._vertexOutputNodes) {
  1545. codeString += node._dumpCodeForOutputConnections(alreadyDumped);
  1546. }
  1547. for (const node of this._fragmentOutputNodes) {
  1548. codeString += node._dumpCodeForOutputConnections(alreadyDumped);
  1549. }
  1550. // Output nodes
  1551. codeString += "\n// Output nodes\n";
  1552. for (const node of this._vertexOutputNodes) {
  1553. codeString += `nodeMaterial.addOutputNode(${node._codeVariableName});\n`;
  1554. }
  1555. for (const node of this._fragmentOutputNodes) {
  1556. codeString += `nodeMaterial.addOutputNode(${node._codeVariableName});\n`;
  1557. }
  1558. codeString += `nodeMaterial.build();\n`;
  1559. return codeString;
  1560. }
  1561. /**
  1562. * Serializes this material in a JSON representation
  1563. * @param selectedBlocks defines an optional list of blocks to serialize
  1564. * @returns the serialized material object
  1565. */
  1566. serialize(selectedBlocks) {
  1567. const serializationObject = selectedBlocks ? {} : SerializationHelper.Serialize(this);
  1568. serializationObject.editorData = JSON.parse(JSON.stringify(this.editorData)); // Copy
  1569. let blocks = [];
  1570. if (selectedBlocks) {
  1571. blocks = selectedBlocks;
  1572. }
  1573. else {
  1574. serializationObject.customType = "BABYLON.NodeMaterial";
  1575. serializationObject.outputNodes = [];
  1576. // Outputs
  1577. for (const outputNode of this._vertexOutputNodes) {
  1578. this._gatherBlocks(outputNode, blocks);
  1579. serializationObject.outputNodes.push(outputNode.uniqueId);
  1580. }
  1581. for (const outputNode of this._fragmentOutputNodes) {
  1582. this._gatherBlocks(outputNode, blocks);
  1583. if (serializationObject.outputNodes.indexOf(outputNode.uniqueId) === -1) {
  1584. serializationObject.outputNodes.push(outputNode.uniqueId);
  1585. }
  1586. }
  1587. }
  1588. // Blocks
  1589. serializationObject.blocks = [];
  1590. for (const block of blocks) {
  1591. serializationObject.blocks.push(block.serialize());
  1592. }
  1593. if (!selectedBlocks) {
  1594. for (const block of this.attachedBlocks) {
  1595. if (blocks.indexOf(block) !== -1) {
  1596. continue;
  1597. }
  1598. serializationObject.blocks.push(block.serialize());
  1599. }
  1600. }
  1601. return serializationObject;
  1602. }
  1603. _restoreConnections(block, source, map) {
  1604. for (const outputPoint of block.outputs) {
  1605. for (const candidate of source.blocks) {
  1606. const target = map[candidate.id];
  1607. if (!target) {
  1608. continue;
  1609. }
  1610. for (const input of candidate.inputs) {
  1611. if (map[input.targetBlockId] === block && input.targetConnectionName === outputPoint.name) {
  1612. const inputPoint = target.getInputByName(input.inputName);
  1613. if (!inputPoint || inputPoint.isConnected) {
  1614. continue;
  1615. }
  1616. outputPoint.connectTo(inputPoint, true);
  1617. this._restoreConnections(target, source, map);
  1618. continue;
  1619. }
  1620. }
  1621. }
  1622. }
  1623. }
  1624. /**
  1625. * Clear the current graph and load a new one from a serialization object
  1626. * @param source defines the JSON representation of the material
  1627. * @param rootUrl defines the root URL to use to load textures and relative dependencies
  1628. * @param merge defines whether or not the source must be merged or replace the current content
  1629. */
  1630. parseSerializedObject(source, rootUrl = "", merge = false) {
  1631. if (!merge) {
  1632. this.clear();
  1633. }
  1634. const map = {};
  1635. // Create blocks
  1636. for (const parsedBlock of source.blocks) {
  1637. const blockType = GetClass(parsedBlock.customType);
  1638. if (blockType) {
  1639. const block = new blockType();
  1640. block._deserialize(parsedBlock, this.getScene(), rootUrl);
  1641. map[parsedBlock.id] = block;
  1642. this.attachedBlocks.push(block);
  1643. }
  1644. }
  1645. // Reconnect teleportation
  1646. for (const block of this.attachedBlocks) {
  1647. if (block.isTeleportOut) {
  1648. const teleportOut = block;
  1649. const id = teleportOut._tempEntryPointUniqueId;
  1650. if (id) {
  1651. const source = map[id];
  1652. source.attachToEndpoint(teleportOut);
  1653. }
  1654. }
  1655. }
  1656. // Connections - Starts with input blocks only (except if in "merge" mode where we scan all blocks)
  1657. for (let blockIndex = 0; blockIndex < source.blocks.length; blockIndex++) {
  1658. const parsedBlock = source.blocks[blockIndex];
  1659. const block = map[parsedBlock.id];
  1660. if (!block) {
  1661. continue;
  1662. }
  1663. if (block.inputs.length && !merge) {
  1664. continue;
  1665. }
  1666. this._restoreConnections(block, source, map);
  1667. }
  1668. // Outputs
  1669. if (source.outputNodes) {
  1670. for (const outputNodeId of source.outputNodes) {
  1671. this.addOutputNode(map[outputNodeId]);
  1672. }
  1673. }
  1674. // UI related info
  1675. if (source.locations || (source.editorData && source.editorData.locations)) {
  1676. const locations = source.locations || source.editorData.locations;
  1677. for (const location of locations) {
  1678. if (map[location.blockId]) {
  1679. location.blockId = map[location.blockId].uniqueId;
  1680. }
  1681. }
  1682. if (merge && this.editorData && this.editorData.locations) {
  1683. locations.concat(this.editorData.locations);
  1684. }
  1685. if (source.locations) {
  1686. this.editorData = {
  1687. locations: locations,
  1688. };
  1689. }
  1690. else {
  1691. this.editorData = source.editorData;
  1692. this.editorData.locations = locations;
  1693. }
  1694. const blockMap = [];
  1695. for (const key in map) {
  1696. blockMap[key] = map[key].uniqueId;
  1697. }
  1698. this.editorData.map = blockMap;
  1699. }
  1700. this.comment = source.comment;
  1701. if (source.forceAlphaBlending !== undefined) {
  1702. this.forceAlphaBlending = source.forceAlphaBlending;
  1703. }
  1704. if (source.alphaMode !== undefined) {
  1705. this.alphaMode = source.alphaMode;
  1706. }
  1707. if (!merge) {
  1708. this._mode = source.mode ?? NodeMaterialModes.Material;
  1709. }
  1710. }
  1711. /**
  1712. * Clear the current graph and load a new one from a serialization object
  1713. * @param source defines the JSON representation of the material
  1714. * @param rootUrl defines the root URL to use to load textures and relative dependencies
  1715. * @param merge defines whether or not the source must be merged or replace the current content
  1716. * @deprecated Please use the parseSerializedObject method instead
  1717. */
  1718. loadFromSerialization(source, rootUrl = "", merge = false) {
  1719. this.parseSerializedObject(source, rootUrl, merge);
  1720. }
  1721. /**
  1722. * Makes a duplicate of the current material.
  1723. * @param name defines the name to use for the new material
  1724. * @param shareEffect defines if the clone material should share the same effect (default is false)
  1725. * @returns the cloned material
  1726. */
  1727. clone(name, shareEffect = false) {
  1728. const serializationObject = this.serialize();
  1729. const clone = SerializationHelper.Clone(() => new NodeMaterial(name, this.getScene(), this.options), this);
  1730. clone.id = name;
  1731. clone.name = name;
  1732. clone.parseSerializedObject(serializationObject);
  1733. clone._buildId = this._buildId;
  1734. clone.build(false, !shareEffect);
  1735. return clone;
  1736. }
  1737. /**
  1738. * Awaits for all the material textures to be ready before resolving the returned promise.
  1739. * @returns A promise that resolves when the textures are ready.
  1740. */
  1741. whenTexturesReadyAsync() {
  1742. // Ensures all textures are ready to render.
  1743. const textureReadyPromises = [];
  1744. this.getActiveTextures().forEach((texture) => {
  1745. const internalTexture = texture.getInternalTexture();
  1746. if (internalTexture && !internalTexture.isReady) {
  1747. textureReadyPromises.push(new Promise((textureResolve, textureReject) => {
  1748. internalTexture.onLoadedObservable.addOnce(() => {
  1749. textureResolve();
  1750. });
  1751. internalTexture.onErrorObservable.addOnce((e) => {
  1752. textureReject(e);
  1753. });
  1754. }));
  1755. }
  1756. });
  1757. return Promise.all(textureReadyPromises);
  1758. }
  1759. /**
  1760. * Creates a node material from parsed material data
  1761. * @param source defines the JSON representation of the material
  1762. * @param scene defines the hosting scene
  1763. * @param rootUrl defines the root URL to use to load textures and relative dependencies
  1764. * @returns a new node material
  1765. */
  1766. static Parse(source, scene, rootUrl = "") {
  1767. const nodeMaterial = SerializationHelper.Parse(() => new NodeMaterial(source.name, scene), source, scene, rootUrl);
  1768. nodeMaterial.parseSerializedObject(source, rootUrl);
  1769. nodeMaterial.build();
  1770. return nodeMaterial;
  1771. }
  1772. /**
  1773. * Creates a node material from a snippet saved in a remote file
  1774. * @param name defines the name of the material to create
  1775. * @param url defines the url to load from
  1776. * @param scene defines the hosting scene
  1777. * @param rootUrl defines the root URL for nested url in the node material
  1778. * @param skipBuild defines whether to build the node material
  1779. * @param targetMaterial defines a material to use instead of creating a new one
  1780. * @returns a promise that will resolve to the new node material
  1781. */
  1782. static async ParseFromFileAsync(name, url, scene, rootUrl = "", skipBuild = false, targetMaterial) {
  1783. const material = targetMaterial ?? new NodeMaterial(name, scene);
  1784. const data = await scene._loadFileAsync(url);
  1785. const serializationObject = JSON.parse(data);
  1786. material.parseSerializedObject(serializationObject, rootUrl);
  1787. if (!skipBuild) {
  1788. material.build();
  1789. }
  1790. return material;
  1791. }
  1792. /**
  1793. * Creates a node material from a snippet saved by the node material editor
  1794. * @param snippetId defines the snippet to load
  1795. * @param scene defines the hosting scene
  1796. * @param rootUrl defines the root URL to use to load textures and relative dependencies
  1797. * @param nodeMaterial defines a node material to update (instead of creating a new one)
  1798. * @param skipBuild defines whether to build the node material
  1799. * @param waitForTextureReadyness defines whether to wait for texture readiness resolving the promise (default: false)
  1800. * @returns a promise that will resolve to the new node material
  1801. */
  1802. static ParseFromSnippetAsync(snippetId, scene = EngineStore.LastCreatedScene, rootUrl = "", nodeMaterial, skipBuild = false, waitForTextureReadyness = false) {
  1803. if (snippetId === "_BLANK") {
  1804. return Promise.resolve(NodeMaterial.CreateDefault("blank", scene));
  1805. }
  1806. return new Promise((resolve, reject) => {
  1807. const request = new WebRequest();
  1808. request.addEventListener("readystatechange", () => {
  1809. if (request.readyState == 4) {
  1810. if (request.status == 200) {
  1811. const snippet = JSON.parse(JSON.parse(request.responseText).jsonPayload);
  1812. const serializationObject = JSON.parse(snippet.nodeMaterial);
  1813. if (!nodeMaterial) {
  1814. nodeMaterial = SerializationHelper.Parse(() => new NodeMaterial(snippetId, scene), serializationObject, scene, rootUrl);
  1815. nodeMaterial.uniqueId = scene.getUniqueId();
  1816. }
  1817. nodeMaterial.parseSerializedObject(serializationObject);
  1818. nodeMaterial.snippetId = snippetId;
  1819. try {
  1820. if (!skipBuild) {
  1821. nodeMaterial.build();
  1822. }
  1823. }
  1824. catch (err) {
  1825. reject(err);
  1826. }
  1827. if (waitForTextureReadyness) {
  1828. nodeMaterial
  1829. .whenTexturesReadyAsync()
  1830. .then(() => {
  1831. resolve(nodeMaterial);
  1832. })
  1833. .catch((err) => {
  1834. reject(err);
  1835. });
  1836. }
  1837. else {
  1838. resolve(nodeMaterial);
  1839. }
  1840. }
  1841. else {
  1842. reject("Unable to load the snippet " + snippetId);
  1843. }
  1844. }
  1845. });
  1846. request.open("GET", this.SnippetUrl + "/" + snippetId.replace(/#/g, "/"));
  1847. request.send();
  1848. });
  1849. }
  1850. /**
  1851. * Creates a new node material set to default basic configuration
  1852. * @param name defines the name of the material
  1853. * @param scene defines the hosting scene
  1854. * @returns a new NodeMaterial
  1855. */
  1856. static CreateDefault(name, scene) {
  1857. const newMaterial = new NodeMaterial(name, scene);
  1858. newMaterial.setToDefault();
  1859. newMaterial.build();
  1860. return newMaterial;
  1861. }
  1862. }
  1863. NodeMaterial._BuildIdGenerator = 0;
  1864. /** Define the Url to load node editor script */
  1865. NodeMaterial.EditorURL = `${Tools._DefaultCdnUrl}/v${Engine.Version}/nodeEditor/babylon.nodeEditor.js`;
  1866. /** Define the Url to load snippets */
  1867. NodeMaterial.SnippetUrl = `https://snippet.babylonjs.com`;
  1868. /** Gets or sets a boolean indicating that node materials should not deserialize textures from json / snippet content */
  1869. NodeMaterial.IgnoreTexturesAtLoadTime = false;
  1870. __decorate([
  1871. serialize()
  1872. ], NodeMaterial.prototype, "ignoreAlpha", void 0);
  1873. __decorate([
  1874. serialize()
  1875. ], NodeMaterial.prototype, "maxSimultaneousLights", void 0);
  1876. __decorate([
  1877. serialize("mode")
  1878. ], NodeMaterial.prototype, "_mode", void 0);
  1879. __decorate([
  1880. serialize("comment")
  1881. ], NodeMaterial.prototype, "comment", void 0);
  1882. __decorate([
  1883. serialize()
  1884. ], NodeMaterial.prototype, "forceAlphaBlending", void 0);
  1885. RegisterClass("BABYLON.NodeMaterial", NodeMaterial);
  1886. //# sourceMappingURL=nodeMaterial.js.map