nodeMaterial.ts 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425
  1. import { NodeMaterialBlock } from './nodeMaterialBlock';
  2. import { PushMaterial } from '../pushMaterial';
  3. import { Scene } from '../../scene';
  4. import { AbstractMesh } from '../../Meshes/abstractMesh';
  5. import { Matrix } from '../../Maths/math.vector';
  6. import { Color4 } from '../../Maths/math.color';
  7. import { Mesh } from '../../Meshes/mesh';
  8. import { Engine } from '../../Engines/engine';
  9. import { NodeMaterialBuildState } from './nodeMaterialBuildState';
  10. import { IEffectCreationOptions } from '../effect';
  11. import { BaseTexture } from '../../Materials/Textures/baseTexture';
  12. import { Observable, Observer } from '../../Misc/observable';
  13. import { NodeMaterialBlockTargets } from './Enums/nodeMaterialBlockTargets';
  14. import { NodeMaterialBuildStateSharedData } from './nodeMaterialBuildStateSharedData';
  15. import { SubMesh } from '../../Meshes/subMesh';
  16. import { MaterialDefines } from '../../Materials/materialDefines';
  17. import { NodeMaterialOptimizer } from './Optimizers/nodeMaterialOptimizer';
  18. import { ImageProcessingConfiguration, IImageProcessingConfigurationDefines } from '../imageProcessingConfiguration';
  19. import { Nullable } from '../../types';
  20. import { VertexBuffer } from '../../Meshes/buffer';
  21. import { Tools } from '../../Misc/tools';
  22. import { TransformBlock } from './Blocks/transformBlock';
  23. import { VertexOutputBlock } from './Blocks/Vertex/vertexOutputBlock';
  24. import { FragmentOutputBlock } from './Blocks/Fragment/fragmentOutputBlock';
  25. import { InputBlock } from './Blocks/Input/inputBlock';
  26. import { _TypeStore } from '../../Misc/typeStore';
  27. import { SerializationHelper } from '../../Misc/decorators';
  28. import { TextureBlock } from './Blocks/Dual/textureBlock';
  29. import { ReflectionTextureBaseBlock } from './Blocks/Dual/reflectionTextureBaseBlock';
  30. import { EffectFallbacks } from '../effectFallbacks';
  31. import { WebRequest } from '../../Misc/webRequest';
  32. import { Effect } from '../effect';
  33. const onCreatedEffectParameters = { effect: null as unknown as Effect, subMesh: null as unknown as Nullable<SubMesh> };
  34. // declare NODEEDITOR namespace for compilation issue
  35. declare var NODEEDITOR: any;
  36. declare var BABYLON: any;
  37. /**
  38. * Interface used to configure the node material editor
  39. */
  40. export interface INodeMaterialEditorOptions {
  41. /** Define the URl to load node editor script */
  42. editorURL?: string;
  43. }
  44. /** @hidden */
  45. export class NodeMaterialDefines extends MaterialDefines implements IImageProcessingConfigurationDefines {
  46. public NORMAL = false;
  47. public TANGENT = false;
  48. public UV1 = false;
  49. /** BONES */
  50. public NUM_BONE_INFLUENCERS = 0;
  51. public BonesPerMesh = 0;
  52. public BONETEXTURE = false;
  53. /** MORPH TARGETS */
  54. public MORPHTARGETS = false;
  55. public MORPHTARGETS_NORMAL = false;
  56. public MORPHTARGETS_TANGENT = false;
  57. public MORPHTARGETS_UV = false;
  58. public NUM_MORPH_INFLUENCERS = 0;
  59. /** IMAGE PROCESSING */
  60. public IMAGEPROCESSING = false;
  61. public VIGNETTE = false;
  62. public VIGNETTEBLENDMODEMULTIPLY = false;
  63. public VIGNETTEBLENDMODEOPAQUE = false;
  64. public TONEMAPPING = false;
  65. public TONEMAPPING_ACES = false;
  66. public CONTRAST = false;
  67. public EXPOSURE = false;
  68. public COLORCURVES = false;
  69. public COLORGRADING = false;
  70. public COLORGRADING3D = false;
  71. public SAMPLER3DGREENDEPTH = false;
  72. public SAMPLER3DBGRMAP = false;
  73. public IMAGEPROCESSINGPOSTPROCESS = false;
  74. /** MISC. */
  75. public BUMPDIRECTUV = 0;
  76. constructor() {
  77. super();
  78. this.rebuild();
  79. }
  80. public setValue(name: string, value: any, markAsUnprocessedIfDirty = false) {
  81. if (this[name] === undefined) {
  82. this._keys.push(name);
  83. }
  84. if (markAsUnprocessedIfDirty && this[name] !== value) {
  85. this.markAsUnprocessed();
  86. }
  87. this[name] = value;
  88. }
  89. }
  90. /**
  91. * Class used to configure NodeMaterial
  92. */
  93. export interface INodeMaterialOptions {
  94. /**
  95. * Defines if blocks should emit comments
  96. */
  97. emitComments: boolean;
  98. }
  99. /**
  100. * Class used to create a node based material built by assembling shader blocks
  101. */
  102. export class NodeMaterial extends PushMaterial {
  103. private static _BuildIdGenerator: number = 0;
  104. private _options: INodeMaterialOptions;
  105. private _vertexCompilationState: NodeMaterialBuildState;
  106. private _fragmentCompilationState: NodeMaterialBuildState;
  107. private _sharedData: NodeMaterialBuildStateSharedData;
  108. private _buildId: number = NodeMaterial._BuildIdGenerator++;
  109. private _buildWasSuccessful = false;
  110. private _cachedWorldViewMatrix = new Matrix();
  111. private _cachedWorldViewProjectionMatrix = new Matrix();
  112. private _optimizers = new Array<NodeMaterialOptimizer>();
  113. private _animationFrame = -1;
  114. /** Define the Url to load node editor script */
  115. public static EditorURL = `https://unpkg.com/babylonjs-node-editor@${Engine.Version}/babylon.nodeEditor.js`;
  116. /** Define the Url to load snippets */
  117. public static SnippetUrl = "https://snippet.babylonjs.com";
  118. /** Gets or sets a boolean indicating that node materials should not deserialize textures from json / snippet content */
  119. public static IgnoreTexturesAtLoadTime = false;
  120. private BJSNODEMATERIALEDITOR = this._getGlobalNodeMaterialEditor();
  121. /** Get the inspector from bundle or global */
  122. private _getGlobalNodeMaterialEditor(): any {
  123. // UMD Global name detection from Webpack Bundle UMD Name.
  124. if (typeof NODEEDITOR !== 'undefined') {
  125. return NODEEDITOR;
  126. }
  127. // In case of module let's check the global emitted from the editor entry point.
  128. if (typeof BABYLON !== 'undefined' && typeof BABYLON.NodeEditor !== 'undefined') {
  129. return BABYLON;
  130. }
  131. return undefined;
  132. }
  133. /**
  134. * Snippet ID if the material was created from the snippet server
  135. */
  136. public snippetId: string;
  137. /**
  138. * Gets or sets data used by visual editor
  139. * @see https://nme.babylonjs.com
  140. */
  141. public editorData: any = null;
  142. /**
  143. * 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)
  144. */
  145. public ignoreAlpha = false;
  146. /**
  147. * Defines the maximum number of lights that can be used in the material
  148. */
  149. public maxSimultaneousLights = 4;
  150. /**
  151. * Observable raised when the material is built
  152. */
  153. public onBuildObservable = new Observable<NodeMaterial>();
  154. /**
  155. * Gets or sets the root nodes of the material vertex shader
  156. */
  157. public _vertexOutputNodes = new Array<NodeMaterialBlock>();
  158. /**
  159. * Gets or sets the root nodes of the material fragment (pixel) shader
  160. */
  161. public _fragmentOutputNodes = new Array<NodeMaterialBlock>();
  162. /** Gets or sets options to control the node material overall behavior */
  163. public get options() {
  164. return this._options;
  165. }
  166. public set options(options: INodeMaterialOptions) {
  167. this._options = options;
  168. }
  169. /**
  170. * Default configuration related to image processing available in the standard Material.
  171. */
  172. protected _imageProcessingConfiguration: ImageProcessingConfiguration;
  173. /**
  174. * Gets the image processing configuration used either in this material.
  175. */
  176. public get imageProcessingConfiguration(): ImageProcessingConfiguration {
  177. return this._imageProcessingConfiguration;
  178. }
  179. /**
  180. * Sets the Default image processing configuration used either in the this material.
  181. *
  182. * If sets to null, the scene one is in use.
  183. */
  184. public set imageProcessingConfiguration(value: ImageProcessingConfiguration) {
  185. this._attachImageProcessingConfiguration(value);
  186. // Ensure the effect will be rebuilt.
  187. this._markAllSubMeshesAsTexturesDirty();
  188. }
  189. /**
  190. * Gets an array of blocks that needs to be serialized even if they are not yet connected
  191. */
  192. public attachedBlocks = new Array<NodeMaterialBlock>();
  193. /**
  194. * Create a new node based material
  195. * @param name defines the material name
  196. * @param scene defines the hosting scene
  197. * @param options defines creation option
  198. */
  199. constructor(name: string, scene?: Scene, options: Partial<INodeMaterialOptions> = {}) {
  200. super(name, scene || Engine.LastCreatedScene!);
  201. this._options = {
  202. emitComments: false,
  203. ...options
  204. };
  205. // Setup the default processing configuration to the scene.
  206. this._attachImageProcessingConfiguration(null);
  207. }
  208. /**
  209. * Gets the current class name of the material e.g. "NodeMaterial"
  210. * @returns the class name
  211. */
  212. public getClassName(): string {
  213. return "NodeMaterial";
  214. }
  215. /**
  216. * Keep track of the image processing observer to allow dispose and replace.
  217. */
  218. private _imageProcessingObserver: Nullable<Observer<ImageProcessingConfiguration>>;
  219. /**
  220. * Attaches a new image processing configuration to the Standard Material.
  221. * @param configuration
  222. */
  223. protected _attachImageProcessingConfiguration(configuration: Nullable<ImageProcessingConfiguration>): void {
  224. if (configuration === this._imageProcessingConfiguration) {
  225. return;
  226. }
  227. // Detaches observer.
  228. if (this._imageProcessingConfiguration && this._imageProcessingObserver) {
  229. this._imageProcessingConfiguration.onUpdateParameters.remove(this._imageProcessingObserver);
  230. }
  231. // Pick the scene configuration if needed.
  232. if (!configuration) {
  233. this._imageProcessingConfiguration = this.getScene().imageProcessingConfiguration;
  234. }
  235. else {
  236. this._imageProcessingConfiguration = configuration;
  237. }
  238. // Attaches observer.
  239. if (this._imageProcessingConfiguration) {
  240. this._imageProcessingObserver = this._imageProcessingConfiguration.onUpdateParameters.add(() => {
  241. this._markAllSubMeshesAsImageProcessingDirty();
  242. });
  243. }
  244. }
  245. /**
  246. * Get a block by its name
  247. * @param name defines the name of the block to retrieve
  248. * @returns the required block or null if not found
  249. */
  250. public getBlockByName(name: string) {
  251. let result = null;
  252. for (var block of this.attachedBlocks) {
  253. if (block.name === name) {
  254. if (!result) {
  255. result = block;
  256. } else {
  257. Tools.Warn("More than one block was found with the name `" + name + "`");
  258. return result;
  259. }
  260. }
  261. }
  262. return result;
  263. }
  264. /**
  265. * Get a block by its name
  266. * @param predicate defines the predicate used to find the good candidate
  267. * @returns the required block or null if not found
  268. */
  269. public getBlockByPredicate(predicate: (block: NodeMaterialBlock) => boolean) {
  270. for (var block of this.attachedBlocks) {
  271. if (predicate(block)) {
  272. return block;
  273. }
  274. }
  275. return null;
  276. }
  277. /**
  278. * Get an input block by its name
  279. * @param predicate defines the predicate used to find the good candidate
  280. * @returns the required input block or null if not found
  281. */
  282. public getInputBlockByPredicate(predicate: (block: InputBlock) => boolean): Nullable<InputBlock> {
  283. for (var block of this.attachedBlocks) {
  284. if (block.isInput && predicate(block as InputBlock)) {
  285. return block as InputBlock;
  286. }
  287. }
  288. return null;
  289. }
  290. /**
  291. * Gets the list of input blocks attached to this material
  292. * @returns an array of InputBlocks
  293. */
  294. public getInputBlocks() {
  295. let blocks: InputBlock[] = [];
  296. for (var block of this.attachedBlocks) {
  297. if (block.isInput) {
  298. blocks.push(block as InputBlock);
  299. }
  300. }
  301. return blocks;
  302. }
  303. /**
  304. * Adds a new optimizer to the list of optimizers
  305. * @param optimizer defines the optimizers to add
  306. * @returns the current material
  307. */
  308. public registerOptimizer(optimizer: NodeMaterialOptimizer) {
  309. let index = this._optimizers.indexOf(optimizer);
  310. if (index > -1) {
  311. return;
  312. }
  313. this._optimizers.push(optimizer);
  314. return this;
  315. }
  316. /**
  317. * Remove an optimizer from the list of optimizers
  318. * @param optimizer defines the optimizers to remove
  319. * @returns the current material
  320. */
  321. public unregisterOptimizer(optimizer: NodeMaterialOptimizer) {
  322. let index = this._optimizers.indexOf(optimizer);
  323. if (index === -1) {
  324. return;
  325. }
  326. this._optimizers.splice(index, 1);
  327. return this;
  328. }
  329. /**
  330. * Add a new block to the list of output nodes
  331. * @param node defines the node to add
  332. * @returns the current material
  333. */
  334. public addOutputNode(node: NodeMaterialBlock) {
  335. if (node.target === null) {
  336. throw "This node is not meant to be an output node. You may want to explicitly set its target value.";
  337. }
  338. if ((node.target & NodeMaterialBlockTargets.Vertex) !== 0) {
  339. this._addVertexOutputNode(node);
  340. }
  341. if ((node.target & NodeMaterialBlockTargets.Fragment) !== 0) {
  342. this._addFragmentOutputNode(node);
  343. }
  344. return this;
  345. }
  346. /**
  347. * Remove a block from the list of root nodes
  348. * @param node defines the node to remove
  349. * @returns the current material
  350. */
  351. public removeOutputNode(node: NodeMaterialBlock) {
  352. if (node.target === null) {
  353. return this;
  354. }
  355. if ((node.target & NodeMaterialBlockTargets.Vertex) !== 0) {
  356. this._removeVertexOutputNode(node);
  357. }
  358. if ((node.target & NodeMaterialBlockTargets.Fragment) !== 0) {
  359. this._removeFragmentOutputNode(node);
  360. }
  361. return this;
  362. }
  363. private _addVertexOutputNode(node: NodeMaterialBlock) {
  364. if (this._vertexOutputNodes.indexOf(node) !== -1) {
  365. return;
  366. }
  367. node.target = NodeMaterialBlockTargets.Vertex;
  368. this._vertexOutputNodes.push(node);
  369. return this;
  370. }
  371. private _removeVertexOutputNode(node: NodeMaterialBlock) {
  372. let index = this._vertexOutputNodes.indexOf(node);
  373. if (index === -1) {
  374. return;
  375. }
  376. this._vertexOutputNodes.splice(index, 1);
  377. return this;
  378. }
  379. private _addFragmentOutputNode(node: NodeMaterialBlock) {
  380. if (this._fragmentOutputNodes.indexOf(node) !== -1) {
  381. return;
  382. }
  383. node.target = NodeMaterialBlockTargets.Fragment;
  384. this._fragmentOutputNodes.push(node);
  385. return this;
  386. }
  387. private _removeFragmentOutputNode(node: NodeMaterialBlock) {
  388. let index = this._fragmentOutputNodes.indexOf(node);
  389. if (index === -1) {
  390. return;
  391. }
  392. this._fragmentOutputNodes.splice(index, 1);
  393. return this;
  394. }
  395. /**
  396. * Specifies if the material will require alpha blending
  397. * @returns a boolean specifying if alpha blending is needed
  398. */
  399. public needAlphaBlending(): boolean {
  400. if (this.ignoreAlpha) {
  401. return false;
  402. }
  403. return (this.alpha < 1.0) || (this._sharedData && this._sharedData.hints.needAlphaBlending);
  404. }
  405. /**
  406. * Specifies if this material should be rendered in alpha test mode
  407. * @returns a boolean specifying if an alpha test is needed.
  408. */
  409. public needAlphaTesting(): boolean {
  410. return this._sharedData && this._sharedData.hints.needAlphaTesting;
  411. }
  412. private _initializeBlock(node: NodeMaterialBlock, state: NodeMaterialBuildState, nodesToProcessForOtherBuildState: NodeMaterialBlock[]) {
  413. node.initialize(state);
  414. node.autoConfigure(this);
  415. node._preparationId = this._buildId;
  416. if (this.attachedBlocks.indexOf(node) === -1) {
  417. if (node.isUnique) {
  418. const className = node.getClassName();
  419. for (var other of this.attachedBlocks) {
  420. if (other.getClassName() === className) {
  421. throw `Cannot have multiple blocks of type ${className} in the same NodeMaterial`;
  422. }
  423. }
  424. }
  425. this.attachedBlocks.push(node);
  426. }
  427. for (var input of node.inputs) {
  428. input.associatedVariableName = "";
  429. let connectedPoint = input.connectedPoint;
  430. if (connectedPoint) {
  431. let block = connectedPoint.ownerBlock;
  432. if (block !== node) {
  433. if (block.target === NodeMaterialBlockTargets.VertexAndFragment) {
  434. nodesToProcessForOtherBuildState.push(block);
  435. } else if (state.target === NodeMaterialBlockTargets.Fragment
  436. && block.target === NodeMaterialBlockTargets.Vertex
  437. && block._preparationId !== this._buildId) {
  438. nodesToProcessForOtherBuildState.push(block);
  439. }
  440. this._initializeBlock(block, state, nodesToProcessForOtherBuildState);
  441. }
  442. }
  443. }
  444. for (var output of node.outputs) {
  445. output.associatedVariableName = "";
  446. }
  447. }
  448. private _resetDualBlocks(node: NodeMaterialBlock, id: number) {
  449. if (node.target === NodeMaterialBlockTargets.VertexAndFragment) {
  450. node.buildId = id;
  451. }
  452. for (var inputs of node.inputs) {
  453. let connectedPoint = inputs.connectedPoint;
  454. if (connectedPoint) {
  455. let block = connectedPoint.ownerBlock;
  456. if (block !== node) {
  457. this._resetDualBlocks(block, id);
  458. }
  459. }
  460. }
  461. }
  462. /**
  463. * Remove a block from the current node material
  464. * @param block defines the block to remove
  465. */
  466. public removeBlock(block: NodeMaterialBlock) {
  467. let attachedBlockIndex = this.attachedBlocks.indexOf(block);
  468. if (attachedBlockIndex > -1) {
  469. this.attachedBlocks.splice(attachedBlockIndex, 1);
  470. }
  471. if (block.isFinalMerger) {
  472. this.removeOutputNode(block);
  473. }
  474. }
  475. /**
  476. * Build the material and generates the inner effect
  477. * @param verbose defines if the build should log activity
  478. */
  479. public build(verbose: boolean = false) {
  480. this._buildWasSuccessful = false;
  481. var engine = this.getScene().getEngine();
  482. if (this._vertexOutputNodes.length === 0) {
  483. throw "You must define at least one vertexOutputNode";
  484. }
  485. if (this._fragmentOutputNodes.length === 0) {
  486. throw "You must define at least one fragmentOutputNode";
  487. }
  488. // Compilation state
  489. this._vertexCompilationState = new NodeMaterialBuildState();
  490. this._vertexCompilationState.supportUniformBuffers = engine.supportsUniformBuffers;
  491. this._vertexCompilationState.target = NodeMaterialBlockTargets.Vertex;
  492. this._fragmentCompilationState = new NodeMaterialBuildState();
  493. this._fragmentCompilationState.supportUniformBuffers = engine.supportsUniformBuffers;
  494. this._fragmentCompilationState.target = NodeMaterialBlockTargets.Fragment;
  495. // Shared data
  496. this._sharedData = new NodeMaterialBuildStateSharedData();
  497. this._vertexCompilationState.sharedData = this._sharedData;
  498. this._fragmentCompilationState.sharedData = this._sharedData;
  499. this._sharedData.buildId = this._buildId;
  500. this._sharedData.emitComments = this._options.emitComments;
  501. this._sharedData.verbose = verbose;
  502. this._sharedData.scene = this.getScene();
  503. // Initialize blocks
  504. let vertexNodes: NodeMaterialBlock[] = [];
  505. let fragmentNodes: NodeMaterialBlock[] = [];
  506. for (var vertexOutputNode of this._vertexOutputNodes) {
  507. vertexNodes.push(vertexOutputNode);
  508. this._initializeBlock(vertexOutputNode, this._vertexCompilationState, fragmentNodes);
  509. }
  510. for (var fragmentOutputNode of this._fragmentOutputNodes) {
  511. fragmentNodes.push(fragmentOutputNode);
  512. this._initializeBlock(fragmentOutputNode, this._fragmentCompilationState, vertexNodes);
  513. }
  514. // Optimize
  515. this.optimize();
  516. // Vertex
  517. for (var vertexOutputNode of vertexNodes) {
  518. vertexOutputNode.build(this._vertexCompilationState, vertexNodes);
  519. }
  520. // Fragment
  521. this._fragmentCompilationState.uniforms = this._vertexCompilationState.uniforms.slice(0);
  522. this._fragmentCompilationState._uniformDeclaration = this._vertexCompilationState._uniformDeclaration;
  523. this._fragmentCompilationState._constantDeclaration = this._vertexCompilationState._constantDeclaration;
  524. this._fragmentCompilationState._vertexState = this._vertexCompilationState;
  525. for (var fragmentOutputNode of fragmentNodes) {
  526. this._resetDualBlocks(fragmentOutputNode, this._buildId - 1);
  527. }
  528. for (var fragmentOutputNode of fragmentNodes) {
  529. fragmentOutputNode.build(this._fragmentCompilationState, fragmentNodes);
  530. }
  531. // Finalize
  532. this._vertexCompilationState.finalize(this._vertexCompilationState);
  533. this._fragmentCompilationState.finalize(this._fragmentCompilationState);
  534. this._buildId = NodeMaterial._BuildIdGenerator++;
  535. // Errors
  536. this._sharedData.emitErrors();
  537. if (verbose) {
  538. console.log("Vertex shader:");
  539. console.log(this._vertexCompilationState.compilationString);
  540. console.log("Fragment shader:");
  541. console.log(this._fragmentCompilationState.compilationString);
  542. }
  543. this._buildWasSuccessful = true;
  544. this.onBuildObservable.notifyObservers(this);
  545. // Wipe defines
  546. const meshes = this.getScene().meshes;
  547. for (var mesh of meshes) {
  548. if (!mesh.subMeshes) {
  549. continue;
  550. }
  551. for (var subMesh of mesh.subMeshes) {
  552. if (subMesh.getMaterial() !== this) {
  553. continue;
  554. }
  555. if (!subMesh._materialDefines) {
  556. continue;
  557. }
  558. let defines = subMesh._materialDefines;
  559. defines.markAllAsDirty();
  560. defines.reset();
  561. }
  562. }
  563. }
  564. /**
  565. * Runs an otpimization phase to try to improve the shader code
  566. */
  567. public optimize() {
  568. for (var optimizer of this._optimizers) {
  569. optimizer.optimize(this._vertexOutputNodes, this._fragmentOutputNodes);
  570. }
  571. }
  572. private _prepareDefinesForAttributes(mesh: AbstractMesh, defines: NodeMaterialDefines) {
  573. let oldNormal = defines["NORMAL"];
  574. let oldTangent = defines["TANGENT"];
  575. let oldUV1 = defines["UV1"];
  576. defines["NORMAL"] = mesh.isVerticesDataPresent(VertexBuffer.NormalKind);
  577. defines["TANGENT"] = mesh.isVerticesDataPresent(VertexBuffer.TangentKind);
  578. defines["UV1"] = mesh.isVerticesDataPresent(VertexBuffer.UVKind);
  579. if (oldNormal !== defines["NORMAL"] || oldTangent !== defines["TANGENT"] || oldUV1 !== defines["UV1"]) {
  580. defines.markAsAttributesDirty();
  581. }
  582. }
  583. /**
  584. * Get if the submesh is ready to be used and all its information available.
  585. * Child classes can use it to update shaders
  586. * @param mesh defines the mesh to check
  587. * @param subMesh defines which submesh to check
  588. * @param useInstances specifies that instances should be used
  589. * @returns a boolean indicating that the submesh is ready or not
  590. */
  591. public isReadyForSubMesh(mesh: AbstractMesh, subMesh: SubMesh, useInstances: boolean = false): boolean {
  592. if (!this._buildWasSuccessful) {
  593. return false;
  594. }
  595. var scene = this.getScene();
  596. if (this._sharedData.animatedInputs) {
  597. let frameId = scene.getFrameId();
  598. if (this._animationFrame !== frameId) {
  599. for (var input of this._sharedData.animatedInputs) {
  600. input.animate(scene);
  601. }
  602. this._animationFrame = frameId;
  603. }
  604. }
  605. if (subMesh.effect && this.isFrozen) {
  606. if (subMesh.effect._wasPreviouslyReady) {
  607. return true;
  608. }
  609. }
  610. if (!subMesh._materialDefines) {
  611. subMesh._materialDefines = new NodeMaterialDefines();
  612. }
  613. var defines = <NodeMaterialDefines>subMesh._materialDefines;
  614. if (this._isReadyForSubMesh(subMesh)) {
  615. return true;
  616. }
  617. var engine = scene.getEngine();
  618. this._prepareDefinesForAttributes(mesh, defines);
  619. // Check if blocks are ready
  620. if (this._sharedData.blockingBlocks.some((b) => !b.isReady(mesh, this, defines, useInstances))) {
  621. return false;
  622. }
  623. // Shared defines
  624. this._sharedData.blocksWithDefines.forEach((b) => {
  625. b.initializeDefines(mesh, this, defines, useInstances);
  626. });
  627. this._sharedData.blocksWithDefines.forEach((b) => {
  628. b.prepareDefines(mesh, this, defines, useInstances);
  629. });
  630. // Need to recompile?
  631. if (defines.isDirty) {
  632. const lightDisposed = defines._areLightsDisposed;
  633. defines.markAsProcessed();
  634. // Repeatable content generators
  635. this._vertexCompilationState.compilationString = this._vertexCompilationState._builtCompilationString;
  636. this._fragmentCompilationState.compilationString = this._fragmentCompilationState._builtCompilationString;
  637. this._sharedData.repeatableContentBlocks.forEach((b) => {
  638. b.replaceRepeatableContent(this._vertexCompilationState, this._fragmentCompilationState, mesh, defines);
  639. });
  640. // Uniforms
  641. let uniformBuffers: string[] = [];
  642. this._sharedData.dynamicUniformBlocks.forEach((b) => {
  643. b.updateUniformsAndSamples(this._vertexCompilationState, this, defines, uniformBuffers);
  644. });
  645. let mergedUniforms = this._vertexCompilationState.uniforms;
  646. this._fragmentCompilationState.uniforms.forEach((u) => {
  647. let index = mergedUniforms.indexOf(u);
  648. if (index === -1) {
  649. mergedUniforms.push(u);
  650. }
  651. });
  652. // Samplers
  653. let mergedSamplers = this._vertexCompilationState.samplers;
  654. this._fragmentCompilationState.samplers.forEach((s) => {
  655. let index = mergedSamplers.indexOf(s);
  656. if (index === -1) {
  657. mergedSamplers.push(s);
  658. }
  659. });
  660. var fallbacks = new EffectFallbacks();
  661. this._sharedData.blocksWithFallbacks.forEach((b) => {
  662. b.provideFallbacks(mesh, fallbacks);
  663. });
  664. let previousEffect = subMesh.effect;
  665. // Compilation
  666. var join = defines.toString();
  667. var effect = engine.createEffect({
  668. vertex: "nodeMaterial" + this._buildId,
  669. fragment: "nodeMaterial" + this._buildId,
  670. vertexSource: this._vertexCompilationState.compilationString,
  671. fragmentSource: this._fragmentCompilationState.compilationString
  672. }, <IEffectCreationOptions>{
  673. attributes: this._vertexCompilationState.attributes,
  674. uniformsNames: mergedUniforms,
  675. uniformBuffersNames: uniformBuffers,
  676. samplers: mergedSamplers,
  677. defines: join,
  678. fallbacks: fallbacks,
  679. onCompiled: this.onCompiled,
  680. onError: this.onError,
  681. indexParameters: { maxSimultaneousLights: this.maxSimultaneousLights, maxSimultaneousMorphTargets: defines.NUM_MORPH_INFLUENCERS }
  682. }, engine);
  683. if (effect) {
  684. if (this._onEffectCreatedObservable) {
  685. onCreatedEffectParameters.effect = effect;
  686. onCreatedEffectParameters.subMesh = subMesh;
  687. this._onEffectCreatedObservable.notifyObservers(onCreatedEffectParameters);
  688. }
  689. // Use previous effect while new one is compiling
  690. if (this.allowShaderHotSwapping && previousEffect && !effect.isReady()) {
  691. effect = previousEffect;
  692. defines.markAsUnprocessed();
  693. if (lightDisposed) {
  694. // re register in case it takes more than one frame.
  695. defines._areLightsDisposed = true;
  696. return false;
  697. }
  698. } else {
  699. scene.resetCachedMaterial();
  700. subMesh.setEffect(effect, defines);
  701. }
  702. }
  703. }
  704. if (!subMesh.effect || !subMesh.effect.isReady()) {
  705. return false;
  706. }
  707. defines._renderId = scene.getRenderId();
  708. subMesh.effect._wasPreviouslyReady = true;
  709. return true;
  710. }
  711. /**
  712. * Get a string representing the shaders built by the current node graph
  713. */
  714. public get compiledShaders() {
  715. return `// Vertex shader\r\n${this._vertexCompilationState.compilationString}\r\n\r\n// Fragment shader\r\n${this._fragmentCompilationState.compilationString}`;
  716. }
  717. /**
  718. * Binds the world matrix to the material
  719. * @param world defines the world transformation matrix
  720. */
  721. public bindOnlyWorldMatrix(world: Matrix): void {
  722. var scene = this.getScene();
  723. if (!this._activeEffect) {
  724. return;
  725. }
  726. let hints = this._sharedData.hints;
  727. if (hints.needWorldViewMatrix) {
  728. world.multiplyToRef(scene.getViewMatrix(), this._cachedWorldViewMatrix);
  729. }
  730. if (hints.needWorldViewProjectionMatrix) {
  731. world.multiplyToRef(scene.getTransformMatrix(), this._cachedWorldViewProjectionMatrix);
  732. }
  733. // Connection points
  734. for (var inputBlock of this._sharedData.inputBlocks) {
  735. inputBlock._transmitWorld(this._activeEffect, world, this._cachedWorldViewMatrix, this._cachedWorldViewProjectionMatrix);
  736. }
  737. }
  738. /**
  739. * Binds the submesh to this material by preparing the effect and shader to draw
  740. * @param world defines the world transformation matrix
  741. * @param mesh defines the mesh containing the submesh
  742. * @param subMesh defines the submesh to bind the material to
  743. */
  744. public bindForSubMesh(world: Matrix, mesh: Mesh, subMesh: SubMesh): void {
  745. let scene = this.getScene();
  746. var effect = subMesh.effect;
  747. if (!effect) {
  748. return;
  749. }
  750. this._activeEffect = effect;
  751. // Matrices
  752. this.bindOnlyWorldMatrix(world);
  753. let mustRebind = this._mustRebind(scene, effect, mesh.visibility);
  754. if (mustRebind) {
  755. let sharedData = this._sharedData;
  756. if (effect && scene.getCachedEffect() !== effect) {
  757. // Bindable blocks
  758. for (var block of sharedData.bindableBlocks) {
  759. block.bind(effect, this, mesh, subMesh);
  760. }
  761. // Connection points
  762. for (var inputBlock of sharedData.inputBlocks) {
  763. inputBlock._transmit(effect, scene);
  764. }
  765. }
  766. }
  767. this._afterBind(mesh, this._activeEffect);
  768. }
  769. /**
  770. * Gets the active textures from the material
  771. * @returns an array of textures
  772. */
  773. public getActiveTextures(): BaseTexture[] {
  774. var activeTextures = super.getActiveTextures();
  775. if (this._sharedData) {
  776. activeTextures.push(...this._sharedData.textureBlocks.filter((tb) => tb.texture).map((tb) => tb.texture!));
  777. }
  778. return activeTextures;
  779. }
  780. /**
  781. * Gets the list of texture blocks
  782. * @returns an array of texture blocks
  783. */
  784. public getTextureBlocks(): (TextureBlock | ReflectionTextureBaseBlock)[] {
  785. if (!this._sharedData) {
  786. return [];
  787. }
  788. return this._sharedData.textureBlocks;
  789. }
  790. /**
  791. * Specifies if the material uses a texture
  792. * @param texture defines the texture to check against the material
  793. * @returns a boolean specifying if the material uses the texture
  794. */
  795. public hasTexture(texture: BaseTexture): boolean {
  796. if (super.hasTexture(texture)) {
  797. return true;
  798. }
  799. if (!this._sharedData) {
  800. return false;
  801. }
  802. for (var t of this._sharedData.textureBlocks) {
  803. if (t.texture === texture) {
  804. return true;
  805. }
  806. }
  807. return false;
  808. }
  809. /**
  810. * Disposes the material
  811. * @param forceDisposeEffect specifies if effects should be forcefully disposed
  812. * @param forceDisposeTextures specifies if textures should be forcefully disposed
  813. * @param notBoundToMesh specifies if the material that is being disposed is known to be not bound to any mesh
  814. */
  815. public dispose(forceDisposeEffect?: boolean, forceDisposeTextures?: boolean, notBoundToMesh?: boolean): void {
  816. if (forceDisposeTextures) {
  817. for (var texture of this._sharedData.textureBlocks.filter((tb) => tb.texture).map((tb) => tb.texture!)) {
  818. texture.dispose();
  819. }
  820. }
  821. for (var block of this.attachedBlocks) {
  822. block.dispose();
  823. }
  824. this.onBuildObservable.clear();
  825. super.dispose(forceDisposeEffect, forceDisposeTextures, notBoundToMesh);
  826. }
  827. /** Creates the node editor window. */
  828. private _createNodeEditor() {
  829. this.BJSNODEMATERIALEDITOR = this.BJSNODEMATERIALEDITOR || this._getGlobalNodeMaterialEditor();
  830. this.BJSNODEMATERIALEDITOR.NodeEditor.Show({
  831. nodeMaterial: this
  832. });
  833. }
  834. /**
  835. * Launch the node material editor
  836. * @param config Define the configuration of the editor
  837. * @return a promise fulfilled when the node editor is visible
  838. */
  839. public edit(config?: INodeMaterialEditorOptions): Promise<void> {
  840. return new Promise((resolve, reject) => {
  841. if (typeof this.BJSNODEMATERIALEDITOR == 'undefined') {
  842. const editorUrl = config && config.editorURL ? config.editorURL : NodeMaterial.EditorURL;
  843. // Load editor and add it to the DOM
  844. Tools.LoadScript(editorUrl, () => {
  845. this._createNodeEditor();
  846. resolve();
  847. });
  848. } else {
  849. // Otherwise creates the editor
  850. this._createNodeEditor();
  851. resolve();
  852. }
  853. });
  854. }
  855. /**
  856. * Clear the current material
  857. */
  858. public clear() {
  859. this._vertexOutputNodes = [];
  860. this._fragmentOutputNodes = [];
  861. this.attachedBlocks = [];
  862. }
  863. /**
  864. * Clear the current material and set it to a default state
  865. */
  866. public setToDefault() {
  867. this.clear();
  868. this.editorData = null;
  869. var positionInput = new InputBlock("Position");
  870. positionInput.setAsAttribute("position");
  871. var worldInput = new InputBlock("World");
  872. worldInput.setAsSystemValue(BABYLON.NodeMaterialSystemValues.World);
  873. var worldPos = new TransformBlock("WorldPos");
  874. positionInput.connectTo(worldPos);
  875. worldInput.connectTo(worldPos);
  876. var viewProjectionInput = new InputBlock("ViewProjection");
  877. viewProjectionInput.setAsSystemValue(BABYLON.NodeMaterialSystemValues.ViewProjection);
  878. var worldPosdMultipliedByViewProjection = new TransformBlock("WorldPos * ViewProjectionTransform");
  879. worldPos.connectTo(worldPosdMultipliedByViewProjection);
  880. viewProjectionInput.connectTo(worldPosdMultipliedByViewProjection);
  881. var vertexOutput = new VertexOutputBlock("VertexOutput");
  882. worldPosdMultipliedByViewProjection.connectTo(vertexOutput);
  883. // Pixel
  884. var pixelColor = new InputBlock("color");
  885. pixelColor.value = new Color4(0.8, 0.8, 0.8, 1);
  886. var fragmentOutput = new FragmentOutputBlock("FragmentOutput");
  887. pixelColor.connectTo(fragmentOutput);
  888. // Add to nodes
  889. this.addOutputNode(vertexOutput);
  890. this.addOutputNode(fragmentOutput);
  891. }
  892. /**
  893. * Loads the current Node Material from a url pointing to a file save by the Node Material Editor
  894. * @param url defines the url to load from
  895. * @returns a promise that will fullfil when the material is fully loaded
  896. */
  897. public loadAsync(url: string) {
  898. return this.getScene()._loadFileAsync(url).then((data) => {
  899. const serializationObject = JSON.parse(data as string);
  900. this.loadFromSerialization(serializationObject, "");
  901. });
  902. }
  903. private _gatherBlocks(rootNode: NodeMaterialBlock, list: NodeMaterialBlock[]) {
  904. if (list.indexOf(rootNode) !== -1) {
  905. return;
  906. }
  907. list.push(rootNode);
  908. for (var input of rootNode.inputs) {
  909. let connectedPoint = input.connectedPoint;
  910. if (connectedPoint) {
  911. let block = connectedPoint.ownerBlock;
  912. if (block !== rootNode) {
  913. this._gatherBlocks(block, list);
  914. }
  915. }
  916. }
  917. }
  918. /**
  919. * Generate a string containing the code declaration required to create an equivalent of this material
  920. * @returns a string
  921. */
  922. public generateCode() {
  923. let alreadyDumped: NodeMaterialBlock[] = [];
  924. let vertexBlocks: NodeMaterialBlock[] = [];
  925. let uniqueNames: string[] = [];
  926. // Gets active blocks
  927. for (var outputNode of this._vertexOutputNodes) {
  928. this._gatherBlocks(outputNode, vertexBlocks);
  929. }
  930. let fragmentBlocks: NodeMaterialBlock[] = [];
  931. for (var outputNode of this._fragmentOutputNodes) {
  932. this._gatherBlocks(outputNode, fragmentBlocks);
  933. }
  934. // Generate vertex shader
  935. let codeString = `var nodeMaterial = new BABYLON.NodeMaterial("${this.name || "node material"}");\r\n`;
  936. for (var node of vertexBlocks) {
  937. if (node.isInput && alreadyDumped.indexOf(node) === -1) {
  938. codeString += node._dumpCode(uniqueNames, alreadyDumped);
  939. }
  940. }
  941. // Generate fragment shader
  942. for (var node of fragmentBlocks) {
  943. if (node.isInput && alreadyDumped.indexOf(node) === -1) {
  944. codeString += node._dumpCode(uniqueNames, alreadyDumped);
  945. }
  946. }
  947. // Connections
  948. alreadyDumped = [];
  949. codeString += "\r\n// Connections\r\n";
  950. for (var node of this._vertexOutputNodes) {
  951. codeString += node._dumpCodeForOutputConnections(alreadyDumped);
  952. }
  953. for (var node of this._fragmentOutputNodes) {
  954. codeString += node._dumpCodeForOutputConnections(alreadyDumped);
  955. }
  956. // Output nodes
  957. codeString += "\r\n// Output nodes\r\n";
  958. for (var node of this._vertexOutputNodes) {
  959. codeString += `nodeMaterial.addOutputNode(${node._codeVariableName});\r\n`;
  960. }
  961. for (var node of this._fragmentOutputNodes) {
  962. codeString += `nodeMaterial.addOutputNode(${node._codeVariableName});\r\n`;
  963. }
  964. codeString += `nodeMaterial.build();\r\n`;
  965. return codeString;
  966. }
  967. /**
  968. * Serializes this material in a JSON representation
  969. * @returns the serialized material object
  970. */
  971. public serialize(selectedBlocks?: NodeMaterialBlock[]): any {
  972. var serializationObject = selectedBlocks ? {} : SerializationHelper.Serialize(this);
  973. serializationObject.editorData = JSON.parse(JSON.stringify(this.editorData)); // Copy
  974. let blocks: NodeMaterialBlock[] = [];
  975. if (selectedBlocks) {
  976. blocks = selectedBlocks;
  977. } else {
  978. serializationObject.customType = "BABYLON.NodeMaterial";
  979. serializationObject.outputNodes = [];
  980. // Outputs
  981. for (var outputNode of this._vertexOutputNodes) {
  982. this._gatherBlocks(outputNode, blocks);
  983. serializationObject.outputNodes.push(outputNode.uniqueId);
  984. }
  985. for (var outputNode of this._fragmentOutputNodes) {
  986. this._gatherBlocks(outputNode, blocks);
  987. if (serializationObject.outputNodes.indexOf(outputNode.uniqueId) === -1) {
  988. serializationObject.outputNodes.push(outputNode.uniqueId);
  989. }
  990. }
  991. }
  992. // Blocks
  993. serializationObject.blocks = [];
  994. for (var block of blocks) {
  995. serializationObject.blocks.push(block.serialize());
  996. }
  997. if (!selectedBlocks) {
  998. for (var block of this.attachedBlocks) {
  999. if (blocks.indexOf(block) !== -1) {
  1000. continue;
  1001. }
  1002. serializationObject.blocks.push(block.serialize());
  1003. }
  1004. }
  1005. return serializationObject;
  1006. }
  1007. private _restoreConnections(block: NodeMaterialBlock, source: any, map: {[key: number]: NodeMaterialBlock}) {
  1008. for (var outputPoint of block.outputs) {
  1009. for (var candidate of source.blocks) {
  1010. let target = map[candidate.id];
  1011. for (var input of candidate.inputs) {
  1012. if (map[input.targetBlockId] === block && input.targetConnectionName === outputPoint.name) {
  1013. let inputPoint = target.getInputByName(input.inputName);
  1014. if (!inputPoint || inputPoint.isConnected) {
  1015. continue;
  1016. }
  1017. outputPoint.connectTo(inputPoint, true);
  1018. this._restoreConnections(target, source, map);
  1019. continue;
  1020. }
  1021. }
  1022. }
  1023. }
  1024. }
  1025. /**
  1026. * Clear the current graph and load a new one from a serialization object
  1027. * @param source defines the JSON representation of the material
  1028. * @param rootUrl defines the root URL to use to load textures and relative dependencies
  1029. */
  1030. public loadFromSerialization(source: any, rootUrl: string = "") {
  1031. this.clear();
  1032. let map: {[key: number]: NodeMaterialBlock} = {};
  1033. // Create blocks
  1034. for (var parsedBlock of source.blocks) {
  1035. let blockType = _TypeStore.GetClass(parsedBlock.customType);
  1036. if (blockType) {
  1037. let block: NodeMaterialBlock = new blockType();
  1038. block._deserialize(parsedBlock, this.getScene(), rootUrl);
  1039. map[parsedBlock.id] = block;
  1040. this.attachedBlocks.push(block);
  1041. }
  1042. }
  1043. // Connections
  1044. // Starts with input blocks only
  1045. for (var blockIndex = 0; blockIndex < source.blocks.length; blockIndex++) {
  1046. let parsedBlock = source.blocks[blockIndex];
  1047. let block = map[parsedBlock.id];
  1048. if (block.inputs.length) {
  1049. continue;
  1050. }
  1051. this._restoreConnections(block, source, map);
  1052. }
  1053. // Outputs
  1054. for (var outputNodeId of source.outputNodes) {
  1055. this.addOutputNode(map[outputNodeId]);
  1056. }
  1057. // UI related info
  1058. if (source.locations || source.editorData && source.editorData.locations) {
  1059. let locations: {
  1060. blockId: number;
  1061. x: number;
  1062. y: number;
  1063. }[] = source.locations || source.editorData.locations;
  1064. for (var location of locations) {
  1065. if (map[location.blockId]) {
  1066. location.blockId = map[location.blockId].uniqueId;
  1067. }
  1068. }
  1069. if (source.locations) {
  1070. this.editorData = {
  1071. locations: locations
  1072. };
  1073. } else {
  1074. this.editorData = source.editorData;
  1075. this.editorData.locations = locations;
  1076. }
  1077. let blockMap: number[] = [];
  1078. for (var key in map) {
  1079. blockMap[key] = map[key].uniqueId;
  1080. }
  1081. this.editorData.map = blockMap;
  1082. }
  1083. }
  1084. /**
  1085. * Makes a duplicate of the current material.
  1086. * @param name - name to use for the new material.
  1087. */
  1088. public clone(name: string): NodeMaterial {
  1089. const serializationObject = this.serialize();
  1090. const clone = SerializationHelper.Clone(() => new NodeMaterial(name, this.getScene(), this.options), this);
  1091. clone.id = name;
  1092. clone.name = name;
  1093. clone.loadFromSerialization(serializationObject);
  1094. clone.build();
  1095. return clone;
  1096. }
  1097. /**
  1098. * Creates a node material from parsed material data
  1099. * @param source defines the JSON representation of the material
  1100. * @param scene defines the hosting scene
  1101. * @param rootUrl defines the root URL to use to load textures and relative dependencies
  1102. * @returns a new node material
  1103. */
  1104. public static Parse(source: any, scene: Scene, rootUrl: string = ""): NodeMaterial {
  1105. let nodeMaterial = SerializationHelper.Parse(() => new NodeMaterial(source.name, scene), source, scene, rootUrl);
  1106. nodeMaterial.loadFromSerialization(source, rootUrl);
  1107. nodeMaterial.build();
  1108. return nodeMaterial;
  1109. }
  1110. /**
  1111. * Creates a node material from a snippet saved in a remote file
  1112. * @param name defines the name of the material to create
  1113. * @param url defines the url to load from
  1114. * @param scene defines the hosting scene
  1115. * @returns a promise that will resolve to the new node material
  1116. */
  1117. public static ParseFromFileAsync(name: string, url: string, scene: Scene): Promise<NodeMaterial> {
  1118. var material = new NodeMaterial(name, scene);
  1119. return new Promise((resolve, reject) => {
  1120. return material.loadAsync(url).then(() => resolve(material)).catch(reject);
  1121. });
  1122. }
  1123. /**
  1124. * Creates a node material from a snippet saved by the node material editor
  1125. * @param snippetId defines the snippet to load
  1126. * @param scene defines the hosting scene
  1127. * @param rootUrl defines the root URL to use to load textures and relative dependencies
  1128. * @param nodeMaterial defines a node material to update (instead of creating a new one)
  1129. * @returns a promise that will resolve to the new node material
  1130. */
  1131. public static ParseFromSnippetAsync(snippetId: string, scene: Scene, rootUrl: string = "", nodeMaterial?: NodeMaterial): Promise<NodeMaterial> {
  1132. return new Promise((resolve, reject) => {
  1133. var request = new WebRequest();
  1134. request.addEventListener("readystatechange", () => {
  1135. if (request.readyState == 4) {
  1136. if (request.status == 200) {
  1137. var snippet = JSON.parse(JSON.parse(request.responseText).jsonPayload);
  1138. let serializationObject = JSON.parse(snippet.nodeMaterial);
  1139. if (!nodeMaterial) {
  1140. nodeMaterial = SerializationHelper.Parse(() => new NodeMaterial(snippetId, scene), serializationObject, scene, rootUrl);
  1141. nodeMaterial.uniqueId = scene.getUniqueId();
  1142. }
  1143. nodeMaterial.loadFromSerialization(serializationObject);
  1144. nodeMaterial.snippetId = snippetId;
  1145. try {
  1146. nodeMaterial.build();
  1147. resolve(nodeMaterial);
  1148. } catch (err) {
  1149. reject(err);
  1150. }
  1151. } else {
  1152. reject("Unable to load the snippet " + snippetId);
  1153. }
  1154. }
  1155. });
  1156. request.open("GET", this.SnippetUrl + "/" + snippetId.replace("#", "/"));
  1157. request.send();
  1158. });
  1159. }
  1160. /**
  1161. * Creates a new node material set to default basic configuration
  1162. * @param name defines the name of the material
  1163. * @param scene defines the hosting scene
  1164. * @returns a new NodeMaterial
  1165. */
  1166. public static CreateDefault(name: string, scene?: Scene) {
  1167. let newMaterial = new NodeMaterial(name, scene);
  1168. newMaterial.setToDefault();
  1169. newMaterial.build();
  1170. return newMaterial;
  1171. }
  1172. }
  1173. _TypeStore.RegisteredTypes["BABYLON.NodeMaterial"] = NodeMaterial;