var schema = require('./schema'); var processSchema = schema.process; var shaders = module.exports.shaders = {}; // Keep track of registered shaders. var shaderNames = module.exports.shaderNames = []; // Keep track of the names of registered shaders. var THREE = require('../lib/three'); var utils = require('../utils'); // A-Frame properties to three.js uniform types. var propertyToThreeMapping = { array: 'v3', color: 'v3', int: 'i', number: 'f', map: 't', time: 'f', vec2: 'v2', vec3: 'v3', vec4: 'v4' }; /** * Shader class definition. * * Shaders extend the material component API so you can create your own library * of customized materials * */ var Shader = module.exports.Shader = function () {}; Shader.prototype = { /** * Contains the type schema and defaults for the data values. * Data is coerced into the types of the values of the defaults. */ schema: {}, vertexShader: 'void main() {' + 'gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);' + '}', fragmentShader: 'void main() {' + 'gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);' + '}', /** * Init handler. Similar to attachedCallback. * Called during shader initialization and is only run once. */ init: function (data) { this.attributes = this.initVariables(data, 'attribute'); this.uniforms = this.initVariables(data, 'uniform'); this.material = new (this.raw ? THREE.RawShaderMaterial : THREE.ShaderMaterial)({ // attributes: this.attributes, uniforms: this.uniforms, vertexShader: this.vertexShader, fragmentShader: this.fragmentShader }); return this.material; }, initVariables: function (data, type) { var key; var schema = this.schema; var variables = {}; var varType; for (key in schema) { if (schema[key].is !== type) { continue; } varType = propertyToThreeMapping[schema[key].type]; variables[key] = { type: varType, value: undefined // Let updateVariables handle setting these. }; } return variables; }, /** * Update handler. Similar to attributeChangedCallback. * Called whenever the associated material data changes. * * @param {object} data - New material data. */ update: function (data) { this.updateVariables(data, 'attribute'); this.updateVariables(data, 'uniform'); }, updateVariables: function (data, type) { var key; var materialKey; var schema = this.schema; var variables; variables = type === 'uniform' ? this.uniforms : this.attributes; for (key in data) { if (!schema[key] || schema[key].is !== type) { continue; } if (schema[key].type === 'map') { // If data unchanged, get out early. if (!variables[key] || variables[key].value === data[key]) { continue; } // Special handling is needed for textures. materialKey = '_texture_' + key; // We can't actually set the variable correctly until we've loaded the texture. this.setMapOnTextureLoad(variables, key, materialKey); // Kick off the texture update now that handler is added. utils.material.updateMapMaterialFromData(materialKey, key, this, data); continue; } variables[key].value = this.parseValue(schema[key].type, data[key]); variables[key].needsUpdate = true; } }, parseValue: function (type, value) { var color; switch (type) { case 'vec2': { return new THREE.Vector2(value.x, value.y); } case 'vec3': { return new THREE.Vector3(value.x, value.y, value.z); } case 'vec4': { return new THREE.Vector4(value.x, value.y, value.z, value.w); } case 'color': { color = new THREE.Color(value); return new THREE.Vector3(color.r, color.g, color.b); } case 'map': { return THREE.ImageUtils.loadTexture(value); } default: { return value; } } }, setMapOnTextureLoad: function (variables, key, materialKey) { var self = this; this.el.addEventListener('materialtextureloaded', function () { variables[key].value = self.material[materialKey]; variables[key].needsUpdate = true; }); } }; /** * Registers a shader to A-Frame. * * @param {string} name - shader name. * @param {object} definition - shader property and methods. * @returns {object} Shader. */ module.exports.registerShader = function (name, definition) { var NewShader; var proto = {}; // Format definition object to prototype object. Object.keys(definition).forEach(function (key) { proto[key] = { value: definition[key], writable: true }; }); if (shaders[name]) { throw new Error('The shader ' + name + ' has been already registered'); } NewShader = function () { Shader.call(this); }; NewShader.prototype = Object.create(Shader.prototype, proto); NewShader.prototype.name = name; NewShader.prototype.constructor = NewShader; shaders[name] = { Shader: NewShader, schema: processSchema(NewShader.prototype.schema) }; shaderNames.push(name); return NewShader; };