API Docs for: 0.2.0
Show:

File: src\scenes\Scene.js

/**
 * The Scene class contains the objects that are rendered with the WebGLRenderer
 *
 * @class Scene
 * @constructor
 * @param {Object} [parameters] Parameters is an object that contains the Scenes properties
 * @param {AmbientLight} [parameters.ambientLight=new LE.AmbientLight(new LE.Colour(255, 255, 255, 255))] Default value is an ambient light with maximum brightness.
 */
LE.Scene = function(parameters) {
    // Stop error if no parameters given
    if(parameters == null) {
        parameters = { };
    }
    // Variable declarations
    /**
     * Stores a reference to the WebGL canvas context
     *
     * @private
     * @property gl
     * @type Object
     */
    this.gl,
    /**
     * Stores the lights
     *
     * @private
     * @property lights
     * @type Array
     */
    this.lights = [],
    /**
     * Stores the objects that cast shadows
     *
     * @private
     * @property shadowObjects
     * @type Array
     */
    this.shadowObjects = [],
    /**
     * Stores the objects that do not cast shadows
     *
     * @private
     * @property objects
     * @type Array
     */
    this.objects = [],
    /**
     * Stores the ambient light
     *
     * @property ambientLight
     * @type AmbientLight
     * @default new LE.AmbientLight(new LE.Colour(255, 255, 255, 255))
     */
    this.ambientLight = parameters.ambientLight || new LE.AmbientLight(new LE.Colour(255, 255, 255, 255)),
    /**
     * Stores all the texture data to be rendered by WebGL
     *
     * @private
     * @property textures
     * @type Array
     */
    this.textures = [],

    // WebGL Buffers
    /**
     * Stores all the WebGL vertex buffers
     *
     * @private
     * @property objectBuffers
     * @type Array
     */
    this.objectBuffers = [],
    /**
     * Stores all the WebGL colour vertex buffers
     *
     * @private
     * @property objectColourBuffers 
     * @type Array
     */
    this.objectColourBuffers = [],
     /**
     * Stores all the WebGL object indices
     *
     * @private
     * @property objectIndexBuffers 
     * @type Array
     */
    this.objectIndexBuffers = [],
    /**
     * Stores all the WebGL texture vertex buffers
     *
     * @private
     * @property objectTextureBuffers 
     * @type Array
     */
    this.objectTextureBuffers = [],
    /**
     * Stores all the WebGL light vertex buffers
     *
     * @private
     * @property lightBuffers
     * @type Array
     */
    this.lightBuffers = [],
    /**
     * Stores all the WebGL shadow vertex buffers
     *
     * @private
     * @property shadowBuffers 
     * @type Array
     */
    this.shadowBuffers = [],
    /**
     * Stores all the WebGL shadow colour vertex buffers
     *
     * @private
     * @property shadowColourBuffers 
     * @type Array
     */
    this.shadowColourBuffers = [];
};

/**
 * Initialise the scene with a WebGL context
 *
 * @method init
 * @param {Object} gl
 * @private
 */
LE.Scene.prototype.init = function(gl) {
    this.gl = gl
    this.initBuffers();
    this.initTextures();
};

/**
 * Initialise all buffers, this includes object, texture and light buffers
 *
 * @method initBuffers
 * @param {Object} gl
 * @private
 */
LE.Scene.prototype.initBuffers = function(gl) {
    for(var so = 0; so < this.shadowObjects.length; so++) {
        if(this.shadowObjects[so] instanceof LE.Texture) {
            this.initTextureBuffer(this.shadowObjects, so);
        } else {
            this.initPolygonBuffer(this.shadowObjects, so);
        }
    }

    for(var o = 0; o < this.objects.length; o++) {
        if(this.shadowObjects[o] instanceof LE.Texture) {
            this.initTextureBuffer(this.objects, o);
        } else {
            this.initPolygonBuffer(this.objects, o);
        }
    }

    this.shadowColourBuffers[0] = this.gl.createBuffer();
    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.shadowColourBuffers[0]);
    colors = [];

    // This number should not be fixed. TEMPORARY
    for (var i=0; i < 10000; i++) {
      colors = colors.concat([1.0, 1.0, 1.0, 0.5]);
    }
    this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(colors), this.gl.STATIC_DRAW);
    this.shadowColourBuffers[0].itemSize = 4;
    this.shadowColourBuffers[0].numItems = 10000;

    this.shadowBuffers[0] = this.gl.createBuffer();

    for(var l = 0; l < this.lights.length; l++) {
        this.initLightBuffer(this.lights, l);
    }
};

/**
 * Initialise a texture buffer
 *
 * @method initTextureBuffer
 * @param {Array} array
 * @param {Number} i Index in the given array
 * @private
 */
LE.Scene.prototype.initTextureBuffer = function(array, i) {
    var size = LE.Utilities.sizeFromVerts(array[i].vertices);
    for(var ii = i; ii >= 0; ii--) {
        var iiSize = LE.Utilities.sizeFromVerts(array[ii].vertices);
        if(array[i] != array[ii]  && array[i].vertices.length == array[ii].vertices.length
            && size.width == iiSize.width && size.height == iiSize.height) {
            array[i].bufferIndex = array[ii].bufferIndex;
        } else if(ii == 0) {
            array[i].bufferIndex = this.objectBuffers.length;

            this.objectBuffers[this.objectBuffers.length] = this.gl.createBuffer();
            this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.objectBuffers[array[i].bufferIndex]);

            var vertices = [];

            vertices = [
                LE.Utilities.toMatrix(this.gl, size.width, true), LE.Utilities.toMatrix(this.gl, size.height, false),  0.0,
                0,  LE.Utilities.toMatrix(this.gl, size.height, false),  0.0,
                LE.Utilities.toMatrix(this.gl, size.width, true), 0,  0.0,
                0, 0,  0.0,
            ];
            this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(vertices), this.gl.STATIC_DRAW);

            this.objectBuffers[array[i].bufferIndex].itemSize = 3;
            this.objectBuffers[array[i].bufferIndex].numItems = vertices.length / 3;

            // These are just pushed to the array to fill the gap because the bufferIndex variable is shared across arrays
            // These are not actually used while rendering textures
            // Indices
            this.objectIndexBuffers[this.objectIndexBuffers.length] = this.gl.createBuffer();
            this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.objectIndexBuffers[array[i].bufferIndex]);
            var vertIndices = earcut(vertices, null, 3);
            this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(vertIndices), this.gl.STATIC_DRAW);
            this.objectIndexBuffers[array[i].bufferIndex].itemSize = 1;
            this.objectIndexBuffers[array[i].bufferIndex].numItems = vertIndices.length;
            // End  

            this.objectTextureBuffers[array[i].bufferIndex] = this.gl.createBuffer();
            this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.objectTextureBuffers[array[i].bufferIndex]);
            var textureCoords = [
                1.0, 1.0,
                0.0, 1.0,
                1.0, 0.0, 
                0.0, 0.0,
            ];
            this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(textureCoords), this.gl.STATIC_DRAW);
            this.objectTextureBuffers[array[i].bufferIndex].itemSize = 2;
            this.objectTextureBuffers[array[i].bufferIndex].numItems = 4;
        }
    }
}

/**
 * Checks if a given polygon is equal to another polygon
 *
 * @method comparePolygons
 * @param {Object} object1
 * @param {Object} object2
 * @private
 */
LE.Scene.prototype.comparePolygons = function(object1, object2) {
    if(object1 != object2) {
        if(object1.vertices.length == object2.vertices.length) {
            for(var v = 0; v < object1.vertices.length; v++) {
                if(object1.vertices[v].x != object2.vertices[v].x ||
                   object1.vertices[v].y != object2.vertices[v].y) {
                    // If the individual verts arent the same exit
                    return false;
                }
            }
        } else {
            // If the vert lengths aren't the same exit
            return false;
        }
        // Could be a flaw here
        // If the object doesnt have a colour no need to check it
        if(object2.colour != null) {
            if(object1.colour.r == object2.colour.r && object1.colour.g == object2.colour.g 
            && object1.colour.b == object2.colour.b && object1.colour.a == object2.colour.a) {
                return true;
            }
        } else {

            return true;
        }
    }
    return false;
};

/**
 * Initialise a polygon buffer
 *
 * @method initPolygonBuffer
 * @param {Array} array
 * @param {Number} i Index in the given array
 * @private
 */
LE.Scene.prototype.initPolygonBuffer = function(array, i) {
    for(var ii = i; ii >= 0; ii--) {
        if(this.comparePolygons(array[i], array[ii])) {
            array[i].bufferIndex = array[ii].bufferIndex;
            break;
        } else if(ii == 0) {
            array[i].bufferIndex = this.objectBuffers.length;

            // Vertices
            this.objectBuffers[this.objectBuffers.length] = this.gl.createBuffer();
            this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.objectBuffers[array[i].bufferIndex]);
            var vertices = [];
            for(var v = 0; v < array[i].vertices.length; v++) {
                vertices.push(LE.Utilities.toMatrix(this.gl, array[i].vertices[v].x, true), 
                    LE.Utilities.toMatrix(this.gl, array[i].vertices[v].y, false),
                    0.0);
            }
            this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(vertices), this.gl.STATIC_DRAW);

            this.objectBuffers[array[i].bufferIndex].itemSize = 3;
            this.objectBuffers[array[i].bufferIndex].numItems = vertices.length / 3;
            // End

            // Indices
            this.objectIndexBuffers[this.objectIndexBuffers.length] = this.gl.createBuffer();
            this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.objectIndexBuffers[array[i].bufferIndex]);
            var vertIndices = earcut(vertices, null, 3);
            this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(vertIndices), this.gl.STATIC_DRAW);
            this.objectIndexBuffers[array[i].bufferIndex].itemSize = 1;
            this.objectIndexBuffers[array[i].bufferIndex].numItems = vertIndices.length;
            // End

            // Colours
            this.objectColourBuffers[array[i].bufferIndex] = this.gl.createBuffer();
            this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.objectColourBuffers[array[i].bufferIndex]);
            colors = [];
            for (var c = 0; c < array[i].vertices.length; c++) {
              colors = colors.concat([array[i].colour.r / 255, array[i].colour.g / 255, array[i].colour.b / 255, array[i].colour.a / 255]);
            }
            this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(colors), this.gl.STATIC_DRAW);
            this.objectColourBuffers[array[i].bufferIndex].itemSize = 4;
            this.objectColourBuffers[array[i].bufferIndex].numItems = array[i].vertices.length / 3;
            // End
        }
    }
};

/**
 * Initialises all textures
 *
 * @method initTextures
 * @private
 */
LE.Scene.prototype.initTextures = function() {
    for(var so = 0; so < this.shadowObjects.length; so++) {
        if(this.shadowObjects[so] instanceof LE.Texture) {
            this.assignTextureIndices(this.shadowObjects, so);
        }
    }

    for(var o = 0; o < this.objects.length; o++) {
        if(this.shadowObjects[o] instanceof LE.Texture) {
            this.assignTextureIndices(this.objects, o);
        }
    }
};

/**
 * Assigns a texture index to an object
 *
 * @method assignTextureIndices
 * @param {Array} array
 * @param {Number} i Index in the given array
 * @private
 */
LE.Scene.prototype.assignTextureIndices = function(array, i) {
    var self = this;
    var createNew = true;
    for(var t = 0; t < this.textures.length; t++) {
        // Required for the equals check bellow
        var temp = this.gl.createTexture();
        temp.image = new Image();
        temp.image.src = array[i].textureURL;
        //
        if(temp.image.src == this.textures[t].image.src) {
            array[i].textureIndex = t;
            createNew = false;
            break;
        }
    }
    if(createNew == true) {
        var texture = this.gl.createTexture();
        this.textures.push(texture);
        this.textures[this.textures.length -1].image = new Image();
        this.textures[this.textures.length -1].image.onload = function() {
            for(var i = 0; i < self.textures.length; i++) {
                if(self.textures[i].hasLoaded == false) {
                    self.handleLoadedTexture(self.textures[i]);
                    break; 
                } 
            }
        }
        this.textures[this.textures.length -1].image.src = array[i].textureURL; 
        this.textures[this.textures.length -1].hasLoaded = false;
        array[i].textureIndex = this.textures.length -1;
    }
};

/**
 * Prepares the loaded texture for WebGL use
 *
 * @method handleLoadedTexture
 * @param {Object} texture
 * @private
 */
LE.Scene.prototype.handleLoadedTexture = function(texture) {
    this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
    this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, true);
    this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, texture.image);
    this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
    this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
    this.gl.generateMipmap(this.gl.TEXTURE_2D);
    this.gl.bindTexture(this.gl.TEXTURE_2D, null);
    texture.hasLoaded = true;
};

/**
 * Initialise a light buffer
 *
 * @method initLightBuffer
 * @param {Array} array
 * @param {Number} i Index in the given array
 * @private
 */
LE.Scene.prototype.initLightBuffer = function(array, i) {
    for(var ii = i; ii >= 0; ii--) {
        if(array[i] != array[ii] && array[i].type === array[ii].type) {
            array[i].bufferIndex = array[ii].bufferIndex;
            break;
        } else if(ii == 0) {
            array[i].bufferIndex = this.lightBuffers.length;
            this.lightBuffers[this.lightBuffers.length] = this.gl.createBuffer();
            this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.lightBuffers[array[i].bufferIndex]);

            if(array[i].type === LE.Lights.POINT_LIGHT || array[i].type === LE.Lights.RADIAL_POINT_LIGHT) {
                // Unsure if this size of these vertices affects performance. Doesnt seem to.
                vertices = [
                    LE.Utilities.toMatrix(this.gl, this.gl.viewportWidth * 5, true), LE.Utilities.toMatrix(this.gl, this.gl.viewportHeight * 5,false),  0.0,
                    LE.Utilities.toMatrix(this.gl, -this.gl.viewportWidth * 5, true), LE.Utilities.toMatrix(this.gl, this.gl.viewportHeight * 5, false),  0.0,
                    LE.Utilities.toMatrix(this.gl, this.gl.viewportWidth * 5 , true), LE.Utilities.toMatrix(this.gl, -this.gl.viewportHeight * 5, false),  0.0,
                    LE.Utilities.toMatrix(this.gl, -this.gl.viewportWidth * 5, true), LE.Utilities.toMatrix(this.gl, -this.gl.viewportHeight * 5, false),  0.0
                ];

                this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(vertices), this.gl.DYNAMIC_DRAW);

                this.lightBuffers[array[i].bufferIndex].itemSize = 3;
                this.lightBuffers[array[i].bufferIndex].numItems = 4;  
            } else if(array[i].type === LE.Lights.DIRECTIONAL_LIGHT) {
                var originalAngle = array[i].range;

                var angle = LE.Utilities.degToRad(originalAngle / 2);
                var distance = 200000;
                // Point 1
                var x = Math.round(0 + distance * Math.cos(angle));
                var y = Math.round(0 + distance * Math.sin(angle));
                angle = LE.Utilities.degToRad(-originalAngle / 2);
                // Point 2
                var xx = Math.round(0 + distance * Math.cos(angle));
                var yy = Math.round(0 + distance * Math.sin(angle));

                vertices = [
                    0, 0,  0.0,
                    LE.Utilities.toMatrix(this.gl, x, true), LE.Utilities.toMatrix(this.gl, y, false),  0.0,
                    LE.Utilities.toMatrix(this.gl, xx, true), LE.Utilities.toMatrix(this.gl, yy, false),  0.0,
                ];

                this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(vertices), this.gl.DYNAMIC_DRAW);

                this.lightBuffers[array[i].bufferIndex].itemSize = 3;
                this.lightBuffers[array[i].bufferIndex].numItems = 3;  
            }
        }
    }
};

/**
 * Adds a PointLight, DirectionalLight or RadialPointLight to the scene
 *
 * @method addLight
 * @param {Object} light
 */
// Merge into a single "add()" function in the future
LE.Scene.prototype.addLight = function(light) {
    if(light instanceof LE.PointLight || light instanceof LE.DirectionalLight || light instanceof LE.RadialPointLight) {
        this.lights.push(light);
        if(this.gl != null) {
            this.initLightBuffer(this.lights, this.lights.length - 1);
        }
    }
};

/**
 * Adds a Polygon or Texture to the scene. This object will cast shadows
 *
 * @method addShadowObject
 * @param {Object} object
 */
// Merge into a single "add()" function in the future
LE.Scene.prototype.addShadowObject = function(object) {
    if(object instanceof LE.Texture) {
        this.shadowObjects.push(object);
        if(this.gl != null) {
            this.initTextureBuffer(this.shadowObjects, this.shadowObjects.length - 1);
            this.assignTextureIndices(this.shadowObjects, this.shadowObjects.length - 1);
        }
    } else if(object instanceof LE.Polygon) {
        this.shadowObjects.push(object);
        if(this.gl != null) {
            this.initPolygonBuffer(this.shadowObjects, this.shadowObjects.length - 1);
        }
    }
};

/**
 * Adds a Polygon or Texture to the scene. This object will not cast shadows
 *
 * @method addObject
 * @param {Object} object
 */
// Merge into a single "add()" function in the future
LE.Scene.prototype.addObject = function(object) {
    if(object instanceof LE.Texture) {
        this.objects.push(object);
        if(this.gl != null) {
            this.initTextureBuffer(this.objects, this.objects.length - 1);
            this.assignTextureIndices(this.objects, this.objects.length - 1);
        }
    } else if(object instanceof LE.Polygon) {
        this.objects.push(object);
        if(this.gl != null) {
            this.initPolygonBuffer(this.objects, this.objects.length - 1);
        }
    }
};

/**
 * Removes a PointLight, DirectionalLight or RadialPointLight from the scene
 *
 * @method removeLight
 * @param {Object} light
 */
// Merge into a single "remove()" function in the future
LE.Scene.prototype.removeLight = function(light) {
    if(light instanceof LE.PointLight || light instanceof LE.DirectionalLight || light instanceof LE.RadialPointLight) {
        this.removeObjectAndBuffer(light, this.lights, [this.lightBuffers]);
    }
};

/**
 * Removes a Polygon or Texture from the scene. This object casts shadows
 *
 * @method removeShadowObject
 * @param {Object} object
 */
// Merge into a single "remove()" function in the future
LE.Scene.prototype.removeShadowObject = function(object) {
    if(object instanceof LE.Texture || object instanceof LE.Polygon) {
        this.removeObjectAndBuffer(object, this.shadowObjects, [this.objectBuffers, this.objectColourBuffers]);
    }
};

/**
 * Removes a Polygon or Texture from the scene. This object does not cast shadows
 *
 * @method removeObject
 * @param {Object} object
 */
// Merge into a single "remove()" function in the future
LE.Scene.prototype.removeObject = function(object) {
    if(object instanceof LE.Texture || object instanceof LE.Polygon) {
        this.removeObjectAndBuffer(object, this.objects, [this.objectBuffers, this.objectColourBuffers]);
    }
};


// Remove an object and any buffers associated with it - works with lights as well
LE.Scene.prototype.removeObjectAndBuffer = function(object, array, buffers) {
    var counter = 0;
    // Check if another object uses the same buffer
    for(var i = 0; i < array.length; i++) {
        if(object != array[i] && object.bufferIndex == array[i].bufferIndex) {
            counter++;
            break;
        }
    }
    // If counter is 0 there are no other objects that use the buffer
    // This means we should remove the buffer
    if(counter == 0) {
        // Because we are removing a buffer all the bufferIndices need to be updated
        for(var i = 0; i < array.length; i++) {        
            if(array[i].bufferIndex > object.bufferIndex) {
                array[i].bufferIndex--;
            }
        }
        // Remove the buffers
        for(var b = 0; b < buffers.length; b++) {
            buffers[b].splice(object.bufferIndex, 1);
        }
    }
    // Now finally remove the object
    for(var i = 0; i < array.length; i++) {
        if(array[i] == object) {
            array.splice(i,1);
            break;
        }
    }
};