API Docs for: 0.5.5
Show:

File: kick/material/Material.js

define(["kick/core/ProjectAsset", "kick/core/Util", "kick/core/Constants", "./Shader", "./MaterialUniform", "kick/core/EngineSingleton", "kick/core/Observable"],
    function (ProjectAsset, Util, Constants, Shader, MaterialUniform, EngineSingleton, Observable) {
        "use strict";

        /**
         * @module kick.material
         */
        var ASSERT = Constants._ASSERT,
            fail = Util.fail;

        /**
         * Material configuration. Stores a material configuration and a shader.
         * @example
         *      var material = new KICK.material.Material({
         *      shader: project.load(project.ENGINE_SHADER_DIFFUSE),
         *          uniformData:{
         *              mainColor:[1.0,0.0,0.9,0.5],
         *              mainTexture: project.load(project.ENGINE_TEXTURE_WHITE)
         *          }
         *      });
         * @class Material
         * @namespace kick.material
         * @constructor
         * @param {Object} config
         * @extends kick.core.ProjectAsset
         */
        return function (config) {
            // extend ProjectAsset
            ProjectAsset(this, config, "kick.material.Material");
            if (ASSERT){
                if (config === EngineSingleton.engine){
                    Util.fail("Material constructor changed - engine parameter is removed");
                }
            }
            var engine = EngineSingleton.engine,
                _name = "Material",
                _shader = null,
                _uniforms = [],
                thisObj = this,
                gl = engine.gl,
                _renderOrder = 0,
                contextRestored= function (newGL) {
                    gl = newGL;
                    // force shader update (will re-initialize uniforms)
                    if (_shader) {
                        _shader.contextRestored(newGL);
                        var s = _shader;
                        _shader = null;
                        thisObj.shader = s;
                    }
                },
                notifyShaderChange = function(){
                    thisObj.fireEvent('shaderChanged', _shader);
                },
                /**
                 * Called when a shader is set or changed.
                 * Add location and type information to each uniform.
                 * Besides it checks that the uniforms exists in the shader
                 * (if not the default uniforms are added).
                 * @method decorateUniforms
                 * @private
                 */
                decorateUniforms = function () {
                    _renderOrder = _shader.renderOrder;
                    notifyShaderChange();
                    var i, uniform,
                        foundUniformNames = {},
                        name,
                        element;
                    for (i = _uniforms.length - 1; i >= 0; i--) {
                        uniform = _shader.lookupUniform[_uniforms[i].name];
                        if (uniform) {
                            _uniforms[i].type = uniform.type;
                            _uniforms[i].location = uniform.location;
                            foundUniformNames[_uniforms[i].name] = true;
                        } else {
                            _uniforms.splice(i, 1); // remove element from array
                        }
                    }
                    // add items not found
                    for (i = 0; i < _shader.materialUniforms.length; i++) {
                        uniform = _shader.materialUniforms[i];
                        name = uniform.name;
                        if (!foundUniformNames.hasOwnProperty(name)) {
                            // add default name
                            element = thisObj.setUniform(name, _shader.defaultUniforms[name]);
                            element.location = uniform.location;
                            element.type = uniform.type;
                        }
                    }
                };

            Observable.call(this, [
            /**
             * Fired when shader is changed (set to a new instance)
             * @event shaderChanged
             * @param {kick.material.Shader} shaderInstance
             */
                "shaderChanged"
            ]
            );

            Object.defineProperties(this, {
                /**
                 * @property engine
                 * @type kick.core.Engine
                 */
                engine: {
                    value: engine
                },
                /**
                 * @property name
                 * @type String
                 */
                name: {
                    get: function () { return _name; },
                    set: function (newValue) { _name = newValue; }
                },
                /**
                 * @property shader
                 * @type kick.material.Shader
                 */
                shader: {
                    get: function () {
                        return _shader;
                    },
                    set: function (newValue) {
                        if (!newValue instanceof Shader) {
                            Util.fail("kick.material.Shader expected");
                        }
                        if (_shader !== newValue) {
                            if (_shader) {
                                _shader.removeEventListener("shaderUpdated", decorateUniforms);
                            }
                            _shader = newValue;
                            if (_shader) {
                                _renderOrder = _shader.renderOrder;
                                decorateUniforms();
                                _shader.addEventListener("shaderUpdated", decorateUniforms);
                            }
                        }
                    }
                },
                /**
                 * Instead call setUniform
                 * @deprecated
                 * @property uniforms
                 * @type Object
                 */
                uniforms: {
                    value: null
                },
                /**
                 * @property renderOrder
                 * @type Number
                 */
                renderOrder: {
                    get: function () {
                        return _renderOrder;
                    }
                }
            });

            /**
             * Listener is notified whenever shader is changed
             * @method addShaderChangeListeners
             * @param {Function} listenerFn
             * @deprecated
             */
            this.addShaderChangeListener = function (listenerFn) {
                if (ASSERT) {
                    if (typeof listenerFn !== "function") {
                        Util.warn("Material.addShaderChangeListener: listenerFn not function");
                    }
                }

                Util.fail("Use addEventListener('shaderChanged', listenerFn) instead");

                thisObj.addEventListener("shaderChanged", listenerFn);
            };

            /**
             * @method removeShaderChangeListener
             * @param {Function} listenerFn
             * @deprecated
             */
            this.removeShaderChangeListener = function (listenerFn) {
                if (ASSERT) {
                    if (typeof listenerFn !== "function") {
                        Util.warn("Material.removeShaderChangeListener: listenerFn not function");
                    }
                }
                Util.fail("Use removeEventListener('shaderChanged', listenerFn) instead");

                thisObj.removeEventListener("shaderChanged", listenerFn);
            };

            /**
             * Bind material uniforms
             * @method bind
             * @param {Number} currentTexture
             * @protected
             * @return {Number}
             */
            this.bind = function (currentTexture) {
                var i,
                    value,
                    location;
                for (i = 0; i < _uniforms.length; i++) {
                    value = _uniforms[i].value;
                    location = _uniforms[i].location;
                    switch (_uniforms[i].type) {
                    case Constants.GL_FLOAT:
                        gl.uniform1fv(location, value);
                        break;
                    case Constants.GL_FLOAT_MAT2:
                        gl.uniformMatrix2fv(location, false, value);
                        break;
                    case Constants.GL_FLOAT_MAT3:
                        gl.uniformMatrix3fv(location, false, value);
                        break;
                    case Constants.GL_FLOAT_MAT4:
                        gl.uniformMatrix4fv(location, false, value);
                        break;
                    case Constants.GL_FLOAT_VEC2:
                        gl.uniform2fv(location, value);
                        break;
                    case Constants.GL_FLOAT_VEC3:
                        gl.uniform3fv(location, value);
                        break;
                    case Constants.GL_FLOAT_VEC4:
                        gl.uniform4fv(location, value);
                        break;
                    case Constants.GL_INT:
                        gl.uniform1iv(location, value);
                        break;
                    case Constants.GL_INT_VEC2:
                        gl.uniform2iv(location, value);
                        break;
                    case Constants.GL_INT_VEC3:
                        gl.uniform3iv(location, value);
                        break;
                    case Constants.GL_INT_VEC4:
                        gl.uniform4iv(location, value);
                        break;
                    case Constants.GL_SAMPLER_CUBE:
                    case Constants.GL_SAMPLER_2D:
                        value.bind(currentTexture);
                        gl.uniform1i(location, currentTexture);
                        currentTexture++;
                        break;
                    }
                }
                return currentTexture;
            };

            /**
             * Bind material uniforms. Returns undefined or null if value is undefined or null (or uniform not found)
             * @method setUniform
             * @param {String} name
             * @param {Float32Array|Int32Array|kick.texture.Texture} value
             * @return {kick.material.MaterialUniform}
             */
            this.setUniform = function (name, value) {
                if (Constants._DEBUG){
                    console.log("Material.setUniform",name,value);
                }
                if (value === undefined || value === null) {
                    return null;
                }

                if (ASSERT && typeof value === "number"){
                    fail("setUniform(name,value) expect a value as a Float32Array|Int32Array|kick.texture.Texture - but was a number.");
                }
                var foundElement,
                    i;
                for (i = 0; i < _uniforms.length && !foundElement; i++) {
                    if (_uniforms[i].name === name) {
                        foundElement = _uniforms[i];
                        foundElement.value = value;
                    }
                }

                if (foundElement && _shader) {
                    _shader.markUniformUpdated();
                } else if (!foundElement) {
                    foundElement = new MaterialUniform({
                        name: name,
                        value: value
                    });
                    _uniforms.push(foundElement);
                    if (Constants._DEBUG){
                        console.log("Material.setUniform not found. Using default ",foundElement);
                    }
                }
                if (ASSERT) {
                    if (_shader) {
                        if (value === undefined) {
                            Util.fail("Type of value is undefined");
                        }
                    }
                }
                return foundElement;
            };

            /**
             * @method getUniform
             * @param name
             * @return {Float32Array|Int32Array|kick.texture.Texture}
             */
            this.getUniform = function (name) {
                var i;
                for (i = 0; i < _uniforms.length; i++) {
                    if (_uniforms[i].name === name) {
                        return _uniforms[i].value;
                    }
                }
                return null;
            };

            /**
             * @method destroy
             */
            this.destroy = function () {
                thisObj.shader = null;
                engine.project.removeResourceDescriptor(thisObj.uid);
                engine.removeEventListener('contextRestored', contextRestored);
            };

            /**
             * Returns a JSON representation of the material<br>
             * @method toJSON
             * @return {string}
             */
            this.toJSON = function () {
                var i,
                    serializedUniforms = {};
                for (i = 0; i < _uniforms.length; i++) {
                    serializedUniforms[_uniforms[i].name] = _uniforms[i].toJSON().value;
                }
                return {
                    uid: thisObj.uid,
                    name: _name,
                    shader: Util.getJSONReference(_shader),
                    uniformData: serializedUniforms // uniformData only used during serialization
                };
            };

            this.init = function(config) {
                var conf = config || {},
                    uniformData = conf.uniformData,
                    name,
                    value,
                    configCopy = {
                        uid: conf .uid || 0,
                        name: conf.name,
                        shader: conf.shader
                    };
                engine.addEventListener('contextRestored', contextRestored);
                Util.applyConfig(thisObj, configCopy, ["uid"]);
                if (!_shader || !_shader.isValid()) {
                    if (conf.shader) {
                        Util.warn("Problem using shader in material. ", conf.shader);
                    }
                    thisObj.shader = engine.project.load(engine.project.ENGINE_SHADER___ERROR);
                }
                if (uniformData) {
                    if (Constants._DEBUG){
                        console.log("Material.init",uniformData);
                    }
                    for (name in uniformData) {
                        if (uniformData.hasOwnProperty(name)) {
                            if (_shader.lookupUniform[name]) { // if found in shader
                                value = uniformData[name];
                                value = Shader.convertUniformValue(_shader.lookupUniform[name].type, value, engine);
                                thisObj.setUniform(name, value);
                            } else {
                                Util.warn("Cannot find uniform " + name + " in shader. ");
                            }
                        }
                    }
                }
                decorateUniforms();
            };
            this.init(config);
        };

    });