API Docs for: 0.5.5
Show:

File: kick/mesh/Mesh.js

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


    /**
     * @module kick.mesh
     */
    var ASSERT = Constants._ASSERT;

    /**
     * A Mesh object allows you to bind and render a MeshData object
     * @class Mesh
     * @namespace kick.mesh
     * @constructor
     * @param {Object} config
     * @extends kick.core.ProjectAsset
     */
    return function (config) {
        // extend ProjectAsset
        ProjectAsset(this, config, "kick.mesh.Mesh");
        if (ASSERT){
            if (config === EngineSingleton.engine){
                Util.fail("Mesh constructor changed - engine parameter is removed");
            }
        }
        var engine = EngineSingleton.engine,
            gl = engine.gl,
            glState = engine.glState,
            vertexArrayObjectExtension = glState.vertexArrayObjectExtension,
            vertexArrayObject = {},
            meshVertexAttBuffer,
            interleavedArrayFormat,
            interleavedArrayFormatArray = [],
            meshVertexIndexBuffer = 0,
            meshVertexBufferOffsetBytes = [],
            _name,
            _meshData,
            _dataURI = "memory://void",
            _aabb = null,
            thisObj = this,
            c = Constants,
            vertexAttrLength = 0,
            meshType,
            meshElements = [],
            deleteVertexArrayObjects = function () {
                var name;
                for (name in vertexArrayObject){
                    if (vertexArrayObject.hasOwnProperty(name)) {
                        vertexArrayObjectExtension.deleteVertexArrayOES(vertexArrayObject[name]);
                    }
                }
                vertexArrayObject = {};
            },
            deleteBuffersAndVertexArrayObjects = function () {
                deleteVertexArrayObjects();
                if (meshVertexIndexBuffer){
                    gl.deleteBuffer(meshVertexIndexBuffer);
                    meshVertexIndexBuffer = 0;
                }
                meshVertexBufferOffsetBytes.length = 0;
                if (meshVertexAttBuffer) {
                    gl.deleteBuffer(meshVertexAttBuffer);
                    meshVertexAttBuffer = null;
                }
                meshElements.length = 0;
            },
            createInterleavedArrayFormatArray = function () {
                var obj,
                    descName;
                interleavedArrayFormatArray.length = 0;
                for (descName in interleavedArrayFormat) {
                    if (interleavedArrayFormat.hasOwnProperty(descName)) {
                        obj = interleavedArrayFormat[descName];
                        if (!obj.name) {
                            obj.name = descName;
                        }
                        interleavedArrayFormatArray.push(obj);
                    }
                }
            },
            /**
             * Copy data to the vertex buffer object (VBO)
             * @method updateData
             * @private
             * @param {Boolean} updateVertexData
             * @param {Boolean} updateIndices
             * @param {Boolean} updateVertexStructure
             */
            updateData = (function(){
                var meshVertexAttBufferLength = -1,
                    meshVertexIndexBufferLength = -1,
                    indicesSize = 0;
                return function (updateVertexData, updateIndices, updateVertexStructure) {
                    var subMeshes = _meshData.subMeshes,
                        i,
                        indexLen,
                        interleavedData = _meshData.interleavedArray,
                        meshVertexIndexBufferConcat,
                        SIZE_OF_SHORT = 2;
                    if (vertexArrayObjectExtension) {
                        vertexArrayObjectExtension.bindVertexArrayOES(null);
                    }

                    if (updateVertexStructure){
                        deleteVertexArrayObjects();
                        interleavedArrayFormat = _meshData.interleavedArrayFormat;
                        createInterleavedArrayFormatArray();
                        vertexAttrLength = _meshData.vertexAttrLength;
                        meshType = _meshData.meshType;
                        meshVertexBufferOffsetBytes.length = 0;
                        meshElements.length = 0;
                        indicesSize = 0;
                        for (i = 0; i < subMeshes.length; i++) {
                            meshVertexBufferOffsetBytes.push(indicesSize * SIZE_OF_SHORT);
                            indexLen = subMeshes[i].length;
                            meshElements[i] = indexLen;
                            indicesSize += indexLen;
                        }
                    }

                    if (updateVertexData){
                        if (interleavedData.length !== meshVertexAttBufferLength || !meshVertexAttBuffer){
                            if (meshVertexAttBuffer){
                                gl.deleteBuffer(meshVertexAttBuffer);
                            }
                            meshVertexAttBuffer = gl.createBuffer();
                        }
                        gl.bindBuffer(c.GL_ARRAY_BUFFER, meshVertexAttBuffer);
                        gl.bufferData(c.GL_ARRAY_BUFFER, interleavedData, _meshData.usage);
                        meshVertexAttBufferLength = interleavedData.length;
                    }

                    if (updateIndices){
                        meshVertexIndexBufferConcat = new Uint16Array(indicesSize);
                        for (i = 0; i < subMeshes.length; i++) {
                            meshVertexIndexBufferConcat.set(subMeshes[i], meshVertexBufferOffsetBytes[i] / 2);
                        }
                        if (meshVertexIndexBufferConcat.length !== meshVertexIndexBufferLength || !meshVertexIndexBuffer){
                            if (meshVertexIndexBuffer){
                                gl.deleteBuffer(meshVertexIndexBuffer);
                            }
                            meshVertexIndexBuffer = gl.createBuffer();
                        }
                        gl.bindBuffer(c.GL_ELEMENT_ARRAY_BUFFER, meshVertexIndexBuffer);
                        gl.bufferData(c.GL_ELEMENT_ARRAY_BUFFER, meshVertexIndexBufferConcat, _meshData.usage);
                        meshVertexIndexBufferLength = meshVertexIndexBufferConcat.length;
                    }
                    glState.meshBuffer = null;
                }}()),
            contextLost = function () {
                meshVertexIndexBuffer = 0;
                meshVertexAttBuffer = null;
                gl = null;
            },
            contextRestored = function (newGl) {
                gl = newGl;
                vertexArrayObject = {};
                vertexArrayObjectExtension = glState.vertexArrayObjectExtension,
                updateData(true, true, true);
            },
            bindBuffers = function(shader){
                var i,
                    interleavedDataDescriptor,
                    name,
                    shaderAttribute,
                    activeAttribute,
                    attributeIndex;
                gl.bindBuffer(Constants.GL_ARRAY_BUFFER, meshVertexAttBuffer);
                for (i = 0; i < interleavedArrayFormatArray.length; i++) {
                    interleavedDataDescriptor = interleavedArrayFormatArray[i];
                    name = interleavedDataDescriptor.name;
                    shaderAttribute = shader.lookupAttribute[name];
                    if (typeof (shaderAttribute) !== 'undefined') {
                        gl.enableVertexAttribArray(shaderAttribute);
                        gl.vertexAttribPointer(shaderAttribute, interleavedDataDescriptor.size,
                            interleavedDataDescriptor.type, false, vertexAttrLength, interleavedDataDescriptor.pointer);
                    }
                }
                gl.bindBuffer(Constants.GL_ELEMENT_ARRAY_BUFFER, meshVertexIndexBuffer);
                if (ASSERT) {
                    for (i = shader.activeAttributes.length - 1; i >= 0; i--) {
                        activeAttribute = shader.activeAttributes[i];
                        if (interleavedArrayFormat && !(interleavedArrayFormat[activeAttribute.name])) {
                            Util.fail("Shader wants " + activeAttribute.name + " but mesh does not have it.");
                            attributeIndex = shader.lookupAttribute[activeAttribute.name];
                            gl.disableVertexAttribArray(attributeIndex);
                            switch (activeAttribute.type) {
                            case c.GL_FLOAT:
                                gl.vertexAttrib1f(attributeIndex, 0.0);
                                break;
                            case c.GL_FLOAT_VEC2:
                                gl.vertexAttrib2f(attributeIndex, 0.0, 0.0);
                                break;
                            case c.GL_FLOAT_VEC3:
                                gl.vertexAttrib3f(attributeIndex, 0.0, 0.0, 0.0);
                                break;
                            case c.GL_FLOAT_VEC4:
                                gl.vertexAttrib4f(attributeIndex, 0.0, 0.0, 0.0, 0.0);
                                break;
                            default:
                                Util.fail("Shader wants " + activeAttribute.name + " no default value for type.");
                                break;
                            }
                        }
                    }
                }
            };

        engine.addEventListener('contextLost', contextLost);
        engine.addEventListener('contextRestored', contextRestored);

        Object.defineProperties(this, {
            /**
             * Axis aligned bounding box.
             * Readonly.
             * @property aabb
             * @type kick.math.Aabb
             */
            aabb: {
                get: function () {
                    if (!_aabb && _meshData) {
                        _aabb = _meshData.aabb;
                    }
                    return _aabb;
                }
            },
            /**
             * @property name
             * @type String
             */
            name: {
                get: function () {
                    return _name;
                },
                set: function (newValue) {
                    _name = newValue || "Mesh";
                }
            },
            /**
             * Setting this property to something will update the data in WebGL. Note that
             * changing a MeshData object will not itself update anything.
             * @property meshData
             * @type kick.mesh.MeshData
             */
            meshData: {
                get: function () {
                    return _meshData;
                },
                set: function (newValue) {
                    if (ASSERT) {
                        if (newValue && !(newValue instanceof MeshData)) {
                            Util.fail("Mesh.meshData must be instanceof kick.mesh.MeshData");
                        }
                    }
                    _meshData = newValue;
                    _aabb = null;
                    updateData(true, true, true);
                }
            },
            /**
             * The resource url of the mesh. Setting this property will try to load the meshData.
             * @property dataURI
             * @type String
             */
            dataURI: {
                get: function () {
                    return _dataURI;
                },
                set: function (newValue) {
                    thisObj.setDataURI(newValue, true);
                }
            }
        });

        /**
         * Gives more control over what parts of mesh data is uploaded to the GPU.
         * @example
         *      // Usual use case: Only update vertex data when updating an existing mesh
         *      mesh.setMeshData(meshData, true);
         * @method updateMeshData
         * @param {kick.mesh.MeshData} meshData
         * @param {Boolean} updateVertexData
         * @param {Boolean} updateIndices
         * @param {Boolean} updateVertexStructure
         * @param {Boolean} updateBoundingbox
         */
        this.updateMeshData = function(meshData, updateVertexData, updateIndices, updateVertexStructure, updateBoundingbox){
            if (ASSERT) {
                if (!(meshData instanceof MeshData)) {
                    Util.fail("meshData must be instanceof kick.mesh.MeshData");
                }
            }
            var updateAll = !_meshData; // if no mesh data update all
            _meshData = meshData;
            if (updateBoundingbox) {
                _aabb = null;
            }
            updateData(updateVertexData || updateAll, updateIndices || updateAll, updateVertexStructure || updateAll);
        };

        /**
         * @method setDataURI
         * @param {String} newValue
         * @param {Boolean} automaticGetMeshData optional. if true the mesh data is attempted to be loaded by resourceLoader.getMeshData
         * @param resourceTracker {ResourceTracker} Optional
         */
        this.setDataURI = function (newValue, automaticGetMeshData, resourceTracker) {
            if (newValue !== _dataURI) {
                _dataURI = newValue;
                if (automaticGetMeshData) {
                    engine.resourceLoader.getMeshData(newValue, thisObj, resourceTracker);
                }
            }
        };

        Util.applyConfig(this, config, ["uid"]);


        /**
         * This function verifies that the mesh has the vertex attributes (normals, uvs, tangents) that the shader uses.
         * @method verify
         * @param {kick.material.Shader} shader
         * @return {Array_String} list of missing vertex attributes in mesh or null if no missing attributes
         */
        this.verify = function (shader) {
            var missingVertexAttributes = [],
                found,
                attName;
            for (attName in shader.lookupAttribute) {
                if (typeof (attName) === "string") {
                    found = interleavedArrayFormat[attName];
                    if (!found) {
                        missingVertexAttributes.push(attName);
                    }
                }
            }
            if (missingVertexAttributes.length === 0) {
                return null;
            }
            return null;
        };

        /**
         * Bind the vertex attributes of the mesh to the shader
         * @method bind
         * @param {kick.material.Shader} shader
         */
        this.bind = vertexArrayObjectExtension ?
            function bindUsingVAO(shader) {
                shader.bind();

                if (glState.meshBuffer !== meshVertexAttBuffer || glState.meshShader !== shader) {
                    glState.meshBuffer = meshVertexAttBuffer;
                    glState.meshShader = shader;
                    var vao = vertexArrayObject[shader.uid];
                    if (!vao){
                        vao = vertexArrayObjectExtension.createVertexArrayOES();
                        vertexArrayObjectExtension.bindVertexArrayOES(vao);
                        vertexArrayObject[shader.uid] = vao;
                        bindBuffers(shader);
                    } else {
                        vertexArrayObjectExtension.bindVertexArrayOES(vao);
                    }
                    gl.bindBuffer(Constants.GL_ELEMENT_ARRAY_BUFFER, meshVertexIndexBuffer);
                }
            }:
            function bindDefault(shader) {
                shader.bind();

                if (glState.meshBuffer !== meshVertexAttBuffer || glState.meshShader !== shader) {
                    glState.meshBuffer = meshVertexAttBuffer;
                    glState.meshShader = shader;
                    bindBuffers(shader);
                }
            };

        /**
         * Renders the current mesh.
         * Assumes that the Mesh.bind(shader) has been called prior to this, to setup the mesh with the shader.
         * @method render
         * @param {Number} submeshIndex
         */
        this.render = function (submeshIndex) {
            gl.drawElements(meshType, meshElements[submeshIndex], c.GL_UNSIGNED_SHORT, meshVertexBufferOffsetBytes[submeshIndex]);
        };

        /**
         * Destroys the mesh data and deletes the associated resources
         * After this the mesh cannot be bound
         * @method destroy
         */
        this.destroy = function () {
            if (meshVertexAttBuffer !== null) {
                deleteBuffersAndVertexArrayObjects();
                engine.removeEventListener('contextLost', contextLost);
                engine.removeEventListener('contextRestored', contextRestored);
                engine.project.removeResourceDescriptor(thisObj.uid);
            }

        };

        /**
         * @method toJSON
         * @return {Object} data object
         */
        this.toJSON = function () {
            if (ASSERT) {
                if (!_dataURI) {
                    Util.fail("_dataURI not defined");
                }
            }
            return {
                uid: thisObj.uid,
                name: _name,
                dataURI: _dataURI
            };
        };

        this.init = function(config, resourceTracker){
            Util.applyConfig(this, config, ["uid", "dataURI"]);
            if (config.dataURI){
                thisObj.setDataURI(config.dataURI, true, resourceTracker);
            }
        };
    };
});