createCompositor.js

  1. import util from './core/util';
  2. import Compositor from './compositor/Compositor';
  3. import CompoSceneNode from './compositor/SceneNode';
  4. import CompoTextureNode from './compositor/TextureNode';
  5. import CompoFilterNode from './compositor/FilterNode';
  6. import Shader from './Shader';
  7. import Texture from './Texture';
  8. import Texture2D from './Texture2D';
  9. import TextureCube from './TextureCube';
  10. import registerBuiltinCompositor from './shader/registerBuiltinCompositor';
  11. registerBuiltinCompositor(Shader);
  12. var shaderSourceReg = /^#source\((.*?)\)/;
  13. /**
  14. * @name clay.createCompositor
  15. * @function
  16. * @param {Object} json
  17. * @param {Object} [opts]
  18. * @return {clay.compositor.Compositor}
  19. */
  20. function createCompositor(json, opts) {
  21. var compositor = new Compositor();
  22. opts = opts || {};
  23. var lib = {
  24. textures: {},
  25. parameters: {}
  26. };
  27. var afterLoad = function(shaderLib, textureLib) {
  28. for (var i = 0; i < json.nodes.length; i++) {
  29. var nodeInfo = json.nodes[i];
  30. var node = createNode(nodeInfo, lib, opts);
  31. if (node) {
  32. compositor.addNode(node);
  33. }
  34. }
  35. };
  36. for (var name in json.parameters) {
  37. var paramInfo = json.parameters[name];
  38. lib.parameters[name] = convertParameter(paramInfo);
  39. }
  40. // TODO load texture asynchronous
  41. loadTextures(json, lib, opts, function(textureLib) {
  42. lib.textures = textureLib;
  43. afterLoad();
  44. });
  45. return compositor;
  46. }
  47. function createNode(nodeInfo, lib, opts) {
  48. var type = nodeInfo.type || 'filter';
  49. var shaderSource;
  50. var inputs;
  51. var outputs;
  52. if (type === 'filter') {
  53. var shaderExp = nodeInfo.shader.trim();
  54. var res = shaderSourceReg.exec(shaderExp);
  55. if (res) {
  56. shaderSource = Shader.source(res[1].trim());
  57. }
  58. else if (shaderExp.charAt(0) === '#') {
  59. shaderSource = lib.shaders[shaderExp.substr(1)];
  60. }
  61. if (!shaderSource) {
  62. shaderSource = shaderExp;
  63. }
  64. if (!shaderSource) {
  65. return;
  66. }
  67. }
  68. if (nodeInfo.inputs) {
  69. inputs = {};
  70. for (var name in nodeInfo.inputs) {
  71. if (typeof nodeInfo.inputs[name] === 'string') {
  72. inputs[name] = nodeInfo.inputs[name];
  73. }
  74. else {
  75. inputs[name] = {
  76. node: nodeInfo.inputs[name].node,
  77. pin: nodeInfo.inputs[name].pin
  78. };
  79. }
  80. }
  81. }
  82. if (nodeInfo.outputs) {
  83. outputs = {};
  84. for (var name in nodeInfo.outputs) {
  85. var outputInfo = nodeInfo.outputs[name];
  86. outputs[name] = {};
  87. if (outputInfo.attachment != null) {
  88. outputs[name].attachment = outputInfo.attachment;
  89. }
  90. if (outputInfo.keepLastFrame != null) {
  91. outputs[name].keepLastFrame = outputInfo.keepLastFrame;
  92. }
  93. if (outputInfo.outputLastFrame != null) {
  94. outputs[name].outputLastFrame = outputInfo.outputLastFrame;
  95. }
  96. if (outputInfo.parameters) {
  97. outputs[name].parameters = convertParameter(outputInfo.parameters);
  98. }
  99. }
  100. }
  101. var node;
  102. if (type === 'scene') {
  103. node = new CompoSceneNode({
  104. name: nodeInfo.name,
  105. scene: opts.scene,
  106. camera: opts.camera,
  107. outputs: outputs
  108. });
  109. }
  110. else if (type === 'texture') {
  111. node = new CompoTextureNode({
  112. name: nodeInfo.name,
  113. outputs: outputs
  114. });
  115. }
  116. // Default is filter
  117. else {
  118. node = new CompoFilterNode({
  119. name: nodeInfo.name,
  120. shader: shaderSource,
  121. inputs: inputs,
  122. outputs: outputs
  123. });
  124. }
  125. if (node) {
  126. if (nodeInfo.parameters) {
  127. for (var name in nodeInfo.parameters) {
  128. var val = nodeInfo.parameters[name];
  129. if (typeof val === 'string') {
  130. val = val.trim();
  131. if (val.charAt(0) === '#') {
  132. val = lib.textures[val.substr(1)];
  133. }
  134. else {
  135. node.on(
  136. 'beforerender', createSizeSetHandler(
  137. name, tryConvertExpr(val)
  138. )
  139. );
  140. }
  141. }
  142. else if (typeof val === 'function') {
  143. node.on('beforerender', val);
  144. }
  145. node.setParameter(name, val);
  146. }
  147. }
  148. if (nodeInfo.defines && node.pass) {
  149. for (var name in nodeInfo.defines) {
  150. var val = nodeInfo.defines[name];
  151. node.pass.material.define('fragment', name, val);
  152. }
  153. }
  154. }
  155. return node;
  156. }
  157. function defaultWidthFunc(width, height) {
  158. return width;
  159. }
  160. function defaultHeightFunc(width, height) {
  161. return height;
  162. }
  163. function convertParameter(paramInfo) {
  164. var param = {};
  165. if (!paramInfo) {
  166. return param;
  167. }
  168. ['type', 'minFilter', 'magFilter', 'wrapS', 'wrapT', 'flipY', 'useMipmap']
  169. .forEach(function(name) {
  170. var val = paramInfo[name];
  171. if (val != null) {
  172. // Convert string to enum
  173. if (typeof val === 'string') {
  174. val = Texture[val];
  175. }
  176. param[name] = val;
  177. }
  178. });
  179. var sizeScale = paramInfo.scale || 1;
  180. ['width', 'height']
  181. .forEach(function(name) {
  182. if (paramInfo[name] != null) {
  183. var val = paramInfo[name];
  184. if (typeof val === 'string') {
  185. val = val.trim();
  186. param[name] = createSizeParser(
  187. name, tryConvertExpr(val), sizeScale
  188. );
  189. }
  190. else {
  191. param[name] = val;
  192. }
  193. }
  194. });
  195. if (!param.width) {
  196. param.width = defaultWidthFunc;
  197. }
  198. if (!param.height) {
  199. param.height = defaultHeightFunc;
  200. }
  201. if (paramInfo.useMipmap != null) {
  202. param.useMipmap = paramInfo.useMipmap;
  203. }
  204. return param;
  205. }
  206. function loadTextures(json, lib, opts, callback) {
  207. if (!json.textures) {
  208. callback({});
  209. return;
  210. }
  211. var textures = {};
  212. var loading = 0;
  213. var cbd = false;
  214. var textureRootPath = opts.textureRootPath;
  215. util.each(json.textures, function(textureInfo, name) {
  216. var texture;
  217. var path = textureInfo.path;
  218. var parameters = convertParameter(textureInfo.parameters);
  219. if (Array.isArray(path) && path.length === 6) {
  220. if (textureRootPath) {
  221. path = path.map(function(item) {
  222. return util.relative2absolute(item, textureRootPath);
  223. });
  224. }
  225. texture = new TextureCube(parameters);
  226. }
  227. else if(typeof path === 'string') {
  228. if (textureRootPath) {
  229. path = util.relative2absolute(path, textureRootPath);
  230. }
  231. texture = new Texture2D(parameters);
  232. }
  233. else {
  234. return;
  235. }
  236. texture.load(path);
  237. loading++;
  238. texture.once('success', function() {
  239. textures[name] = texture;
  240. loading--;
  241. if (loading === 0) {
  242. callback(textures);
  243. cbd = true;
  244. }
  245. });
  246. });
  247. if (loading === 0 && !cbd) {
  248. callback(textures);
  249. }
  250. }
  251. function createSizeSetHandler(name, exprFunc) {
  252. return function (renderer) {
  253. // PENDING viewport size or window size
  254. var dpr = renderer.getDevicePixelRatio();
  255. // PENDING If multiply dpr ?
  256. var width = renderer.getWidth();
  257. var height = renderer.getHeight();
  258. var result = exprFunc(width, height, dpr);
  259. this.setParameter(name, result);
  260. };
  261. }
  262. function createSizeParser(name, exprFunc, scale) {
  263. scale = scale || 1;
  264. return function (renderer) {
  265. var dpr = renderer.getDevicePixelRatio();
  266. var width = renderer.getWidth() * scale;
  267. var height = renderer.getHeight() * scale;
  268. return exprFunc(width, height, dpr);
  269. };
  270. }
  271. function tryConvertExpr(string) {
  272. // PENDING
  273. var exprRes = /^expr\((.*)\)$/.exec(string);
  274. if (exprRes) {
  275. try {
  276. var func = new Function('width', 'height', 'dpr', 'return ' + exprRes[1]);
  277. // Try run t
  278. func(1, 1);
  279. return func;
  280. }
  281. catch (e) {
  282. throw new Error('Invalid expression.');
  283. }
  284. }
  285. }
  286. export default createCompositor;