nodeMaterialBlock.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. import { NodeMaterialBlockConnectionPointTypes } from './nodeMaterialBlockConnectionPointTypes';
  2. import { NodeMaterialCompilationState } from './nodeMaterialCompilationState';
  3. import { Nullable } from '../../types';
  4. import { NodeMaterialConnectionPoint } from './nodeMaterialBlockConnectionPoint';
  5. import { NodeMaterialBlockTargets } from './nodeMaterialBlockTargets';
  6. /**
  7. * Defines a block that can be used inside a node based material
  8. */
  9. export class NodeMaterialBlock {
  10. private _buildId: number;
  11. private _target: NodeMaterialBlockTargets;
  12. private _isFinalMerger = false;
  13. /** @hidden */
  14. protected _inputs = new Array<NodeMaterialConnectionPoint>();
  15. /** @hidden */
  16. protected _outputs = new Array<NodeMaterialConnectionPoint>();
  17. /**
  18. * Gets or sets the name of the block
  19. */
  20. public name: string;
  21. /**
  22. * Gets a boolean indicating that this block is an end block (e.g. it is generating a system value)
  23. */
  24. public get isFinalMerger(): boolean {
  25. return this._isFinalMerger;
  26. }
  27. /**
  28. * Gets or sets the build Id
  29. */
  30. public get buildId(): number {
  31. return this._buildId;
  32. }
  33. public set buildId(value: number) {
  34. this._buildId = value;
  35. }
  36. /**
  37. * Gets or sets the target of the block
  38. */
  39. public get target() {
  40. return this._target;
  41. }
  42. public set target(value: NodeMaterialBlockTargets) {
  43. if ((this._target & value) !== 0) {
  44. return;
  45. }
  46. this._target = value;
  47. }
  48. /**
  49. * Gets the list of input points
  50. */
  51. public get inputs(): NodeMaterialConnectionPoint[] {
  52. return this._inputs;
  53. }
  54. /** Gets the list of output points */
  55. public get outputs(): NodeMaterialConnectionPoint[] {
  56. return this._outputs;
  57. }
  58. /**
  59. * Find an input by its name
  60. * @param name defines the name of the input to look for
  61. * @returns the input or null if not found
  62. */
  63. public getInputByName(name: string) {
  64. let filter = this._inputs.filter((e) => e.name === name);
  65. if (filter.length) {
  66. return filter[0];
  67. }
  68. return null;
  69. }
  70. /**
  71. * Find an output by its name
  72. * @param name defines the name of the outputto look for
  73. * @returns the output or null if not found
  74. */
  75. public getOutputByName(name: string) {
  76. let filter = this._outputs.filter((e) => e.name === name);
  77. if (filter.length) {
  78. return filter[0];
  79. }
  80. return null;
  81. }
  82. /**
  83. * Creates a new NodeMaterialBlock
  84. * @param name defines the block name
  85. * @param target defines the target of that block (Vertex by default)
  86. * @param isFinalMerger defines a boolean indicating that this block is an end block (e.g. it is generating a system value). Default is false
  87. */
  88. public constructor(name: string, target = NodeMaterialBlockTargets.Vertex, isFinalMerger = false) {
  89. this.name = name;
  90. this._target = target;
  91. if (isFinalMerger) {
  92. this._isFinalMerger = true;
  93. }
  94. }
  95. protected _declareOutput(output: NodeMaterialConnectionPoint, state: NodeMaterialCompilationState): string {
  96. if (output.isVarying) {
  97. return `${output.associatedVariableName}`;
  98. }
  99. return `${state._getGLType(output.type)} ${output.associatedVariableName}`;
  100. }
  101. protected _writeFloat(value: number) {
  102. let stringVersion = value.toString();
  103. if (stringVersion.indexOf(".") === -1) {
  104. stringVersion += ".0";
  105. }
  106. return `${stringVersion}`;
  107. }
  108. /**
  109. * Gets the current class name e.g. "NodeMaterialBlock"
  110. * @returns the class name
  111. */
  112. public getClassName() {
  113. return "NodeMaterialBlock";
  114. }
  115. /**
  116. * Register a new input. Must be called inside a block constructor
  117. * @param name defines the connection point name
  118. * @param type defines the connection point type
  119. * @param isOptional defines a boolean indicating that this input can be omitted
  120. * @param target defines the target to use to limit the connection point (will be VetexAndFragment by default)
  121. * @returns the current block
  122. */
  123. public registerInput(name: string, type: NodeMaterialBlockConnectionPointTypes, isOptional: boolean = false, target?: NodeMaterialBlockTargets) {
  124. let point = new NodeMaterialConnectionPoint(name, this);
  125. point.type = type;
  126. point.isOptional = isOptional;
  127. if (target) {
  128. point.target = target;
  129. }
  130. this._inputs.push(point);
  131. return this;
  132. }
  133. /**
  134. * Register a new output. Must be called inside a block constructor
  135. * @param name defines the connection point name
  136. * @param type defines the connection point type
  137. * @param target defines the target to use to limit the connection point (will be VetexAndFragment by default)
  138. * @returns the current block
  139. */
  140. public registerOutput(name: string, type: NodeMaterialBlockConnectionPointTypes, target?: NodeMaterialBlockTargets) {
  141. let point = new NodeMaterialConnectionPoint(name, this);
  142. point.type = type;
  143. if (target) {
  144. point.target = target;
  145. }
  146. this._outputs.push(point);
  147. return this;
  148. }
  149. /**
  150. * Will return the first available input e.g. the first one which is not an uniform or an attribute
  151. * @param forOutput defines an optional connection point to check compatibility with
  152. * @returns the first available input or null
  153. */
  154. public getFirstAvailableInput(forOutput: Nullable<NodeMaterialConnectionPoint> = null) {
  155. for (var input of this._inputs) {
  156. if (!input.isUniform && !input.isAttribute && !input.connectedPoint) {
  157. if (!forOutput || (forOutput.type & input.type) !== 0) {
  158. return input;
  159. }
  160. }
  161. }
  162. return null;
  163. }
  164. /**
  165. * Will return the first available output e.g. the first one which is not yet connected and not a varying
  166. * @param forBlock defines an optional block to check compatibility with
  167. * @returns the first available input or null
  168. */
  169. public getFirstAvailableOutput(forBlock: Nullable<NodeMaterialBlock> = null) {
  170. for (var output of this._outputs) {
  171. if (!forBlock || !forBlock.target || (forBlock.target & output.target) !== 0) {
  172. return output;
  173. }
  174. }
  175. return null;
  176. }
  177. /**
  178. * Connect current block with another block
  179. * @param other defines the block to connect with
  180. * @param inputName define the name of the other block input (will take the first available one if not defined)
  181. * @param outputName define the name of current block output (will take the first one if not defined)
  182. * @returns the current block
  183. */
  184. public connectTo(other: NodeMaterialBlock, inputName?: string, outputName?: string) {
  185. if (this._outputs.length === 0) {
  186. return;
  187. }
  188. let output = outputName ? this.getOutputByName(outputName) : this.getFirstAvailableOutput(other);
  189. let input = inputName ? other.getInputByName(inputName) : other.getFirstAvailableInput(output);
  190. if (output && input) {
  191. output.connectTo(input);
  192. } else {
  193. throw "Unable to find a compatible match";
  194. }
  195. return this;
  196. }
  197. protected _buildBlock(state: NodeMaterialCompilationState) {
  198. // Empty. Must be defined by child nodes
  199. }
  200. /** @hidden */
  201. public get _canAddAtVertexRoot(): boolean {
  202. return true; // Must be overriden by children
  203. }
  204. /** @hidden */
  205. public get _canAddAtFragmentRoot(): boolean {
  206. return true; // Must be overriden by children
  207. }
  208. /**
  209. * Compile the current node and generate the shader code
  210. * @param state defines the current compilation state (uniforms, samplers, current string)
  211. * @returns the current block
  212. */
  213. public build(state: NodeMaterialCompilationState) {
  214. if (this._buildId === state.sharedData.buildId) {
  215. return;
  216. }
  217. // Check if "parent" blocks are compiled
  218. for (var input of this._inputs) {
  219. if (!input.connectedPoint) {
  220. if (!input.isOptional && !input.isAttribute && !input.isUniform) { // Emit a warning
  221. state.sharedData.checks.notConnectedNonOptionalInputs.push(input);
  222. }
  223. continue;
  224. }
  225. if ((input.target & this.target!) === 0) {
  226. continue;
  227. }
  228. let block = input.connectedPoint.ownerBlock;
  229. if (block && block.target === this.target && block.buildId !== state.sharedData.buildId) {
  230. block.build(state);
  231. }
  232. }
  233. if (this._buildId === state.sharedData.buildId) {
  234. return; // Need to check again as inputs can be connected multiple time to this endpoint
  235. }
  236. // Logs
  237. if (state.sharedData.verbose) {
  238. console.log(`${state.target === NodeMaterialBlockTargets.Vertex ? "Vertex shader" : "Fragment shader"}: Building ${this.name} [${this.getClassName()}]`);
  239. }
  240. // Build
  241. for (var input of this._inputs) {
  242. if ((input.target & this.target!) === 0) {
  243. continue;
  244. }
  245. state._emitUniformOrAttributes(input);
  246. }
  247. // Checks final outputs
  248. if (this.isFinalMerger) {
  249. switch (state.target) {
  250. case NodeMaterialBlockTargets.Vertex:
  251. state.sharedData.checks.emitVertex = true;
  252. break;
  253. case NodeMaterialBlockTargets.Fragment:
  254. state.sharedData.checks.emitFragment = true;
  255. break;
  256. }
  257. }
  258. /** Prepare outputs */
  259. for (var output of this._outputs) {
  260. if ((output.target & this.target!) === 0 || output.associatedVariableName) {
  261. continue;
  262. }
  263. output.associatedVariableName = state._getFreeVariableName(output.name);
  264. state._emitVaryings(output);
  265. }
  266. if (state.sharedData.emitComments) {
  267. state.compilationString += `\r\n//${this.name}\r\n`;
  268. }
  269. this._buildBlock(state);
  270. this._buildId = state.sharedData.buildId;
  271. // Compile connected blocks
  272. for (var output of this._outputs) {
  273. if ((output.target & state.target) === 0) {
  274. continue;
  275. }
  276. for (var block of output.connectedBlocks) {
  277. if (block && (block.target & state.target) !== 0) {
  278. block.build(state);
  279. }
  280. }
  281. }
  282. return this;
  283. }
  284. }