import util from './core/util'; import Compositor from './compositor/Compositor'; import CompoSceneNode from './compositor/SceneNode'; import CompoTextureNode from './compositor/TextureNode'; import CompoFilterNode from './compositor/FilterNode'; import Shader from './Shader'; import Texture from './Texture'; import Texture2D from './Texture2D'; import TextureCube from './TextureCube'; import registerBuiltinCompositor from './shader/registerBuiltinCompositor'; registerBuiltinCompositor(Shader); var shaderSourceReg = /^#source\((.*?)\)/; /** * @name clay.createCompositor * @function * @param {Object} json * @param {Object} [opts] * @return {clay.compositor.Compositor} */ function createCompositor(json, opts) { var compositor = new Compositor(); opts = opts || {}; var lib = { textures: {}, parameters: {} }; var afterLoad = function(shaderLib, textureLib) { for (var i = 0; i < json.nodes.length; i++) { var nodeInfo = json.nodes[i]; var node = createNode(nodeInfo, lib, opts); if (node) { compositor.addNode(node); } } }; for (var name in json.parameters) { var paramInfo = json.parameters[name]; lib.parameters[name] = convertParameter(paramInfo); } // TODO load texture asynchronous loadTextures(json, lib, opts, function(textureLib) { lib.textures = textureLib; afterLoad(); }); return compositor; } function createNode(nodeInfo, lib, opts) { var type = nodeInfo.type || 'filter'; var shaderSource; var inputs; var outputs; if (type === 'filter') { var shaderExp = nodeInfo.shader.trim(); var res = shaderSourceReg.exec(shaderExp); if (res) { shaderSource = Shader.source(res[1].trim()); } else if (shaderExp.charAt(0) === '#') { shaderSource = lib.shaders[shaderExp.substr(1)]; } if (!shaderSource) { shaderSource = shaderExp; } if (!shaderSource) { return; } } if (nodeInfo.inputs) { inputs = {}; for (var name in nodeInfo.inputs) { if (typeof nodeInfo.inputs[name] === 'string') { inputs[name] = nodeInfo.inputs[name]; } else { inputs[name] = { node: nodeInfo.inputs[name].node, pin: nodeInfo.inputs[name].pin }; } } } if (nodeInfo.outputs) { outputs = {}; for (var name in nodeInfo.outputs) { var outputInfo = nodeInfo.outputs[name]; outputs[name] = {}; if (outputInfo.attachment != null) { outputs[name].attachment = outputInfo.attachment; } if (outputInfo.keepLastFrame != null) { outputs[name].keepLastFrame = outputInfo.keepLastFrame; } if (outputInfo.outputLastFrame != null) { outputs[name].outputLastFrame = outputInfo.outputLastFrame; } if (outputInfo.parameters) { outputs[name].parameters = convertParameter(outputInfo.parameters); } } } var node; if (type === 'scene') { node = new CompoSceneNode({ name: nodeInfo.name, scene: opts.scene, camera: opts.camera, outputs: outputs }); } else if (type === 'texture') { node = new CompoTextureNode({ name: nodeInfo.name, outputs: outputs }); } // Default is filter else { node = new CompoFilterNode({ name: nodeInfo.name, shader: shaderSource, inputs: inputs, outputs: outputs }); } if (node) { if (nodeInfo.parameters) { for (var name in nodeInfo.parameters) { var val = nodeInfo.parameters[name]; if (typeof val === 'string') { val = val.trim(); if (val.charAt(0) === '#') { val = lib.textures[val.substr(1)]; } else { node.on( 'beforerender', createSizeSetHandler( name, tryConvertExpr(val) ) ); } } else if (typeof val === 'function') { node.on('beforerender', val); } node.setParameter(name, val); } } if (nodeInfo.defines && node.pass) { for (var name in nodeInfo.defines) { var val = nodeInfo.defines[name]; node.pass.material.define('fragment', name, val); } } } return node; } function defaultWidthFunc(width, height) { return width; } function defaultHeightFunc(width, height) { return height; } function convertParameter(paramInfo) { var param = {}; if (!paramInfo) { return param; } ['type', 'minFilter', 'magFilter', 'wrapS', 'wrapT', 'flipY', 'useMipmap'] .forEach(function(name) { var val = paramInfo[name]; if (val != null) { // Convert string to enum if (typeof val === 'string') { val = Texture[val]; } param[name] = val; } }); var sizeScale = paramInfo.scale || 1; ['width', 'height'] .forEach(function(name) { if (paramInfo[name] != null) { var val = paramInfo[name]; if (typeof val === 'string') { val = val.trim(); param[name] = createSizeParser( name, tryConvertExpr(val), sizeScale ); } else { param[name] = val; } } }); if (!param.width) { param.width = defaultWidthFunc; } if (!param.height) { param.height = defaultHeightFunc; } if (paramInfo.useMipmap != null) { param.useMipmap = paramInfo.useMipmap; } return param; } function loadTextures(json, lib, opts, callback) { if (!json.textures) { callback({}); return; } var textures = {}; var loading = 0; var cbd = false; var textureRootPath = opts.textureRootPath; util.each(json.textures, function(textureInfo, name) { var texture; var path = textureInfo.path; var parameters = convertParameter(textureInfo.parameters); if (Array.isArray(path) && path.length === 6) { if (textureRootPath) { path = path.map(function(item) { return util.relative2absolute(item, textureRootPath); }); } texture = new TextureCube(parameters); } else if(typeof path === 'string') { if (textureRootPath) { path = util.relative2absolute(path, textureRootPath); } texture = new Texture2D(parameters); } else { return; } texture.load(path); loading++; texture.once('success', function() { textures[name] = texture; loading--; if (loading === 0) { callback(textures); cbd = true; } }); }); if (loading === 0 && !cbd) { callback(textures); } } function createSizeSetHandler(name, exprFunc) { return function (renderer) { // PENDING viewport size or window size var dpr = renderer.getDevicePixelRatio(); // PENDING If multiply dpr ? var width = renderer.getWidth(); var height = renderer.getHeight(); var result = exprFunc(width, height, dpr); this.setParameter(name, result); }; } function createSizeParser(name, exprFunc, scale) { scale = scale || 1; return function (renderer) { var dpr = renderer.getDevicePixelRatio(); var width = renderer.getWidth() * scale; var height = renderer.getHeight() * scale; return exprFunc(width, height, dpr); }; } function tryConvertExpr(string) { // PENDING var exprRes = /^expr\((.*)\)$/.exec(string); if (exprRes) { try { var func = new Function('width', 'height', 'dpr', 'return ' + exprRes[1]); // Try run t func(1, 1); return func; } catch (e) { throw new Error('Invalid expression.'); } } } export default createCompositor;