API Docs for: 0.0.2
Show:

File: Framework\Graphics\SpriteBatch.js

/**
 * AtlantisEngine.js a lightweight JavaScript game engine.
 * @module Atlantis
 * @submodule Framework
 * @namespace Atlantis
 */

var Atlantis = window.Atlantis || {};

/**
 * Define the sort mode of a sprite batch.
 * - BackToFront: All items of the batch are drawn to the smallest to the biggest layer depth
 * - FrontToBack: All items of the batch are drawn to the biggest to the smallest layer depth
 * - Immediate: No sorting, all is drawn without layer depth sorting.
 * @class SpriteSortMode
 * @static
 */
Atlantis.SpriteSortMode = {
    BackToFront: 0,
    FrontToBack: 1,
    Immediate: 2
};

/**
 * Define an effect to apply on a sprite.
 * @class SpriteEffect
 * @static
 */
Atlantis.SpriteEffect = {
    None: 0,
    FlipHorizontaly: 1,
    FlipVerticaly: 2
};

Atlantis.BatchItemType = {
    Texture: 0,
    Font: 1
};   

/**
 * Define a SpriteBatch that is responsible to draw multiple elements on screen on one pass.
 * @class SpriteBatch
 * @constructor
 * @param {Atlantis.Graphics.GraphicsDevice} The graphics device.
 */
Atlantis.SpriteBatch = function (graphicsDevice) {
    this._graphicsDevice = graphicsDevice;

    // Canvas used to render all item of the batch.
    this._renderTarget = new Atlantis.RenderTarget(graphicsDevice.preferredBackBufferWidth, graphicsDevice.preferredBackBufferHeight, false);
    this._viewport = new Atlantis.Rectangle(0, 0, graphicsDevice.preferredBackBufferWidth, graphicsDevice.preferredBackBufferHeight);
    this._canvas = this._renderTarget.getCanvas();
    this._context = this._renderTarget.getContext();

    this._batchItems = [];
    this._batchStarted = false;
    this._spriteSortMode = Atlantis.SpriteSortMode.Immediate;
    this._transformMatrix = null;
    this._cacheColoredTextures = [];
    
    document.addEventListener(Atlantis.events.ResolutionChanged, this._onResize.bind(this), false);
};

Atlantis.SpriteBatch.prototype._onResize = function (event) {
    this._renderTarget.setSize(event.width, event.height);
    this._viewport.width = event.width;
    this._viewport.height = event.height;
};

/**
 * Begin the batch operation.
 * @method begin
 * @param {Atlantis.SpriteSortMode} The type of sorting to use (option, default is Immediate).
 * @param {Array} A transform matrix to apply for all items (optional). The matrix is a 3x3 matrix in a single array.
 *        - [ScaleX, SkewX, SkewY, ScaleY, TranslationX, TranslationY]
 */
Atlantis.SpriteBatch.prototype.begin = function (spriteSortMode, transformMatrix) {
    if (!this._batchStarted) {
        this._batchStarted = true;
        this._transformMatrix = transformMatrix ? (transformMatrix.length === 6 ? transformMatrix : null) : null;
        this._spriteSortMode = typeof(spriteSortMode) === "number" ? spriteSortMode : Atlantis.SpriteSortMode.Immediate;
        this._renderTarget.clear();
    }
};

Atlantis.SpriteBatch.prototype._sortBatchItem = function (itemA, itemB) {
    if (this._spriteSortMode === Atlantis.SpriteSortMode.BackToFront) {
        if (+itemA.layerDepth > +itemB.layerDepth) {
            return 1;   
        }
        
        if (+itemA.layerDepth < +itemB.layerDepth) {
            return -1;   
        }
        
        return 0;
    }
    else {
        if (+itemA.layerDepth < +itemB.layerDepth) {
            return 1;   
        }
        
        if (+itemA.layerDepth > +itemB.layerDepth) {
            return -1;   
        }
        
        return 0;
    }
};

/**
 * Execute the batch process and draw the result in the screen.
 * @method end
 */
Atlantis.SpriteBatch.prototype.end = function () {
    if (this._batchStarted) {
        var item = {};
        var that = this;

        if (this._spriteSortMode !== Atlantis.SpriteSortMode.Immediate) {
            this._batchItems = this._batchItems.sort(this._sortBatchItem.bind(this));
        }
        
        var fX, fY, fWidth, fHeight;
        var oX, oY;
        var testRectangle = new Atlantis.Rectangle();

        if (this._transformMatrix) {
            this._context.save();
            
            this._context.transform(
                this._transformMatrix[0], this._transformMatrix[1], this._transformMatrix[2],
                this._transformMatrix[3], this._transformMatrix[4], this._transformMatrix[5]);
        }
        
        for (var i = 0, l = this._batchItems.length; i < l; i++) {
            item = this._batchItems[i];
            
            fX = item.destinationRectangle.x;
            fY = item.destinationRectangle.y;
            fWidth = item.destinationRectangle.width;
            fHeight = item.destinationRectangle.height;
            
            // If the entity is visible on the screen.   
            testRectangle.set(fX, fY, fWidth, fHeight);
            if (this._viewport.intersects(testRectangle)) {
                oX = item.origin ? item.origin.x : 0;
                oY = item.origin ? item.origin.y : 0;
       
                this._context.save();

                this._context.translate(item.destinationRectangle.x, item.destinationRectangle.y);
                this._context.translate(oX, oY);
                fX = -oX;
                fY = -oY;

                if (item.rotation) {
                    this._context.rotate(item.rotation);    
                }

                if (item.scale) {
                    this._context.scale(item.scale.x, item.scale.y);   
                }

                if (item.effect != Atlantis.SpriteEffect.None) {
                    if (item.effect == Atlantis.SpriteEffect.FlipHorizontaly) {
                        this._context.scale(-1, 1);
                        fX -= item.destinationRectangle.width;
                    }
                    else {
                        this._context.scale(1, -1);   
                        fY -= item.destinationRectangle.height; 
                    }
                }

                if (item.type === Atlantis.BatchItemType.Texture) { 
                    if (item.color && item.texture2D.width && item.texture2D.height) {
                        Atlantis.SpriteBatch.drawTexture(this._context, this._colorizeTexture(item.texture2D, item.color), fX, fY, fWidth, fHeight, item.sourceRectangle);  
                    }
                    else {
                        Atlantis.SpriteBatch.drawTexture(this._context, item.texture2D, fX, fY, fWidth, fHeight, item.sourceRectangle); 
                    }
                }
                else if (item.type === Atlantis.BatchItemType.Font) {
                    this._context.fillStyle = item.color;
                    this._context.font = item.spriteFont.getFont();
                    this._context.fillText(item.text, fX, fY);
                }

                this._context.restore();
            }
        }
        
         if (this._transformMatrix) {
            this._context.restore();
         }

        // Flush renderTarget into backbuffer
        this._graphicsDevice.getBackBuffer().getContext().drawImage(this._canvas, 0, 0, this._viewport.width, this._viewport.height);
      //  this._graphicsDevice.present();
        this._batchItems.length = 0;
        this._batchStarted = false;
    }
};

/**
 * Draw a texture on the screen
 * @method drawTexture
 * @static
 * @param {CanvasContext} The canvas context.
 * @param {Image} The image or canvas to draw.
 * @param {Number} x coordinate.
 * @param {Number} y coordinate.
 * @param {Number} width of the image.
 * @param {Number} height of the image.
 * @param {Atlantis.Rectangle} A source rectangle.
 */
Atlantis.SpriteBatch.drawTexture  = function (context, texture, x, y, width, height, sourceRectangle) {
    if (sourceRectangle) { 
        context.drawImage(texture, sourceRectangle.x, sourceRectangle.y, sourceRectangle.width, sourceRectangle.height, x, y, width, height); 
    }
    else {
        context.drawImage(texture, x, y, width, height);
    }
};

/**
 * Draw a texture on the screen
 * @method draw
 * @param {Image} The image or canvas to draw.
 * @param {Atlantis.Rectangle|Atlantis.Vector2} The position or the rectangle of the image.
 * @param {Atlantis.Rectangle} A source rectangle.
 * @param {String} A color to apply on the image in hex format.
 * @param {Number} Rotation of the image.
 * @param {Atlantis.Vector2} Origin of the image (defaut is 0, 0 on top/left).
 * @param {Atlantis.Vector2} Scale of the image (default is 1/1);
 * @param {Atlantis.SpriteEffect} An effect to apply (default is none).
 * @param {Number} The layer depth (Important when SpriteSortMode is set to BackToFront or FrontToBack).
 */
Atlantis.SpriteBatch.prototype.draw = function (texture2D, destinationRectangle, sourceRectangle, color, rotation, origin, scale, effect, layerDepth) {
    if (this._batchStarted) {
        if (!destinationRectangle.width) {
            destinationRectangle.width = texture2D.width;
            destinationRectangle.height = texture2D.height;
        }

        this._batchItems.push({ 
            type: Atlantis.BatchItemType.Texture, 
            texture2D: texture2D, 
            sourceRectangle: sourceRectangle, 
            destinationRectangle: destinationRectangle, 
            color: color, 
            rotation: rotation,
            origin: origin, 
            scale: scale,
            effect: effect ? effect : Atlantis.SpriteEffect.None, 
            layerDepth: (typeof(layerDepth) === "number") ? layerDepth : 0 
        });
    }
};

/**
 * Draw a string on the screen
 * @method drawString
 * @param {Atlantis.SpriteFont} The SpriteFont to use.
 * @param {String} The string to draw.
 * @param {Atlantis.Vector2} The position of the string.
 * @param {String} The color of the string in hex format.
 * @param {Number} Rotation of the image.
 * @param {Atlantis.Vector2} Origin of the image (defaut is 0, 0 on top/left).
 * @param {Atlantis.Vector2} Scale of the image (default is 1/1);
 * @param {Atlantis.SpriteEffect} An effect to apply (default is none).
 * @param {Number} The layer depth (Important when SpriteSortMode is set to BackToFront or FrontToBack).
 */
Atlantis.SpriteBatch.prototype.drawString = function (spriteFont, text, position, color, rotation, origin, scale, effect, layerDepth) {
    if (this._batchStarted) {
        this._batchItems.push({ 
            type: Atlantis.BatchItemType.Font, 
            spriteFont: spriteFont, 
            text: text, 
            destinationRectangle: { x: position.x, y: position.y, width: 1, height: 1 }, 
            color: color, 
            rotation: rotation,
            origin: origin, 
            scale: scale,
            effect: effect ? effect : Atlantis.SpriteEffect.None, 
            layerDepth: (typeof(layerDepth) === "number") ? layerDepth : 0 
        });   
    }
};

// Colorize a texture and put it in a cache.
Atlantis.SpriteBatch.prototype._colorizeTexture = function (texture, color) {
    var canvas = this._searchColoredTexture(texture, color);
    
    if (!canvas) {
        canvas = document.createElement("canvas");
        canvas.width = texture.width;
        canvas.height = texture.height;

        var context = canvas.getContext("2d");
        context.drawImage(texture, 0, 0);

        var imageData = context.getImageData(0, 0, texture.width, texture.height);
        var cColor = this._hexaToBytes(color);
        
        for (var i = 0, l = imageData.data.length; i < l; i += 4) {
            imageData.data[i] = cColor.r | imageData.data[i];
            imageData.data[i + 1] = cColor.g | imageData.data[i + 1];
            imageData.data[i + 2] = cColor.b | imageData.data[i + 2]; 
            imageData.data[i + 3] = cColor.a | imageData.data[i + 3]; 
        }

        context.putImageData(imageData, 0, 0);

        this._cacheColoredTextures.push({ texture: texture, color: color, canvas: canvas });
    } 
    
    return canvas;
};

// Convert an hexa color to byte color.
Atlantis.SpriteBatch.prototype._hexaToBytes = function (color) {
    var hexa = color.split("#")[1];
    var bColor = { r: 0, g: 0, b: 0, a: 0 };
    var size = hexa.length;

    if (size === 3) {
        bColor.r = parseInt((hexa[0] + hexa[0]), 16);
        bColor.g = parseInt((hexa[1] + hexa[1]), 16);
        bColor.b = parseInt((hexa[2] + hexa[2]), 16);
    }
    else if (size === 6) {
        bColor.r = parseInt(hexa.slice(0, 2), 16);
        bColor.g = parseInt(hexa.slice(2, 4), 16);
        bColor.b = parseInt(hexa.slice(4, 6), 16);
    }
    else if (size === 8) {
        bColor.r = parseInt(hexa.slice(0, 2), 16);
        bColor.g = parseInt(hexa.slice(2, 4), 16);
        bColor.b = parseInt(hexa.slice(4, 6), 16);
        bColor.a = parseInt(hexa.slice(6, 8), 16);
    }

    return bColor;
};

// Search if a colored texture is already in the cache.
Atlantis.SpriteBatch.prototype._searchColoredTexture = function(texture, color) {
    var i = 0;
    var size = this._cacheColoredTextures.length;
    var canvas = null;

    while (i < size && canvas === null) {
        canvas = (this._cacheColoredTextures[i].texture === texture && this._cacheColoredTextures[i].color === color) ? this._cacheColoredTextures[i].canvas : null;
        i++;
    }

    return canvas;
};