Core/Attribute.js

/** 
 * A class representing an attribute. 
 * 
 * @property {String} type The type name of this object (Lore.Attribute).
 * @property {*} data The data represented by the attribute in a 1D array. Usually a Float32Array.
 * @property {Number} [attributeLength=3] The length of the attribute. '3' for Vector3f.
 * @property {String} name The name of this attribut. Must be the name used by the shader.
 * @property {Number} size The length of the attribute values (defined as data.length / attributeLength).
 * @property {WebGLBuffer} buffer The bound WebGLBuffer.
 * @property {GLint} attributeLocation The attribute location for this attribute.
 * @property {GLenum} bufferType The buffer target. As of WebGL 1: gl.ARRAY_BUFFER or gl.ELEMENT_ARRAY_BUFFER.
 * @property {GLenum} drawMode The draw mode. As of WebGL 1: gl.STATIC_DRAW, gl.DYNAMIC_DRAW or gl.STREAM_DRAW.
 * @property {Boolean} stale A boolean indicating whether or not this attribute has changed and needs to be updated.
 */
Lore.Attribute = class Attribute {
    /**
     * Creates an instance of Attribute.
     * @param {*} data The data represented by the attribute in a 1D array. Usually a Float32Array.
     * @param {Number} attributeLength The length of the attribute (3 for RGB, XYZ, ...).
     * @param {String} name The name of the attribute.
     */
    constructor(data, attributeLength, name) {
        this.type = 'Lore.Attribute';
        this.data = data;
        this.attributeLength = attributeLength || 3;
        this.name = name;
        this.size = this.data.length / this.attributeLength;
        this.buffer = null;
        this.attributeLocation;
        this.bufferType = null;
        this.drawMode = null;
        this.stale = false;
    }

    /**
     * Set the attribute value from a vector at a given index. The vector should have the same number of components as is the length of this attribute.
     * 
     * @param {Number} index The index at which to replace / set the value (is calculated as index * attributeLength).
     * @param {Lore.Vector3f} v A vector.
     */
    setFromVector(index, v) {
        this.data.set(v.components, index * this.attributeLength, v.components.length);
    }

    /**
     * Set the attribute values from vectors in an array.
     * 
     * @param {Lore.Vector3f[]} arr An array containing vectors. The number of components of the vectors must have the same length as the attribute length specified.
     */
    setFromVectorArray(arr) {
        if (this.attributeLength !== arr[0].components.length)
            throw 'The attribute has a length of ' + this.attributeLength + '. But the vectors have ' + arr[0].components.length + ' components.';

        for (let i = 0; i < arr.length; i++) {
            this.data.set(arr[i].components, i * this.attributeLength, arr[i].components.length);
        }
    }

    /**
     * Gets the x value at a given index.
     * 
     * @param {Number} index The index.
     * @returns {Number} The x value at a given index.
     */
    getX(index) {
        return this.data[index * this.attributeLength];
    }

    /**
     * Set the x value at a given index.
     * 
     * @param {Number} index The index.
     * @param {Number} value A number.
     */
    setX(index, value) {
        this.data[index * this.attributeLength];
    }

    /**
     * Gets the y value at a given index.
     * 
     * @param {Number} index The index.
     * @returns {Number} The y value at a given index.
     */
    getY(index) {
        return this.data[index * this.attributeLength + 1];
    }

    /**
     * Set the y value at a given index.
     * 
     * @param {Number} index The index.
     * @param {Number} value A number.
     */
    setY(index, value) {
        this.data[index * this.attributeLength + 1];
    }

    /**
     * Gets the z value at a given index.
     * 
     * @param {Number} index The index.
     * @returns {Number} The z value at a given index.
     */
    getZ(index) {
        return this.data[index * this.attributeLength + 2];
    }

    /**
     * Set the z value at a given index.
     * 
     * @param {Number} index The index.
     * @param {Number} value A number.
     */
    setZ(index, value) {
        this.data[index * this.attributeLength + 2];
    }

    /**
     * Gets the w value at a given index.
     * 
     * @param {Number} index The index.
     * @returns {Number} The w value at a given index.
     */
    getW(index) {
        return this.data[index * this.attributeLength + 3];
    }

    /**
     * Set the w value at a given index.
     * 
     * @param {Number} index The index.
     * @param {Number} value A number.
     */
    setW(index, value) {
        this.data[index * this.attributeLength + 3];
    }

    /**
     * Returns the gl type. Currently only float is supported.
     * 
     * @param {WebGLRenderingContext} gl The WebGL rendering context.
     * @returns {Number} The type.
     */
    getGlType(gl) {
        // Just floats for now
        // TODO: Add additional types.
        return gl.FLOAT;
    }

    /**
     * Update the attribute in order for changes to take effect.
     * 
     * @param {WebGLRenderingContext} gl The WebGL rendering context.
     */
    update(gl) {
        gl.bindBuffer(this.bufferType, this.buffer);
        gl.bufferData(this.bufferType, this.data, this.drawMode);

        this.stale = false;
    }

    /**
     * Create a new WebGL buffer.
     * 
     * @param {WebGLRenderingContext} gl The WebGL rendering context.
     * @param {WebGLProgram} program A WebGL program.
     * @param {GLenum} bufferType The buffer type.
     * @param {GLenum} drawMode The draw mode.
     */
    createBuffer(gl, program, bufferType, drawMode) {
        this.buffer = gl.createBuffer();
        this.bufferType = bufferType || gl.ARRAY_BUFFER;
        this.drawMode = drawMode || gl.STATIC_DRAW;

        gl.bindBuffer(this.bufferType, this.buffer);
        gl.bufferData(this.bufferType, this.data, this.drawMode);

        this.buffer.itemSize = this.attributeLength;
        this.buffer.numItems = this.size;

        this.attributeLocation = gl.getAttribLocation(program, this.name);
        gl.bindBuffer(this.bufferType, null);
    }

    /**
     * Bind the buffer of this attribute. The attribute must exist in the current shader.
     * 
     * @param {WebGLRenderingContext} gl The WebGL rendering context.
     */
    bind(gl) {
        gl.bindBuffer(this.bufferType, this.buffer);

        // Only enable attribute if it actually exists in the Shader
        if (this.attributeLocation >= 0) {
            gl.vertexAttribPointer(this.attributeLocation, this.attributeLength, this.getGlType(gl), gl.FALSE, 0, 0);
            gl.enableVertexAttribArray(this.attributeLocation);
        }
    }
}