ShaderNode.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. import Node, { addNodeClass } from '../core/Node.js';
  2. import ArrayElementNode from '../utils/ArrayElementNode.js';
  3. import ConvertNode from '../utils/ConvertNode.js';
  4. import JoinNode from '../utils/JoinNode.js';
  5. import SplitNode from '../utils/SplitNode.js';
  6. import ConstNode from '../core/ConstNode.js';
  7. import { getValueFromType, getValueType } from '../core/NodeUtils.js';
  8. const NodeElements = new Map(); // @TODO: Currently only a few nodes are added, probably also add others
  9. export function addNodeElement( name, nodeElement ) {
  10. if ( NodeElements.has( name ) ) throw new Error( `Redefinition of node element ${ name }` );
  11. if ( typeof nodeElement !== 'function' ) throw new Error( `Node element ${ name } is not a function` );
  12. NodeElements.set( name, nodeElement );
  13. }
  14. const shaderNodeHandler = {
  15. construct( NodeClosure, params ) {
  16. const inputs = params.shift();
  17. return NodeClosure( nodeObjects( inputs ), ...params );
  18. },
  19. get: function ( node, prop, nodeObj ) {
  20. if ( typeof prop === 'string' && node[ prop ] === undefined ) {
  21. if ( NodeElements.has( prop ) ) {
  22. const nodeElement = NodeElements.get( prop );
  23. return ( ...params ) => nodeElement( nodeObj, ...params );
  24. } else if ( prop.endsWith( 'Assign' ) && NodeElements.has( prop.slice( 0, prop.length - 'Assign'.length ) ) ) {
  25. const nodeElement = NodeElements.get( prop.slice( 0, prop.length - 'Assign'.length ) );
  26. return ( ...params ) => nodeObj.assign( nodeElement( nodeObj, ...params ) );
  27. } else if ( /^[xyzwrgbastpq]{1,4}$/.test( prop ) === true ) {
  28. // accessing properties ( swizzle )
  29. prop = prop
  30. .replace( /r|s/g, 'x' )
  31. .replace( /g|t/g, 'y' )
  32. .replace( /b|p/g, 'z' )
  33. .replace( /a|q/g, 'w' );
  34. return nodeObject( new SplitNode( node, prop ) );
  35. } else if ( prop === 'width' || prop === 'height' ) {
  36. // accessing property
  37. return nodeObject( new SplitNode( node, prop === 'width' ? 'x' : 'y' ) );
  38. } else if ( /^\d+$/.test( prop ) === true ) {
  39. // accessing array
  40. return nodeObject( new ArrayElementNode( node, new ConstNode( Number( prop ), 'uint' ) ) );
  41. }
  42. }
  43. return node[ prop ];
  44. }
  45. };
  46. const nodeObjectsCacheMap = new WeakMap();
  47. const ShaderNodeObject = function ( obj, altType = null ) {
  48. const type = getValueType( obj );
  49. if ( type === 'node' ) {
  50. let nodeObject = nodeObjectsCacheMap.get( obj );
  51. if ( nodeObject === undefined ) {
  52. nodeObject = new Proxy( obj, shaderNodeHandler );
  53. nodeObjectsCacheMap.set( obj, nodeObject );
  54. nodeObjectsCacheMap.set( nodeObject, nodeObject );
  55. }
  56. return nodeObject;
  57. } else if ( ( altType === null ) && ( ( type === 'float' ) || ( type === 'boolean' ) ) ) {
  58. return nodeObject( getAutoTypedConstNode( obj ) );
  59. } else if ( type === 'shader' ) {
  60. return shader( obj );
  61. } else if ( type && type !== 'string' ) {
  62. return nodeObject( new ConstNode( obj, altType ) );
  63. }
  64. return obj;
  65. };
  66. const ShaderNodeObjects = function ( objects, altType = null ) {
  67. for ( const name in objects ) {
  68. objects[ name ] = nodeObject( objects[ name ], altType );
  69. }
  70. return objects;
  71. };
  72. const ShaderNodeArray = function ( array, altType = null ) {
  73. const len = array.length;
  74. for ( let i = 0; i < len; i ++ ) {
  75. array[ i ] = nodeObject( array[ i ], altType );
  76. }
  77. return array;
  78. };
  79. const ShaderNodeProxy = function ( NodeClass, scope = null, factor = null, settings = null ) {
  80. const assignNode = ( node ) => nodeObject( settings !== null ? Object.assign( node, settings ) : node );
  81. if ( scope === null ) {
  82. return ( ...params ) => {
  83. return assignNode( new NodeClass( ...nodeArray( params ) ) );
  84. };
  85. } else if ( factor !== null ) {
  86. factor = nodeObject( factor );
  87. return ( ...params ) => {
  88. return assignNode( new NodeClass( scope, ...nodeArray( params ), factor ) );
  89. };
  90. } else {
  91. return ( ...params ) => {
  92. return assignNode( new NodeClass( scope, ...nodeArray( params ) ) );
  93. };
  94. }
  95. };
  96. const ShaderNodeImmutable = function ( NodeClass, ...params ) {
  97. return nodeObject( new NodeClass( ...nodeArray( params ) ) );
  98. };
  99. class ShaderNodeInternal extends Node {
  100. constructor( jsFunc ) {
  101. super();
  102. this._jsFunc = jsFunc;
  103. }
  104. call( inputs, stack, builder ) {
  105. inputs = nodeObjects( inputs );
  106. return nodeObject( this._jsFunc( inputs, stack, builder ) );
  107. }
  108. getNodeType( builder ) {
  109. const { outputNode } = builder.getNodeProperties( this );
  110. return outputNode ? outputNode.getNodeType( builder ) : super.getNodeType( builder );
  111. }
  112. construct( builder ) {
  113. builder.addStack();
  114. builder.stack.outputNode = nodeObject( this._jsFunc( builder.stack, builder ) );
  115. return builder.removeStack();
  116. }
  117. }
  118. const bools = [ false, true ];
  119. const uints = [ 0, 1, 2, 3 ];
  120. const ints = [ - 1, - 2 ];
  121. const floats = [ 0.5, 1.5, 1 / 3, 1e-6, 1e6, Math.PI, Math.PI * 2, 1 / Math.PI, 2 / Math.PI, 1 / ( Math.PI * 2 ), Math.PI / 2 ];
  122. const boolsCacheMap = new Map();
  123. for ( const bool of bools ) boolsCacheMap.set( bool, new ConstNode( bool ) );
  124. const uintsCacheMap = new Map();
  125. for ( const uint of uints ) uintsCacheMap.set( uint, new ConstNode( uint, 'uint' ) );
  126. const intsCacheMap = new Map( [ ...uintsCacheMap ].map( el => new ConstNode( el.value, 'int' ) ) );
  127. for ( const int of ints ) intsCacheMap.set( int, new ConstNode( int, 'int' ) );
  128. const floatsCacheMap = new Map( [ ...intsCacheMap ].map( el => new ConstNode( el.value ) ) );
  129. for ( const float of floats ) floatsCacheMap.set( float, new ConstNode( float ) );
  130. for ( const float of floats ) floatsCacheMap.set( - float, new ConstNode( - float ) );
  131. const cacheMaps = { bool: boolsCacheMap, uint: uintsCacheMap, ints: intsCacheMap, float: floatsCacheMap };
  132. const constNodesCacheMap = new Map( [ ...boolsCacheMap, ...floatsCacheMap ] );
  133. const getAutoTypedConstNode = ( value ) => {
  134. if ( constNodesCacheMap.has( value ) ) {
  135. return constNodesCacheMap.get( value );
  136. } else if ( value.isNode === true ) {
  137. return value;
  138. } else {
  139. return new ConstNode( value );
  140. }
  141. };
  142. const ConvertType = function ( type, cacheMap = null ) {
  143. return ( ...params ) => {
  144. if ( params.length === 0 ) {
  145. return nodeObject( new ConstNode( getValueFromType( type ), type ) );
  146. } else {
  147. if ( type === 'color' && params[ 0 ].isNode !== true ) {
  148. params = [ getValueFromType( type, ...params ) ];
  149. }
  150. if ( params.length === 1 && cacheMap !== null && cacheMap.has( params[ 0 ] ) ) {
  151. return cacheMap.get( params[ 0 ] );
  152. }
  153. const nodes = params.map( getAutoTypedConstNode );
  154. if ( nodes.length === 1 ) {
  155. return nodeObject( nodes[ 0 ].nodeType === type || getValueType( nodes[ 0 ].value ) === type ? nodes[ 0 ] : new ConvertNode( nodes[ 0 ], type ) );
  156. }
  157. return nodeObject( new JoinNode( nodes, type ) );
  158. }
  159. };
  160. };
  161. // exports
  162. // utils
  163. export const getConstNodeType = ( value ) => ( value !== undefined && value !== null ) ? ( value.nodeType || value.convertTo || ( typeof value === 'string' ? value : null ) ) : null;
  164. // shader node base
  165. export function ShaderNode( jsFunc ) {
  166. return new Proxy( new ShaderNodeInternal( jsFunc ), shaderNodeHandler );
  167. }
  168. export const nodeObject = ( val, altType = null ) => /* new */ ShaderNodeObject( val, altType );
  169. export const nodeObjects = ( val, altType = null ) => new ShaderNodeObjects( val, altType );
  170. export const nodeArray = ( val, altType = null ) => new ShaderNodeArray( val, altType );
  171. export const nodeProxy = ( ...val ) => new ShaderNodeProxy( ...val );
  172. export const nodeImmutable = ( ...val ) => new ShaderNodeImmutable( ...val );
  173. export const shader = ( ...val ) => new ShaderNode( ...val );
  174. addNodeClass( ShaderNode );
  175. // types
  176. // @TODO: Maybe export from ConstNode.js?
  177. export const color = new ConvertType( 'color' );
  178. export const float = new ConvertType( 'float', cacheMaps.float );
  179. export const int = new ConvertType( 'int', cacheMaps.int );
  180. export const uint = new ConvertType( 'uint', cacheMaps.uint );
  181. export const bool = new ConvertType( 'bool', cacheMaps.bool );
  182. export const vec2 = new ConvertType( 'vec2' );
  183. export const ivec2 = new ConvertType( 'ivec2' );
  184. export const uvec2 = new ConvertType( 'uvec2' );
  185. export const bvec2 = new ConvertType( 'bvec2' );
  186. export const vec3 = new ConvertType( 'vec3' );
  187. export const ivec3 = new ConvertType( 'ivec3' );
  188. export const uvec3 = new ConvertType( 'uvec3' );
  189. export const bvec3 = new ConvertType( 'bvec3' );
  190. export const vec4 = new ConvertType( 'vec4' );
  191. export const ivec4 = new ConvertType( 'ivec4' );
  192. export const uvec4 = new ConvertType( 'uvec4' );
  193. export const bvec4 = new ConvertType( 'bvec4' );
  194. export const mat3 = new ConvertType( 'mat3' );
  195. export const imat3 = new ConvertType( 'imat3' );
  196. export const umat3 = new ConvertType( 'umat3' );
  197. export const bmat3 = new ConvertType( 'bmat3' );
  198. export const mat4 = new ConvertType( 'mat4' );
  199. export const imat4 = new ConvertType( 'imat4' );
  200. export const umat4 = new ConvertType( 'umat4' );
  201. export const bmat4 = new ConvertType( 'bmat4' );
  202. export const string = ( value = '' ) => nodeObject( new ConstNode( value, 'string' ) );
  203. export const arrayBuffer = ( value ) => nodeObject( new ConstNode( value, 'ArrayBuffer' ) );
  204. addNodeElement( 'color', color );
  205. addNodeElement( 'float', float );
  206. addNodeElement( 'int', int );
  207. addNodeElement( 'uint', uint );
  208. addNodeElement( 'bool', bool );
  209. addNodeElement( 'vec2', vec2 );
  210. addNodeElement( 'ivec2', ivec2 );
  211. addNodeElement( 'uvec2', uvec2 );
  212. addNodeElement( 'bvec2', bvec2 );
  213. addNodeElement( 'vec3', vec3 );
  214. addNodeElement( 'ivec3', ivec3 );
  215. addNodeElement( 'uvec3', uvec3 );
  216. addNodeElement( 'bvec3', bvec3 );
  217. addNodeElement( 'vec4', vec4 );
  218. addNodeElement( 'ivec4', ivec4 );
  219. addNodeElement( 'uvec4', uvec4 );
  220. addNodeElement( 'bvec4', bvec4 );
  221. addNodeElement( 'mat3', mat3 );
  222. addNodeElement( 'imat3', imat3 );
  223. addNodeElement( 'umat3', umat3 );
  224. addNodeElement( 'bmat3', bmat3 );
  225. addNodeElement( 'mat4', mat4 );
  226. addNodeElement( 'imat4', imat4 );
  227. addNodeElement( 'umat4', umat4 );
  228. addNodeElement( 'bmat4', bmat4 );
  229. addNodeElement( 'string', string );
  230. addNodeElement( 'arrayBuffer', arrayBuffer );
  231. // basic nodes
  232. // HACK - we cannot export them from the corresponding files because of the cyclic dependency
  233. export const element = nodeProxy( ArrayElementNode );
  234. export const convert = ( node, types ) => nodeObject( new ConvertNode( nodeObject( node ), types ) );
  235. export const split = ( node, channels ) => nodeObject( new SplitNode( nodeObject( node ), channels ) );
  236. addNodeElement( 'element', element );
  237. addNodeElement( 'convert', convert );