/** * @author Richard Davey * @copyright 2016 Photon Storm Ltd. * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} * * @overview * * Phaser - http://phaser.io * * v2.4.9 "Four Kings" - Built: Fri Jun 10 2016 16:18:37 * * By Richard Davey http://www.photonstorm.com @photonstorm * * Phaser is a fun, free and fast 2D game framework for making HTML5 games * for desktop and mobile web browsers, supporting Canvas and WebGL rendering. * * Phaser uses Pixi.js for rendering, created by Mat Groves http://matgroves.com @Doormat23 * Phaser uses p2.js for full-body physics, created by Stefan Hedman https://github.com/schteppe/p2.js @schteppe * Phaser contains a port of N+ Physics, converted by Richard Davey, original by http://www.metanetsoftware.com * * Many thanks to Adam Saltsman (@ADAMATOMIC) for releasing Flixel, from which both Phaser * and my love of framework development originate. * * Follow development at http://phaser.io and on our forum * * "If you want your children to be intelligent, read them fairy tales." * "If you want them to be more intelligent, read them more fairy tales." * -- Albert Einstein */ /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ (function(){ var root = this; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * The [pixi.js](http://www.pixijs.com/) module/namespace. * * @module PIXI */ /** * Namespace-class for [pixi.js](http://www.pixijs.com/). * * Contains assorted static properties and enumerations. * * @class PIXI * @static */ var PIXI = PIXI || {}; /** * A reference to the Phaser Game instance that owns this Pixi renderer. * @property {Phaser.Game} game * @static */ PIXI.game = null; /** * @property {Number} WEBGL_RENDERER * @protected * @static */ PIXI.WEBGL_RENDERER = 0; /** * @property {Number} CANVAS_RENDERER * @protected * @static */ PIXI.CANVAS_RENDERER = 1; /** * Version of pixi that is loaded. * @property {String} VERSION * @static */ PIXI.VERSION = "v2.2.9"; // used to create uids for various pixi objects. PIXI._UID = 0; if (typeof(Float32Array) != 'undefined') { PIXI.Float32Array = Float32Array; PIXI.Uint16Array = Uint16Array; // Uint32Array and ArrayBuffer only used by WebGL renderer // We can suppose that if WebGL is supported then typed arrays are supported too // as they predate WebGL support for all browsers: // see typed arrays support: http://caniuse.com/#search=TypedArrays // see WebGL support: http://caniuse.com/#search=WebGL PIXI.Uint32Array = Uint32Array; PIXI.ArrayBuffer = ArrayBuffer; } else { PIXI.Float32Array = Array; PIXI.Uint16Array = Array; } /** * @property {Number} PI_2 * @static */ PIXI.PI_2 = Math.PI * 2; /** * @property {Number} RAD_TO_DEG * @static */ PIXI.RAD_TO_DEG = 180 / Math.PI; /** * @property {Number} DEG_TO_RAD * @static */ PIXI.DEG_TO_RAD = Math.PI / 180; /** * @property {String} RETINA_PREFIX * @protected * @static */ PIXI.RETINA_PREFIX = "@2x"; /** * The default render options if none are supplied to * {{#crossLink "WebGLRenderer"}}{{/crossLink}} or {{#crossLink "CanvasRenderer"}}{{/crossLink}}. * * @property {Object} defaultRenderOptions * @property {Object} defaultRenderOptions.view=null * @property {Boolean} defaultRenderOptions.transparent=false * @property {Boolean} defaultRenderOptions.antialias=false * @property {Boolean} defaultRenderOptions.preserveDrawingBuffer=false * @property {Number} defaultRenderOptions.resolution=1 * @property {Boolean} defaultRenderOptions.clearBeforeRender=true * @property {Boolean} defaultRenderOptions.autoResize=false * @static PIXI.defaultRenderOptions = { view: null, transparent: false, antialias: false, preserveDrawingBuffer: false, resolution: 1, clearBeforeRender: true, autoResize: false }; */ /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * The base class for all objects that are rendered on the screen. * This is an abstract class and should not be used on its own rather it should be extended. * * @class DisplayObject * @constructor */ PIXI.DisplayObject = function() { /** * The coordinate of the object relative to the local coordinates of the parent. * * @property position * @type Point */ this.position = new PIXI.Point(0, 0); /** * The scale factor of the object. * * @property scale * @type Point */ this.scale = new PIXI.Point(1, 1); /** * The pivot point of the displayObject that it rotates around * * @property pivot * @type Point */ this.pivot = new PIXI.Point(0, 0); /** * The rotation of the object in radians. * * @property rotation * @type Number */ this.rotation = 0; /** * The opacity of the object. * * @property alpha * @type Number */ this.alpha = 1; /** * The visibility of the object. * * @property visible * @type Boolean */ this.visible = true; /** * This is the defined area that will pick up mouse / touch events. It is null by default. * Setting it is a neat way of optimising the hitTest function that the interactionManager will use (as it will not need to hit test all the children) * * @property hitArea * @type Rectangle|Circle|Ellipse|Polygon */ this.hitArea = null; /** * Can this object be rendered * * @property renderable * @type Boolean */ this.renderable = false; /** * [read-only] The display object container that contains this display object. * * @property parent * @type DisplayObjectContainer * @readOnly */ this.parent = null; /** * [read-only] The stage the display object is connected to, or undefined if it is not connected to the stage. * * @property stage * @type Stage * @readOnly */ this.stage = null; /** * [read-only] The multiplied alpha of the displayObject * * @property worldAlpha * @type Number * @readOnly */ this.worldAlpha = 1; /** * [read-only] Current transform of the object based on world (parent) factors * * @property worldTransform * @type Matrix * @readOnly * @private */ this.worldTransform = new PIXI.Matrix(); /** * The position of the Display Object based on the world transform. * This value is updated at the end of updateTransform and takes all parent transforms into account. * * @property worldPosition * @type Point * @readOnly */ this.worldPosition = new PIXI.Point(0, 0); /** * The scale of the Display Object based on the world transform. * This value is updated at the end of updateTransform and takes all parent transforms into account. * * @property worldScale * @type Point * @readOnly */ this.worldScale = new PIXI.Point(1, 1); /** * The rotation of the Display Object, in radians, based on the world transform. * This value is updated at the end of updateTransform and takes all parent transforms into account. * * @property worldRotation * @type Number * @readOnly */ this.worldRotation = 0; /** * cached sin rotation and cos rotation * * @property _sr * @type Number * @private */ this._sr = 0; /** * cached sin rotation and cos rotation * * @property _cr * @type Number * @private */ this._cr = 1; /** * The area the filter is applied to like the hitArea this is used as more of an optimisation * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle * * @property filterArea * @type Rectangle */ this.filterArea = null; /** * The original, cached bounds of the object * * @property _bounds * @type Rectangle * @private */ this._bounds = new PIXI.Rectangle(0, 0, 1, 1); /** * The most up-to-date bounds of the object * * @property _currentBounds * @type Rectangle * @private */ this._currentBounds = null; /** * The original, cached mask of the object * * @property _mask * @type Rectangle * @private */ this._mask = null; /** * Cached internal flag. * * @property _cacheAsBitmap * @type Boolean * @private */ this._cacheAsBitmap = false; /** * Cached internal flag. * * @property _cacheIsDirty * @type Boolean * @private */ this._cacheIsDirty = false; }; // constructor PIXI.DisplayObject.prototype.constructor = PIXI.DisplayObject; /** * Destroy this DisplayObject. * Removes all references to transformCallbacks, its parent, the stage, filters, bounds, mask and cached Sprites. * * @method destroy */ PIXI.DisplayObject.prototype.destroy = function() { if (this.children) { var i = this.children.length; while (i--) { this.children[i].destroy(); } this.children = []; } this.hitArea = null; this.parent = null; this.stage = null; this.worldTransform = null; this.filterArea = null; this._bounds = null; this._currentBounds = null; this._mask = null; // In case Pixi is still going to try and render it even though destroyed this.renderable = false; this._destroyCachedSprite(); }; /** * [read-only] Indicates if the sprite is globally visible. * * @property worldVisible * @type Boolean */ Object.defineProperty(PIXI.DisplayObject.prototype, 'worldVisible', { get: function() { var item = this; do { if (!item.visible) return false; item = item.parent; } while(item); return true; } }); /** * Sets a mask for the displayObject. A mask is an object that limits the visibility of an object to the shape of the mask applied to it. * In PIXI a regular mask must be a PIXI.Graphics object. This allows for much faster masking in canvas as it utilises shape clipping. * To remove a mask, set this property to null. * * @property mask * @type Graphics */ Object.defineProperty(PIXI.DisplayObject.prototype, 'mask', { get: function() { return this._mask; }, set: function(value) { if (this._mask) this._mask.isMask = false; this._mask = value; if (this._mask) this._mask.isMask = true; } }); /** * Sets the filters for the displayObject. * IMPORTANT: This is a webGL only feature and will be ignored by the Canvas renderer. * * To remove filters simply set this property to 'null'. * * You cannot have a filter and a multiply blend mode active at the same time. Setting a filter will reset * this objects blend mode to NORMAL. * * @property filters * @type Array(Filter) */ Object.defineProperty(PIXI.DisplayObject.prototype, 'filters', { get: function() { return this._filters; }, set: function(value) { if (value) { // now put all the passes in one place.. var passes = []; for (var i = 0; i < value.length; i++) { var filterPasses = value[i].passes; for (var j = 0; j < filterPasses.length; j++) { passes.push(filterPasses[j]); } } // TODO change this as it is legacy this._filterBlock = { target: this, filterPasses: passes }; } this._filters = value; if (this.blendMode && this.blendMode === PIXI.blendModes.MULTIPLY) { this.blendMode = PIXI.blendModes.NORMAL; } } }); /** * Set if this display object is cached as a bitmap. * This basically takes a snap shot of the display object as it is at that moment. It can provide a performance benefit for complex static displayObjects. * To remove simply set this property to 'null' * @property cacheAsBitmap * @type Boolean */ Object.defineProperty(PIXI.DisplayObject.prototype, 'cacheAsBitmap', { get: function() { return this._cacheAsBitmap; }, set: function(value) { if (this._cacheAsBitmap === value) { return; } if (value) { this._generateCachedSprite(); } else { this._destroyCachedSprite(); } this._cacheAsBitmap = value; } }); /* * Updates the object transform for rendering. * * If the object has no parent, and no parent parameter is provided, it will default to Phaser.Game.World as the parent. * If that is unavailable the transform fails to take place. * * The `parent` parameter has priority over the actual parent. Use it as a parent override. * Setting it does **not** change the actual parent of this DisplayObject, it just uses the parent for the transform update. * * @method updateTransform * @param {DisplayObject} [parent] - Optional parent to parent this DisplayObject transform from. */ PIXI.DisplayObject.prototype.updateTransform = function(parent) { if (!parent && !this.parent && !this.game) { return; } var p = this.parent; if (parent) { p = parent; } else if (!this.parent) { p = this.game.world; } // create some matrix refs for easy access var pt = p.worldTransform; var wt = this.worldTransform; // temporary matrix variables var a, b, c, d, tx, ty; // so if rotation is between 0 then we can simplify the multiplication process.. if (this.rotation % PIXI.PI_2) { // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes if (this.rotation !== this.rotationCache) { this.rotationCache = this.rotation; this._sr = Math.sin(this.rotation); this._cr = Math.cos(this.rotation); } // get the matrix values of the displayobject based on its transform properties.. a = this._cr * this.scale.x; b = this._sr * this.scale.x; c = -this._sr * this.scale.y; d = this._cr * this.scale.y; tx = this.position.x; ty = this.position.y; // check for pivot.. not often used so geared towards that fact! if (this.pivot.x || this.pivot.y) { tx -= this.pivot.x * a + this.pivot.y * c; ty -= this.pivot.x * b + this.pivot.y * d; } // concat the parent matrix with the objects transform. wt.a = a * pt.a + b * pt.c; wt.b = a * pt.b + b * pt.d; wt.c = c * pt.a + d * pt.c; wt.d = c * pt.b + d * pt.d; wt.tx = tx * pt.a + ty * pt.c + pt.tx; wt.ty = tx * pt.b + ty * pt.d + pt.ty; } else { // lets do the fast version as we know there is no rotation.. a = this.scale.x; d = this.scale.y; tx = this.position.x - this.pivot.x * a; ty = this.position.y - this.pivot.y * d; wt.a = a * pt.a; wt.b = a * pt.b; wt.c = d * pt.c; wt.d = d * pt.d; wt.tx = tx * pt.a + ty * pt.c + pt.tx; wt.ty = tx * pt.b + ty * pt.d + pt.ty; } // multiply the alphas.. this.worldAlpha = this.alpha * p.worldAlpha; this.worldPosition.set(wt.tx, wt.ty); this.worldScale.set(Math.sqrt(wt.a * wt.a + wt.b * wt.b), Math.sqrt(wt.c * wt.c + wt.d * wt.d)); this.worldRotation = Math.atan2(wt.c, wt.d); // reset the bounds each time this is called! this._currentBounds = null; // Custom callback? if (this.transformCallback) { this.transformCallback.call(this.transformCallbackContext, wt, pt); } }; // performance increase to avoid using call.. (10x faster) PIXI.DisplayObject.prototype.displayObjectUpdateTransform = PIXI.DisplayObject.prototype.updateTransform; /** * Retrieves the bounds of the displayObject as a rectangle object * * @method getBounds * @param matrix {Matrix} * @return {Rectangle} the rectangular bounding area */ PIXI.DisplayObject.prototype.getBounds = function(matrix) { matrix = matrix;//just to get passed js hinting (and preserve inheritance) return PIXI.EmptyRectangle; }; /** * Retrieves the local bounds of the displayObject as a rectangle object * * @method getLocalBounds * @return {Rectangle} the rectangular bounding area */ PIXI.DisplayObject.prototype.getLocalBounds = function() { return this.getBounds(PIXI.identityMatrix);///PIXI.EmptyRectangle(); }; /** * Sets the object's stage reference, the stage this object is connected to * * @method setStageReference * @param stage {Stage} the stage that the object will have as its current stage reference */ PIXI.DisplayObject.prototype.setStageReference = function(stage) { this.stage = stage; }; /** * Empty, to be overridden by classes that require it. * * @method preUpdate */ PIXI.DisplayObject.prototype.preUpdate = function() { }; /** * Useful function that returns a texture of the displayObject object that can then be used to create sprites * This can be quite useful if your displayObject is static / complicated and needs to be reused multiple times. * * @method generateTexture * @param resolution {Number} The resolution of the texture being generated * @param scaleMode {Number} See {{#crossLink "PIXI/scaleModes:property"}}PIXI.scaleModes{{/crossLink}} for possible values * @param renderer {CanvasRenderer|WebGLRenderer} The renderer used to generate the texture. * @return {RenderTexture} a texture of the graphics object */ PIXI.DisplayObject.prototype.generateTexture = function(resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); var renderTexture = new PIXI.RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); PIXI.DisplayObject._tempMatrix.tx = -bounds.x; PIXI.DisplayObject._tempMatrix.ty = -bounds.y; renderTexture.render(this, PIXI.DisplayObject._tempMatrix); return renderTexture; }; /** * Generates and updates the cached sprite for this object. * * @method updateCache */ PIXI.DisplayObject.prototype.updateCache = function() { this._generateCachedSprite(); }; /** * Calculates the global position of the display object * * @method toGlobal * @param position {Point} The world origin to calculate from * @return {Point} A point object representing the position of this object */ PIXI.DisplayObject.prototype.toGlobal = function(position) { // don't need to u[date the lot this.displayObjectUpdateTransform(); return this.worldTransform.apply(position); }; /** * Calculates the local position of the display object relative to another point * * @method toLocal * @param position {Point} The world origin to calculate from * @param [from] {DisplayObject} The DisplayObject to calculate the global position from * @return {Point} A point object representing the position of this object */ PIXI.DisplayObject.prototype.toLocal = function(position, from) { if (from) { position = from.toGlobal(position); } // don't need to u[date the lot this.displayObjectUpdateTransform(); return this.worldTransform.applyInverse(position); }; /** * Internal method. * * @method _renderCachedSprite * @param renderSession {Object} The render session * @private */ PIXI.DisplayObject.prototype._renderCachedSprite = function(renderSession) { this._cachedSprite.worldAlpha = this.worldAlpha; if (renderSession.gl) { PIXI.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); } else { PIXI.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); } }; /** * Internal method. * * @method _generateCachedSprite * @private */ PIXI.DisplayObject.prototype._generateCachedSprite = function() { this._cacheAsBitmap = false; var bounds = this.getLocalBounds(); // Round it off and force non-zero dimensions bounds.width = Math.max(1, Math.ceil(bounds.width)); bounds.height = Math.max(1, Math.ceil(bounds.height)); this.updateTransform(); if (!this._cachedSprite) { var renderTexture = new PIXI.RenderTexture(bounds.width, bounds.height); this._cachedSprite = new PIXI.Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; } else { this._cachedSprite.texture.resize(bounds.width, bounds.height); } // Remove filters var tempFilters = this._filters; this._filters = null; this._cachedSprite.filters = tempFilters; // PIXI.DisplayObject._tempMatrix.identity(); PIXI.DisplayObject._tempMatrix.tx = -bounds.x; PIXI.DisplayObject._tempMatrix.ty = -bounds.y; this._cachedSprite.texture.render(this, PIXI.DisplayObject._tempMatrix, true); this._cachedSprite.anchor.x = -(bounds.x / bounds.width); this._cachedSprite.anchor.y = -(bounds.y / bounds.height); this._filters = tempFilters; this._cacheAsBitmap = true; }; /** * Destroys the cached sprite. * * @method _destroyCachedSprite * @private */ PIXI.DisplayObject.prototype._destroyCachedSprite = function() { if (!this._cachedSprite) return; this._cachedSprite.texture.destroy(true); // TODO could be object pooled! this._cachedSprite = null; }; /** * Renders the object using the WebGL renderer * * @method _renderWebGL * @param renderSession {RenderSession} * @private */ PIXI.DisplayObject.prototype._renderWebGL = function(renderSession) { // OVERWRITE; // this line is just here to pass jshinting :) renderSession = renderSession; }; /** * Renders the object using the Canvas renderer * * @method _renderCanvas * @param renderSession {RenderSession} * @private */ PIXI.DisplayObject.prototype._renderCanvas = function(renderSession) { // OVERWRITE; // this line is just here to pass jshinting :) renderSession = renderSession; }; /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. * * @property x * @type Number */ Object.defineProperty(PIXI.DisplayObject.prototype, 'x', { get: function() { return this.position.x; }, set: function(value) { this.position.x = value; } }); /** * The position of the displayObject on the y axis relative to the local coordinates of the parent. * * @property y * @type Number */ Object.defineProperty(PIXI.DisplayObject.prototype, 'y', { get: function() { return this.position.y; }, set: function(value) { this.position.y = value; } }); /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * A DisplayObjectContainer represents a collection of display objects. * It is the base class of all display objects that act as a container for other objects. * * @class DisplayObjectContainer * @extends DisplayObject * @constructor */ PIXI.DisplayObjectContainer = function() { PIXI.DisplayObject.call(this); /** * [read-only] The array of children of this container. * * @property children * @type Array(DisplayObject) * @readOnly */ this.children = []; /** * If `ignoreChildInput` is `false` it will allow this objects _children_ to be considered as valid for Input events. * * If this property is `true` then the children will _not_ be considered as valid for Input events. * * Note that this property isn't recursive: only immediate children are influenced, it doesn't scan further down. * @property {boolean} ignoreChildInput * @default */ this.ignoreChildInput = false; }; // constructor PIXI.DisplayObjectContainer.prototype = Object.create( PIXI.DisplayObject.prototype ); PIXI.DisplayObjectContainer.prototype.constructor = PIXI.DisplayObjectContainer; /** * The width of the displayObjectContainer, setting this will actually modify the scale to achieve the value set * * @property width * @type Number */ Object.defineProperty(PIXI.DisplayObjectContainer.prototype, 'width', { get: function() { return this.scale.x * this.getLocalBounds().width; }, set: function(value) { var width = this.getLocalBounds().width; if (width !== 0) { this.scale.x = value / width; } else { this.scale.x = 1; } this._width = value; } }); /** * The height of the displayObjectContainer, setting this will actually modify the scale to achieve the value set * * @property height * @type Number */ Object.defineProperty(PIXI.DisplayObjectContainer.prototype, 'height', { get: function() { return this.scale.y * this.getLocalBounds().height; }, set: function(value) { var height = this.getLocalBounds().height; if (height !== 0) { this.scale.y = value / height; } else { this.scale.y = 1; } this._height = value; } }); /** * Adds a child to the container. * * @method addChild * @param child {DisplayObject} The DisplayObject to add to the container * @return {DisplayObject} The child that was added. */ PIXI.DisplayObjectContainer.prototype.addChild = function(child) { return this.addChildAt(child, this.children.length); }; /** * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown * * @method addChildAt * @param child {DisplayObject} The child to add * @param index {Number} The index to place the child in * @return {DisplayObject} The child that was added. */ PIXI.DisplayObjectContainer.prototype.addChildAt = function(child, index) { if(index >= 0 && index <= this.children.length) { if(child.parent) { child.parent.removeChild(child); } child.parent = this; this.children.splice(index, 0, child); if(this.stage)child.setStageReference(this.stage); return child; } else { throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); } }; /** * Swaps the position of 2 Display Objects within this container. * * @method swapChildren * @param child {DisplayObject} * @param child2 {DisplayObject} */ PIXI.DisplayObjectContainer.prototype.swapChildren = function(child, child2) { if(child === child2) { return; } var index1 = this.getChildIndex(child); var index2 = this.getChildIndex(child2); if(index1 < 0 || index2 < 0) { throw new Error('swapChildren: Both the supplied DisplayObjects must be a child of the caller.'); } this.children[index1] = child2; this.children[index2] = child; }; /** * Returns the index position of a child DisplayObject instance * * @method getChildIndex * @param child {DisplayObject} The DisplayObject instance to identify * @return {Number} The index position of the child display object to identify */ PIXI.DisplayObjectContainer.prototype.getChildIndex = function(child) { var index = this.children.indexOf(child); if (index === -1) { throw new Error('The supplied DisplayObject must be a child of the caller'); } return index; }; /** * Changes the position of an existing child in the display object container * * @method setChildIndex * @param child {DisplayObject} The child DisplayObject instance for which you want to change the index number * @param index {Number} The resulting index number for the child display object */ PIXI.DisplayObjectContainer.prototype.setChildIndex = function(child, index) { if (index < 0 || index >= this.children.length) { throw new Error('The supplied index is out of bounds'); } var currentIndex = this.getChildIndex(child); this.children.splice(currentIndex, 1); //remove from old position this.children.splice(index, 0, child); //add at new position }; /** * Returns the child at the specified index * * @method getChildAt * @param index {Number} The index to get the child from * @return {DisplayObject} The child at the given index, if any. */ PIXI.DisplayObjectContainer.prototype.getChildAt = function(index) { if (index < 0 || index >= this.children.length) { throw new Error('getChildAt: Supplied index '+ index +' does not exist in the child list, or the supplied DisplayObject must be a child of the caller'); } return this.children[index]; }; /** * Removes a child from the container. * * @method removeChild * @param child {DisplayObject} The DisplayObject to remove * @return {DisplayObject} The child that was removed. */ PIXI.DisplayObjectContainer.prototype.removeChild = function(child) { var index = this.children.indexOf( child ); if(index === -1)return; return this.removeChildAt( index ); }; /** * Removes a child from the specified index position. * * @method removeChildAt * @param index {Number} The index to get the child from * @return {DisplayObject} The child that was removed. */ PIXI.DisplayObjectContainer.prototype.removeChildAt = function(index) { var child = this.getChildAt( index ); if(this.stage) child.removeStageReference(); child.parent = undefined; this.children.splice( index, 1 ); return child; }; /** * Removes all children from this container that are within the begin and end indexes. * * @method removeChildren * @param beginIndex {Number} The beginning position. Default value is 0. * @param endIndex {Number} The ending position. Default value is size of the container. */ PIXI.DisplayObjectContainer.prototype.removeChildren = function(beginIndex, endIndex) { var begin = beginIndex || 0; var end = typeof endIndex === 'number' ? endIndex : this.children.length; var range = end - begin; if (range > 0 && range <= end) { var removed = this.children.splice(begin, range); for (var i = 0; i < removed.length; i++) { var child = removed[i]; if(this.stage) child.removeStageReference(); child.parent = undefined; } return removed; } else if (range === 0 && this.children.length === 0) { return []; } else { throw new Error( 'removeChildren: Range Error, numeric values are outside the acceptable range' ); } }; /* * Updates the transform on all children of this container for rendering * * @method updateTransform * @private */ PIXI.DisplayObjectContainer.prototype.updateTransform = function() { if (!this.visible) { return; } this.displayObjectUpdateTransform(); if (this._cacheAsBitmap) { return; } for (var i = 0; i < this.children.length; i++) { this.children[i].updateTransform(); } }; // performance increase to avoid using call.. (10x faster) PIXI.DisplayObjectContainer.prototype.displayObjectContainerUpdateTransform = PIXI.DisplayObjectContainer.prototype.updateTransform; /** * Retrieves the bounds of the displayObjectContainer as a rectangle. The bounds calculation takes all visible children into consideration. * * @method getBounds * @return {Rectangle} The rectangular bounding area */ PIXI.DisplayObjectContainer.prototype.getBounds = function() { if (this.children.length === 0) { return PIXI.EmptyRectangle; } var minX = Infinity; var minY = Infinity; var maxX = -Infinity; var maxY = -Infinity; var childBounds; var childMaxX; var childMaxY; var childVisible = false; for (var i = 0; i < this.children.length; i++) { var child = this.children[i]; if (!child.visible) { continue; } childVisible = true; childBounds = this.children[i].getBounds(); minX = minX < childBounds.x ? minX : childBounds.x; minY = minY < childBounds.y ? minY : childBounds.y; childMaxX = childBounds.width + childBounds.x; childMaxY = childBounds.height + childBounds.y; maxX = maxX > childMaxX ? maxX : childMaxX; maxY = maxY > childMaxY ? maxY : childMaxY; } if (!childVisible) { return PIXI.EmptyRectangle; } var bounds = this._bounds; bounds.x = minX; bounds.y = minY; bounds.width = maxX - minX; bounds.height = maxY - minY; return bounds; }; /** * Retrieves the non-global local bounds of the displayObjectContainer as a rectangle. The calculation takes all visible children into consideration. * * @method getLocalBounds * @return {Rectangle} The rectangular bounding area */ PIXI.DisplayObjectContainer.prototype.getLocalBounds = function() { var matrixCache = this.worldTransform; this.worldTransform = PIXI.identityMatrix; for (var i = 0; i < this.children.length; i++) { this.children[i].updateTransform(); } var bounds = this.getBounds(); this.worldTransform = matrixCache; for (i = 0; i < this.children.length; i++) { this.children[i].updateTransform(); } return bounds; }; /** * Sets the containers Stage reference. This is the Stage that this object, and all of its children, is connected to. * * @method setStageReference * @param stage {Stage} the stage that the container will have as its current stage reference */ PIXI.DisplayObjectContainer.prototype.setStageReference = function(stage) { this.stage = stage; for (var i=0; i < this.children.length; i++) { this.children[i].setStageReference(stage) } }; /** * Removes the current stage reference from the container and all of its children. * * @method removeStageReference */ PIXI.DisplayObjectContainer.prototype.removeStageReference = function() { for (var i = 0; i < this.children.length; i++) { this.children[i].removeStageReference(); } this.stage = null; }; /** * Renders the object using the WebGL renderer * * @method _renderWebGL * @param renderSession {RenderSession} * @private */ PIXI.DisplayObjectContainer.prototype._renderWebGL = function(renderSession) { if (!this.visible || this.alpha <= 0) return; if (this._cacheAsBitmap) { this._renderCachedSprite(renderSession); return; } var i; if (this._mask || this._filters) { // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { renderSession.spriteBatch.flush(); renderSession.filterManager.pushFilter(this._filterBlock); } if (this._mask) { renderSession.spriteBatch.stop(); renderSession.maskManager.pushMask(this.mask, renderSession); renderSession.spriteBatch.start(); } // simple render children! for (i = 0; i < this.children.length; i++) { this.children[i]._renderWebGL(renderSession); } renderSession.spriteBatch.stop(); if (this._mask) renderSession.maskManager.popMask(this._mask, renderSession); if (this._filters) renderSession.filterManager.popFilter(); renderSession.spriteBatch.start(); } else { // simple render children! for (i = 0; i < this.children.length; i++) { this.children[i]._renderWebGL(renderSession); } } }; /** * Renders the object using the Canvas renderer * * @method _renderCanvas * @param renderSession {RenderSession} * @private */ PIXI.DisplayObjectContainer.prototype._renderCanvas = function(renderSession) { if (this.visible === false || this.alpha === 0) return; if (this._cacheAsBitmap) { this._renderCachedSprite(renderSession); return; } if (this._mask) { renderSession.maskManager.pushMask(this._mask, renderSession); } for (var i = 0; i < this.children.length; i++) { this.children[i]._renderCanvas(renderSession); } if (this._mask) { renderSession.maskManager.popMask(renderSession); } }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * The Sprite object is the base for all textured objects that are rendered to the screen * * @class Sprite * @extends DisplayObjectContainer * @constructor * @param texture {Texture} The texture for this sprite * * A sprite can be created directly from an image like this : * var sprite = new PIXI.Sprite.fromImage('assets/image.png'); * yourStage.addChild(sprite); * then obviously don't forget to add it to the stage you have already created */ PIXI.Sprite = function(texture) { PIXI.DisplayObjectContainer.call(this); /** * The anchor sets the origin point of the texture. * The default is 0,0 this means the texture's origin is the top left * Setting than anchor to 0.5,0.5 means the textures origin is centered * Setting the anchor to 1,1 would mean the textures origin points will be the bottom right corner * * @property anchor * @type Point */ this.anchor = new PIXI.Point(); /** * The texture that the sprite is using * * @property texture * @type Texture */ this.texture = texture || PIXI.Texture.emptyTexture; /** * The width of the sprite (this is initially set by the texture) * * @property _width * @type Number * @private */ this._width = 0; /** * The height of the sprite (this is initially set by the texture) * * @property _height * @type Number * @private */ this._height = 0; /** * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. * * @property tint * @type Number * @default 0xFFFFFF */ this.tint = 0xFFFFFF; /** * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. * * @property cachedTint * @private * @type Number * @default -1 */ this.cachedTint = -1; /** * A canvas that contains the tinted version of the Sprite (in Canvas mode, WebGL doesn't populate this) * * @property tintedTexture * @type Canvas * @default null */ this.tintedTexture = null; /** * The blend mode to be applied to the sprite. Set to PIXI.blendModes.NORMAL to remove any blend mode. * * Warning: You cannot have a blend mode and a filter active on the same Sprite. Doing so will render the sprite invisible. * * @property blendMode * @type Number * @default PIXI.blendModes.NORMAL; */ this.blendMode = PIXI.blendModes.NORMAL; /** * The shader that will be used to render the texture to the stage. Set to null to remove a current shader. * * @property shader * @type AbstractFilter * @default null */ this.shader = null; if (this.texture.baseTexture.hasLoaded) { this.onTextureUpdate(); } this.renderable = true; }; // constructor PIXI.Sprite.prototype = Object.create(PIXI.DisplayObjectContainer.prototype); PIXI.Sprite.prototype.constructor = PIXI.Sprite; /** * The width of the sprite, setting this will actually modify the scale to achieve the value set * * @property width * @type Number */ Object.defineProperty(PIXI.Sprite.prototype, 'width', { get: function() { return this.scale.x * this.texture.frame.width; }, set: function(value) { this.scale.x = value / this.texture.frame.width; this._width = value; } }); /** * The height of the sprite, setting this will actually modify the scale to achieve the value set * * @property height * @type Number */ Object.defineProperty(PIXI.Sprite.prototype, 'height', { get: function() { return this.scale.y * this.texture.frame.height; }, set: function(value) { this.scale.y = value / this.texture.frame.height; this._height = value; } }); /** * Sets the texture of the sprite. Be warned that this doesn't remove or destroy the previous * texture this Sprite was using. * * @method setTexture * @param texture {Texture} The PIXI texture that is displayed by the sprite * @param [destroy=false] {boolean} Call Texture.destroy on the current texture before replacing it with the new one? */ PIXI.Sprite.prototype.setTexture = function(texture, destroyBase) { if (destroyBase !== undefined) { this.texture.baseTexture.destroy(); } // Over-ridden by loadTexture as needed this.texture.baseTexture.skipRender = false; this.texture = texture; this.texture.valid = true; this.cachedTint = -1; }; /** * When the texture is updated, this event will fire to update the scale and frame * * @method onTextureUpdate * @param event * @private */ PIXI.Sprite.prototype.onTextureUpdate = function() { // so if _width is 0 then width was not set.. if (this._width) this.scale.x = this._width / this.texture.frame.width; if (this._height) this.scale.y = this._height / this.texture.frame.height; }; /** * Returns the bounds of the Sprite as a rectangle. * The bounds calculation takes the worldTransform into account. * * It is important to note that the transform is not updated when you call this method. * So if this Sprite is the child of a Display Object which has had its transform * updated since the last render pass, those changes will not yet have been applied * to this Sprites worldTransform. If you need to ensure that all parent transforms * are factored into this getBounds operation then you should call `updateTransform` * on the root most object in this Sprites display list first. * * @method getBounds * @param matrix {Matrix} the transformation matrix of the sprite * @return {Rectangle} the framing rectangle */ PIXI.Sprite.prototype.getBounds = function(matrix) { var width = this.texture.frame.width; var height = this.texture.frame.height; var w0 = width * (1-this.anchor.x); var w1 = width * -this.anchor.x; var h0 = height * (1-this.anchor.y); var h1 = height * -this.anchor.y; var worldTransform = matrix || this.worldTransform; var a = worldTransform.a; var b = worldTransform.b; var c = worldTransform.c; var d = worldTransform.d; var tx = worldTransform.tx; var ty = worldTransform.ty; var maxX = -Infinity; var maxY = -Infinity; var minX = Infinity; var minY = Infinity; if (b === 0 && c === 0) { // scale may be negative! if (a < 0) { a *= -1; var temp = w0; w0 = -w1; w1 = -temp; } if (d < 0) { d *= -1; var temp = h0; h0 = -h1; h1 = -temp; } // this means there is no rotation going on right? RIGHT? // if thats the case then we can avoid checking the bound values! yay minX = a * w1 + tx; maxX = a * w0 + tx; minY = d * h1 + ty; maxY = d * h0 + ty; } else { var x1 = a * w1 + c * h1 + tx; var y1 = d * h1 + b * w1 + ty; var x2 = a * w0 + c * h1 + tx; var y2 = d * h1 + b * w0 + ty; var x3 = a * w0 + c * h0 + tx; var y3 = d * h0 + b * w0 + ty; var x4 = a * w1 + c * h0 + tx; var y4 = d * h0 + b * w1 + ty; minX = x1 < minX ? x1 : minX; minX = x2 < minX ? x2 : minX; minX = x3 < minX ? x3 : minX; minX = x4 < minX ? x4 : minX; minY = y1 < minY ? y1 : minY; minY = y2 < minY ? y2 : minY; minY = y3 < minY ? y3 : minY; minY = y4 < minY ? y4 : minY; maxX = x1 > maxX ? x1 : maxX; maxX = x2 > maxX ? x2 : maxX; maxX = x3 > maxX ? x3 : maxX; maxX = x4 > maxX ? x4 : maxX; maxY = y1 > maxY ? y1 : maxY; maxY = y2 > maxY ? y2 : maxY; maxY = y3 > maxY ? y3 : maxY; maxY = y4 > maxY ? y4 : maxY; } var bounds = this._bounds; bounds.x = minX; bounds.width = maxX - minX; bounds.y = minY; bounds.height = maxY - minY; // store a reference so that if this function gets called again in the render cycle we do not have to recalculate this._currentBounds = bounds; return bounds; }; /** * Renders the object using the WebGL renderer * * @method _renderWebGL * @param renderSession {RenderSession} * @param {Matrix} [matrix] - Optional matrix. If provided the Display Object will be rendered using this matrix, otherwise it will use its worldTransform. * @private */ PIXI.Sprite.prototype._renderWebGL = function(renderSession, matrix) { // if the sprite is not visible or the alpha is 0 then no need to render this element if (!this.visible || this.alpha <= 0 || !this.renderable) return; // They provided an alternative rendering matrix, so use it var wt = this.worldTransform; if (matrix) { wt = matrix; } // A quick check to see if this element has a mask or a filter. if (this._mask || this._filters) { var spriteBatch = renderSession.spriteBatch; // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { spriteBatch.flush(); renderSession.filterManager.pushFilter(this._filterBlock); } if (this._mask) { spriteBatch.stop(); renderSession.maskManager.pushMask(this.mask, renderSession); spriteBatch.start(); } // add this sprite to the batch spriteBatch.render(this); // now loop through the children and make sure they get rendered for (var i = 0; i < this.children.length; i++) { this.children[i]._renderWebGL(renderSession); } // time to stop the sprite batch as either a mask element or a filter draw will happen next spriteBatch.stop(); if (this._mask) renderSession.maskManager.popMask(this._mask, renderSession); if (this._filters) renderSession.filterManager.popFilter(); spriteBatch.start(); } else { renderSession.spriteBatch.render(this); // Render children! for (var i = 0; i < this.children.length; i++) { this.children[i]._renderWebGL(renderSession, wt); } } }; /** * Renders the object using the Canvas renderer * * @method _renderCanvas * @param renderSession {RenderSession} * @param {Matrix} [matrix] - Optional matrix. If provided the Display Object will be rendered using this matrix, otherwise it will use its worldTransform. * @private */ PIXI.Sprite.prototype._renderCanvas = function(renderSession, matrix) { // If the sprite is not visible or the alpha is 0 then no need to render this element if (!this.visible || this.alpha === 0 || !this.renderable || this.texture.crop.width <= 0 || this.texture.crop.height <= 0) { return; } var wt = this.worldTransform; // If they provided an alternative rendering matrix then use it if (matrix) { wt = matrix; } if (this.blendMode !== renderSession.currentBlendMode) { renderSession.currentBlendMode = this.blendMode; renderSession.context.globalCompositeOperation = PIXI.blendModesCanvas[renderSession.currentBlendMode]; } if (this._mask) { renderSession.maskManager.pushMask(this._mask, renderSession); } // Ignore null sources if (this.texture.valid) { var resolution = this.texture.baseTexture.resolution / renderSession.resolution; renderSession.context.globalAlpha = this.worldAlpha; // If smoothingEnabled is supported and we need to change the smoothing property for this texture if (renderSession.smoothProperty && renderSession.scaleMode !== this.texture.baseTexture.scaleMode) { renderSession.scaleMode = this.texture.baseTexture.scaleMode; renderSession.context[renderSession.smoothProperty] = (renderSession.scaleMode === PIXI.scaleModes.LINEAR); } // If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions var dx = (this.texture.trim) ? this.texture.trim.x - this.anchor.x * this.texture.trim.width : this.anchor.x * -this.texture.frame.width; var dy = (this.texture.trim) ? this.texture.trim.y - this.anchor.y * this.texture.trim.height : this.anchor.y * -this.texture.frame.height; var tx = (wt.tx * renderSession.resolution) + renderSession.shakeX; var ty = (wt.ty * renderSession.resolution) + renderSession.shakeY; // Allow for pixel rounding if (renderSession.roundPixels) { renderSession.context.setTransform(wt.a, wt.b, wt.c, wt.d, tx | 0, ty | 0); dx |= 0; dy |= 0; } else { renderSession.context.setTransform(wt.a, wt.b, wt.c, wt.d, tx, ty); } var cw = this.texture.crop.width; var ch = this.texture.crop.height; dx /= resolution; dy /= resolution; if (this.tint !== 0xFFFFFF) { if (this.texture.requiresReTint || this.cachedTint !== this.tint) { this.tintedTexture = PIXI.CanvasTinter.getTintedTexture(this, this.tint); this.cachedTint = this.tint; this.texture.requiresReTint = false; } renderSession.context.drawImage(this.tintedTexture, 0, 0, cw, ch, dx, dy, cw / resolution, ch / resolution); } else { var cx = this.texture.crop.x; var cy = this.texture.crop.y; renderSession.context.drawImage(this.texture.baseTexture.source, cx, cy, cw, ch, dx, dy, cw / resolution, ch / resolution); } } for (var i = 0; i < this.children.length; i++) { this.children[i]._renderCanvas(renderSession); } if (this._mask) { renderSession.maskManager.popMask(renderSession); } }; // some helper functions.. /** * * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId * The frame ids are created when a Texture packer file has been loaded * * @method fromFrame * @static * @param frameId {String} The frame Id of the texture in the cache * @return {Sprite} A new Sprite using a texture from the texture cache matching the frameId */ PIXI.Sprite.fromFrame = function(frameId) { var texture = PIXI.TextureCache[frameId]; if (!texture) throw new Error('The frameId "' + frameId + '" does not exist in the texture cache' + this); return new PIXI.Sprite(texture); }; /** * * Helper function that creates a sprite that will contain a texture based on an image url * If the image is not in the texture cache it will be loaded * * @method fromImage * @static * @param imageId {String} The image url of the texture * @return {Sprite} A new Sprite using a texture from the texture cache matching the image id */ PIXI.Sprite.fromImage = function(imageId, crossorigin, scaleMode) { var texture = PIXI.Texture.fromImage(imageId, crossorigin, scaleMode); return new PIXI.Sprite(texture); }; /** * @author Mat Groves http://matgroves.com/ */ /** * The SpriteBatch class is a really fast version of the DisplayObjectContainer * built solely for speed, so use when you need a lot of sprites or particles. * And it's extremely easy to use : var container = new PIXI.SpriteBatch(); stage.addChild(container); for(var i = 0; i < 100; i++) { var sprite = new PIXI.Sprite.fromImage("myImage.png"); container.addChild(sprite); } * And here you have a hundred sprites that will be renderer at the speed of light * * @class SpriteBatch * @constructor * @param texture {Texture} */ PIXI.SpriteBatch = function(texture) { PIXI.DisplayObjectContainer.call( this); this.textureThing = texture; this.ready = false; }; PIXI.SpriteBatch.prototype = Object.create(PIXI.DisplayObjectContainer.prototype); PIXI.SpriteBatch.prototype.constructor = PIXI.SpriteBatch; /* * Initialises the spriteBatch * * @method initWebGL * @param gl {WebGLContext} the current WebGL drawing context */ PIXI.SpriteBatch.prototype.initWebGL = function(gl) { // TODO only one needed for the whole engine really? this.fastSpriteBatch = new PIXI.WebGLFastSpriteBatch(gl); this.ready = true; }; /* * Updates the object transform for rendering * * @method updateTransform * @private */ PIXI.SpriteBatch.prototype.updateTransform = function() { // TODO don't need to! this.displayObjectUpdateTransform(); // PIXI.DisplayObjectContainer.prototype.updateTransform.call( this ); }; /** * Renders the object using the WebGL renderer * * @method _renderWebGL * @param renderSession {RenderSession} * @private */ PIXI.SpriteBatch.prototype._renderWebGL = function(renderSession) { if (!this.visible || this.alpha <= 0 || !this.children.length) return; if (!this.ready) { this.initWebGL(renderSession.gl); } if (this.fastSpriteBatch.gl !== renderSession.gl) { this.fastSpriteBatch.setContext(renderSession.gl); } renderSession.spriteBatch.stop(); renderSession.shaderManager.setShader(renderSession.shaderManager.fastShader); this.fastSpriteBatch.begin(this, renderSession); this.fastSpriteBatch.render(this); renderSession.spriteBatch.start(); }; /** * Renders the object using the Canvas renderer * * @method _renderCanvas * @param renderSession {RenderSession} * @private */ PIXI.SpriteBatch.prototype._renderCanvas = function(renderSession) { if (!this.visible || this.alpha <= 0 || !this.children.length) return; var context = renderSession.context; context.globalAlpha = this.worldAlpha; this.displayObjectUpdateTransform(); var transform = this.worldTransform; var isRotated = true; for (var i = 0; i < this.children.length; i++) { var child = this.children[i]; if (!child.visible) continue; var texture = child.texture; var frame = texture.frame; context.globalAlpha = this.worldAlpha * child.alpha; if (child.rotation % (Math.PI * 2) === 0) { if (isRotated) { context.setTransform(transform.a, transform.b, transform.c, transform.d, transform.tx, transform.ty); isRotated = false; } // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call context.drawImage(texture.baseTexture.source, frame.x, frame.y, frame.width, frame.height, ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5 + renderSession.shakeX) | 0, ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5 + renderSession.shakeY) | 0, frame.width * child.scale.x, frame.height * child.scale.y); } else { if (!isRotated) isRotated = true; child.displayObjectUpdateTransform(); var childTransform = child.worldTransform; var tx = (childTransform.tx * renderSession.resolution) + renderSession.shakeX; var ty = (childTransform.ty * renderSession.resolution) + renderSession.shakeY; // allow for trimming if (renderSession.roundPixels) { context.setTransform(childTransform.a, childTransform.b, childTransform.c, childTransform.d, tx | 0, ty | 0); } else { context.setTransform(childTransform.a, childTransform.b, childTransform.c, childTransform.d, tx, ty); } context.drawImage(texture.baseTexture.source, frame.x, frame.y, frame.width, frame.height, ((child.anchor.x) * (-frame.width) + 0.5) | 0, ((child.anchor.y) * (-frame.height) + 0.5) | 0, frame.width, frame.height); } } }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * Converts a hex color number to an [R, G, B] array * * @method hex2rgb * @param hex {Number} */ PIXI.hex2rgb = function(hex) { return [(hex >> 16 & 0xFF) / 255, ( hex >> 8 & 0xFF) / 255, (hex & 0xFF)/ 255]; }; /** * Converts a color as an [R, G, B] array to a hex number * * @method rgb2hex * @param rgb {Array} */ PIXI.rgb2hex = function(rgb) { return ((rgb[0]*255 << 16) + (rgb[1]*255 << 8) + rgb[2]*255); }; /** * Checks whether the Canvas BlendModes are supported by the current browser for drawImage * * @method canUseNewCanvasBlendModes * @return {Boolean} whether they are supported */ PIXI.canUseNewCanvasBlendModes = function() { if (document === undefined) return false; var pngHead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAABAQMAAADD8p2OAAAAA1BMVEX/'; var pngEnd = 'AAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=='; var magenta = new Image(); magenta.src = pngHead + 'AP804Oa6' + pngEnd; var yellow = new Image(); yellow.src = pngHead + '/wCKxvRF' + pngEnd; var canvas = PIXI.CanvasPool.create(this, 6, 1); var context = canvas.getContext('2d'); context.globalCompositeOperation = 'multiply'; context.drawImage(magenta, 0, 0); context.drawImage(yellow, 2, 0); if (!context.getImageData(2,0,1,1)) { return false; } var data = context.getImageData(2,0,1,1).data; PIXI.CanvasPool.remove(this); return (data[0] === 255 && data[1] === 0 && data[2] === 0); }; /** * Given a number, this function returns the closest number that is a power of two * this function is taken from Starling Framework as its pretty neat ;) * * @method getNextPowerOfTwo * @param number {Number} * @return {Number} the closest number that is a power of two */ PIXI.getNextPowerOfTwo = function(number) { if (number > 0 && (number & (number - 1)) === 0) // see: http://goo.gl/D9kPj return number; else { var result = 1; while (result < number) result <<= 1; return result; } }; /** * checks if the given width and height make a power of two texture * @method isPowerOfTwo * @param width {Number} * @param height {Number} * @return {Boolean} */ PIXI.isPowerOfTwo = function(width, height) { return (width > 0 && (width & (width - 1)) === 0 && height > 0 && (height & (height - 1)) === 0); }; /** * @author Richard Davey * @copyright 2016 Photon Storm Ltd. * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} */ /** * The CanvasPool is a global static object that allows Pixi and Phaser to pool canvas DOM elements. * * @class CanvasPool * @static */ PIXI.CanvasPool = { /** * Creates a new Canvas DOM element, or pulls one from the pool if free. * * @method create * @static * @param parent {any} The parent of the canvas element. * @param width {number} The width of the canvas element. * @param height {number} The height of the canvas element. * @return {HTMLCanvasElement} The canvas element. */ create: function (parent, width, height) { var idx = PIXI.CanvasPool.getFirst(); var canvas; if (idx === -1) { var container = { parent: parent, canvas: document.createElement('canvas') } PIXI.CanvasPool.pool.push(container); canvas = container.canvas; } else { PIXI.CanvasPool.pool[idx].parent = parent; canvas = PIXI.CanvasPool.pool[idx].canvas; } if (width !== undefined) { canvas.width = width; canvas.height = height; } return canvas; }, /** * Gets the first free canvas index from the pool. * * @method getFirst * @static * @return {number} */ getFirst: function () { var pool = PIXI.CanvasPool.pool; for (var i = 0; i < pool.length; i++) { if (pool[i].parent === null) { return i; } } return -1; }, /** * Removes the parent from a canvas element from the pool, freeing it up for re-use. * * @method remove * @param parent {any} The parent of the canvas element. * @static */ remove: function (parent) { var pool = PIXI.CanvasPool.pool; for (var i = 0; i < pool.length; i++) { if (pool[i].parent === parent) { pool[i].parent = null; } } }, /** * Removes the parent from a canvas element from the pool, freeing it up for re-use. * * @method removeByCanvas * @param canvas {HTMLCanvasElement} The canvas element to remove * @static */ removeByCanvas: function (canvas) { var pool = PIXI.CanvasPool.pool; for (var i = 0; i < pool.length; i++) { if (pool[i].canvas === canvas) { pool[i].parent = null; } } }, /** * Gets the total number of used canvas elements in the pool. * * @method getTotal * @static * @return {number} The number of in-use (parented) canvas elements in the pool. */ getTotal: function () { var pool = PIXI.CanvasPool.pool; var c = 0; for (var i = 0; i < pool.length; i++) { if (pool[i].parent !== null) { c++; } } return c; }, /** * Gets the total number of free canvas elements in the pool. * * @method getFree * @static * @return {number} The number of free (un-parented) canvas elements in the pool. */ getFree: function () { var pool = PIXI.CanvasPool.pool; var c = 0; for (var i = 0; i < pool.length; i++) { if (pool[i].parent === null) { c++; } } return c; } }; /** * The pool into which the canvas dom elements are placed. * * @property pool * @type Array * @static */ PIXI.CanvasPool.pool = []; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * @method initDefaultShaders * @static * @private */ PIXI.initDefaultShaders = function() { }; /** * @method CompileVertexShader * @static * @param gl {WebGLContext} the current WebGL drawing context * @param shaderSrc {Array} * @return {Any} */ PIXI.CompileVertexShader = function(gl, shaderSrc) { return PIXI._CompileShader(gl, shaderSrc, gl.VERTEX_SHADER); }; /** * @method CompileFragmentShader * @static * @param gl {WebGLContext} the current WebGL drawing context * @param shaderSrc {Array} * @return {Any} */ PIXI.CompileFragmentShader = function(gl, shaderSrc) { return PIXI._CompileShader(gl, shaderSrc, gl.FRAGMENT_SHADER); }; /** * @method _CompileShader * @static * @private * @param gl {WebGLContext} the current WebGL drawing context * @param shaderSrc {Array} * @param shaderType {Number} * @return {Any} */ PIXI._CompileShader = function(gl, shaderSrc, shaderType) { var src = shaderSrc; if (Array.isArray(shaderSrc)) { src = shaderSrc.join("\n"); } var shader = gl.createShader(shaderType); gl.shaderSource(shader, src); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { window.console.log(gl.getShaderInfoLog(shader)); return null; } return shader; }; /** * @method compileProgram * @static * @param gl {WebGLContext} the current WebGL drawing context * @param vertexSrc {Array} * @param fragmentSrc {Array} * @return {Any} */ PIXI.compileProgram = function(gl, vertexSrc, fragmentSrc) { var fragmentShader = PIXI.CompileFragmentShader(gl, fragmentSrc); var vertexShader = PIXI.CompileVertexShader(gl, vertexSrc); var shaderProgram = gl.createProgram(); gl.attachShader(shaderProgram, vertexShader); gl.attachShader(shaderProgram, fragmentShader); gl.linkProgram(shaderProgram); if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { window.console.log(gl.getProgramInfoLog(shaderProgram)); window.console.log("Could not initialise shaders"); } return shaderProgram; }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 * @author Richard Davey http://www.photonstorm.com @photonstorm */ /** * @class PixiShader * @constructor * @param gl {WebGLContext} the current WebGL drawing context */ PIXI.PixiShader = function(gl) { /** * @property _UID * @type Number * @private */ this._UID = PIXI._UID++; /** * @property gl * @type WebGLContext */ this.gl = gl; /** * The WebGL program. * @property program * @type Any */ this.program = null; /** * The fragment shader. * @property fragmentSrc * @type Array */ this.fragmentSrc = [ 'precision lowp float;', 'varying vec2 vTextureCoord;', 'varying vec4 vColor;', 'uniform sampler2D uSampler;', 'void main(void) {', ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', '}' ]; /** * A local texture counter for multi-texture shaders. * @property textureCount * @type Number */ this.textureCount = 0; /** * A local flag * @property firstRun * @type Boolean * @private */ this.firstRun = true; /** * A dirty flag * @property dirty * @type Boolean */ this.dirty = true; /** * Uniform attributes cache. * @property attributes * @type Array * @private */ this.attributes = []; this.init(); }; PIXI.PixiShader.prototype.constructor = PIXI.PixiShader; /** * Initialises the shader. * * @method init */ PIXI.PixiShader.prototype.init = function() { var gl = this.gl; var program = PIXI.compileProgram(gl, this.vertexSrc || PIXI.PixiShader.defaultVertexSrc, this.fragmentSrc); gl.useProgram(program); // get and store the uniforms for the shader this.uSampler = gl.getUniformLocation(program, 'uSampler'); this.projectionVector = gl.getUniformLocation(program, 'projectionVector'); this.offsetVector = gl.getUniformLocation(program, 'offsetVector'); this.dimensions = gl.getUniformLocation(program, 'dimensions'); // get and store the attributes this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition'); this.aTextureCoord = gl.getAttribLocation(program, 'aTextureCoord'); this.colorAttribute = gl.getAttribLocation(program, 'aColor'); // Begin worst hack eva // // WHY??? ONLY on my chrome pixel the line above returns -1 when using filters? // maybe its something to do with the current state of the gl context. // I'm convinced this is a bug in the chrome browser as there is NO reason why this should be returning -1 especially as it only manifests on my chrome pixel // If theres any webGL people that know why could happen please help :) if(this.colorAttribute === -1) { this.colorAttribute = 2; } this.attributes = [this.aVertexPosition, this.aTextureCoord, this.colorAttribute]; // End worst hack eva // // add those custom shaders! for (var key in this.uniforms) { // get the uniform locations.. this.uniforms[key].uniformLocation = gl.getUniformLocation(program, key); } this.initUniforms(); this.program = program; }; /** * Initialises the shader uniform values. * * Uniforms are specified in the GLSL_ES Specification: http://www.khronos.org/registry/webgl/specs/latest/1.0/ * http://www.khronos.org/registry/gles/specs/2.0/GLSL_ES_Specification_1.0.17.pdf * * @method initUniforms */ PIXI.PixiShader.prototype.initUniforms = function() { this.textureCount = 1; var gl = this.gl; var uniform; for (var key in this.uniforms) { uniform = this.uniforms[key]; var type = uniform.type; if (type === 'sampler2D') { uniform._init = false; if (uniform.value !== null) { this.initSampler2D(uniform); } } else if (type === 'mat2' || type === 'mat3' || type === 'mat4') { // These require special handling uniform.glMatrix = true; uniform.glValueLength = 1; if (type === 'mat2') { uniform.glFunc = gl.uniformMatrix2fv; } else if (type === 'mat3') { uniform.glFunc = gl.uniformMatrix3fv; } else if (type === 'mat4') { uniform.glFunc = gl.uniformMatrix4fv; } } else { // GL function reference uniform.glFunc = gl['uniform' + type]; if (type === '2f' || type === '2i') { uniform.glValueLength = 2; } else if (type === '3f' || type === '3i') { uniform.glValueLength = 3; } else if (type === '4f' || type === '4i') { uniform.glValueLength = 4; } else { uniform.glValueLength = 1; } } } }; /** * Initialises a Sampler2D uniform (which may only be available later on after initUniforms once the texture has loaded) * * @method initSampler2D */ PIXI.PixiShader.prototype.initSampler2D = function(uniform) { if (!uniform.value || !uniform.value.baseTexture || !uniform.value.baseTexture.hasLoaded) { return; } var gl = this.gl; gl.activeTexture(gl['TEXTURE' + this.textureCount]); gl.bindTexture(gl.TEXTURE_2D, uniform.value.baseTexture._glTextures[gl.id]); // Extended texture data if (uniform.textureData) { var data = uniform.textureData; // GLTexture = mag linear, min linear_mipmap_linear, wrap repeat + gl.generateMipmap(gl.TEXTURE_2D); // GLTextureLinear = mag/min linear, wrap clamp // GLTextureNearestRepeat = mag/min NEAREST, wrap repeat // GLTextureNearest = mag/min nearest, wrap clamp // AudioTexture = whatever + luminance + width 512, height 2, border 0 // KeyTexture = whatever + luminance + width 256, height 2, border 0 // magFilter can be: gl.LINEAR, gl.LINEAR_MIPMAP_LINEAR or gl.NEAREST // wrapS/T can be: gl.CLAMP_TO_EDGE or gl.REPEAT var magFilter = (data.magFilter) ? data.magFilter : gl.LINEAR; var minFilter = (data.minFilter) ? data.minFilter : gl.LINEAR; var wrapS = (data.wrapS) ? data.wrapS : gl.CLAMP_TO_EDGE; var wrapT = (data.wrapT) ? data.wrapT : gl.CLAMP_TO_EDGE; var format = (data.luminance) ? gl.LUMINANCE : gl.RGBA; if (data.repeat) { wrapS = gl.REPEAT; wrapT = gl.REPEAT; } gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, !!data.flipY); if (data.width) { var width = (data.width) ? data.width : 512; var height = (data.height) ? data.height : 2; var border = (data.border) ? data.border : 0; // void texImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, ArrayBufferView? pixels); gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, border, format, gl.UNSIGNED_BYTE, null); } else { // void texImage2D(GLenum target, GLint level, GLenum internalformat, GLenum format, GLenum type, ImageData? pixels); gl.texImage2D(gl.TEXTURE_2D, 0, format, gl.RGBA, gl.UNSIGNED_BYTE, uniform.value.baseTexture.source); } gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, magFilter); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, minFilter); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrapS); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrapT); } gl.uniform1i(uniform.uniformLocation, this.textureCount); uniform._init = true; this.textureCount++; }; /** * Updates the shader uniform values. * * @method syncUniforms */ PIXI.PixiShader.prototype.syncUniforms = function() { this.textureCount = 1; var uniform; var gl = this.gl; // This would probably be faster in an array and it would guarantee key order for (var key in this.uniforms) { uniform = this.uniforms[key]; if (uniform.glValueLength === 1) { if (uniform.glMatrix === true) { uniform.glFunc.call(gl, uniform.uniformLocation, uniform.transpose, uniform.value); } else { uniform.glFunc.call(gl, uniform.uniformLocation, uniform.value); } } else if (uniform.glValueLength === 2) { uniform.glFunc.call(gl, uniform.uniformLocation, uniform.value.x, uniform.value.y); } else if (uniform.glValueLength === 3) { uniform.glFunc.call(gl, uniform.uniformLocation, uniform.value.x, uniform.value.y, uniform.value.z); } else if (uniform.glValueLength === 4) { uniform.glFunc.call(gl, uniform.uniformLocation, uniform.value.x, uniform.value.y, uniform.value.z, uniform.value.w); } else if (uniform.type === 'sampler2D') { if (uniform._init) { gl.activeTexture(gl['TEXTURE' + this.textureCount]); if(uniform.value.baseTexture._dirty[gl.id]) { PIXI.instances[gl.id].updateTexture(uniform.value.baseTexture); } else { // bind the current texture gl.bindTexture(gl.TEXTURE_2D, uniform.value.baseTexture._glTextures[gl.id]); } // gl.bindTexture(gl.TEXTURE_2D, uniform.value.baseTexture._glTextures[gl.id] || PIXI.createWebGLTexture( uniform.value.baseTexture, gl)); gl.uniform1i(uniform.uniformLocation, this.textureCount); this.textureCount++; } else { this.initSampler2D(uniform); } } } }; /** * Destroys the shader. * * @method destroy */ PIXI.PixiShader.prototype.destroy = function() { this.gl.deleteProgram( this.program ); this.uniforms = null; this.gl = null; this.attributes = null; }; /** * The Default Vertex shader source. * * @property defaultVertexSrc * @type String */ PIXI.PixiShader.defaultVertexSrc = [ 'attribute vec2 aVertexPosition;', 'attribute vec2 aTextureCoord;', 'attribute vec4 aColor;', 'uniform vec2 projectionVector;', 'uniform vec2 offsetVector;', 'varying vec2 vTextureCoord;', 'varying vec4 vColor;', 'const vec2 center = vec2(-1.0, 1.0);', 'void main(void) {', ' gl_Position = vec4( ((aVertexPosition + offsetVector) / projectionVector) + center , 0.0, 1.0);', ' vTextureCoord = aTextureCoord;', ' vColor = vec4(aColor.rgb * aColor.a, aColor.a);', '}' ]; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * @class PixiFastShader * @constructor * @param gl {WebGLContext} the current WebGL drawing context */ PIXI.PixiFastShader = function(gl) { /** * @property _UID * @type Number * @private */ this._UID = PIXI._UID++; /** * @property gl * @type WebGLContext */ this.gl = gl; /** * The WebGL program. * @property program * @type Any */ this.program = null; /** * The fragment shader. * @property fragmentSrc * @type Array */ this.fragmentSrc = [ 'precision lowp float;', 'varying vec2 vTextureCoord;', 'varying float vColor;', 'uniform sampler2D uSampler;', 'void main(void) {', ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', '}' ]; /** * The vertex shader. * @property vertexSrc * @type Array */ this.vertexSrc = [ 'attribute vec2 aVertexPosition;', 'attribute vec2 aPositionCoord;', 'attribute vec2 aScale;', 'attribute float aRotation;', 'attribute vec2 aTextureCoord;', 'attribute float aColor;', 'uniform vec2 projectionVector;', 'uniform vec2 offsetVector;', 'uniform mat3 uMatrix;', 'varying vec2 vTextureCoord;', 'varying float vColor;', 'const vec2 center = vec2(-1.0, 1.0);', 'void main(void) {', ' vec2 v;', ' vec2 sv = aVertexPosition * aScale;', ' v.x = (sv.x) * cos(aRotation) - (sv.y) * sin(aRotation);', ' v.y = (sv.x) * sin(aRotation) + (sv.y) * cos(aRotation);', ' v = ( uMatrix * vec3(v + aPositionCoord , 1.0) ).xy ;', ' gl_Position = vec4( ( v / projectionVector) + center , 0.0, 1.0);', ' vTextureCoord = aTextureCoord;', // ' vec3 color = mod(vec3(aColor.y/65536.0, aColor.y/256.0, aColor.y), 256.0) / 256.0;', ' vColor = aColor;', '}' ]; /** * A local texture counter for multi-texture shaders. * @property textureCount * @type Number */ this.textureCount = 0; this.init(); }; PIXI.PixiFastShader.prototype.constructor = PIXI.PixiFastShader; /** * Initialises the shader. * * @method init */ PIXI.PixiFastShader.prototype.init = function() { var gl = this.gl; var program = PIXI.compileProgram(gl, this.vertexSrc, this.fragmentSrc); gl.useProgram(program); // get and store the uniforms for the shader this.uSampler = gl.getUniformLocation(program, 'uSampler'); this.projectionVector = gl.getUniformLocation(program, 'projectionVector'); this.offsetVector = gl.getUniformLocation(program, 'offsetVector'); this.dimensions = gl.getUniformLocation(program, 'dimensions'); this.uMatrix = gl.getUniformLocation(program, 'uMatrix'); // get and store the attributes this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition'); this.aPositionCoord = gl.getAttribLocation(program, 'aPositionCoord'); this.aScale = gl.getAttribLocation(program, 'aScale'); this.aRotation = gl.getAttribLocation(program, 'aRotation'); this.aTextureCoord = gl.getAttribLocation(program, 'aTextureCoord'); this.colorAttribute = gl.getAttribLocation(program, 'aColor'); // Begin worst hack eva // // WHY??? ONLY on my chrome pixel the line above returns -1 when using filters? // maybe its somthing to do with the current state of the gl context. // Im convinced this is a bug in the chrome browser as there is NO reason why this should be returning -1 especially as it only manifests on my chrome pixel // If theres any webGL people that know why could happen please help :) if(this.colorAttribute === -1) { this.colorAttribute = 2; } this.attributes = [this.aVertexPosition, this.aPositionCoord, this.aScale, this.aRotation, this.aTextureCoord, this.colorAttribute]; // End worst hack eva // this.program = program; }; /** * Destroys the shader. * * @method destroy */ PIXI.PixiFastShader.prototype.destroy = function() { this.gl.deleteProgram( this.program ); this.uniforms = null; this.gl = null; this.attributes = null; }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * @class StripShader * @constructor * @param gl {WebGLContext} the current WebGL drawing context */ PIXI.StripShader = function(gl) { /** * @property _UID * @type Number * @private */ this._UID = PIXI._UID++; /** * @property gl * @type WebGLContext */ this.gl = gl; /** * The WebGL program. * @property program * @type Any */ this.program = null; /** * The fragment shader. * @property fragmentSrc * @type Array */ this.fragmentSrc = [ 'precision mediump float;', 'varying vec2 vTextureCoord;', // 'varying float vColor;', 'uniform float alpha;', 'uniform sampler2D uSampler;', 'void main(void) {', ' gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * alpha;', // ' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);',//gl_FragColor * alpha;', '}' ]; /** * The vertex shader. * @property vertexSrc * @type Array */ this.vertexSrc = [ 'attribute vec2 aVertexPosition;', 'attribute vec2 aTextureCoord;', 'uniform mat3 translationMatrix;', 'uniform vec2 projectionVector;', 'uniform vec2 offsetVector;', // 'uniform float alpha;', // 'uniform vec3 tint;', 'varying vec2 vTextureCoord;', // 'varying vec4 vColor;', 'void main(void) {', ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', ' v -= offsetVector.xyx;', ' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);', ' vTextureCoord = aTextureCoord;', // ' vColor = aColor * vec4(tint * alpha, alpha);', '}' ]; this.init(); }; PIXI.StripShader.prototype.constructor = PIXI.StripShader; /** * Initialises the shader. * * @method init */ PIXI.StripShader.prototype.init = function() { var gl = this.gl; var program = PIXI.compileProgram(gl, this.vertexSrc, this.fragmentSrc); gl.useProgram(program); // get and store the uniforms for the shader this.uSampler = gl.getUniformLocation(program, 'uSampler'); this.projectionVector = gl.getUniformLocation(program, 'projectionVector'); this.offsetVector = gl.getUniformLocation(program, 'offsetVector'); this.colorAttribute = gl.getAttribLocation(program, 'aColor'); //this.dimensions = gl.getUniformLocation(this.program, 'dimensions'); // get and store the attributes this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition'); this.aTextureCoord = gl.getAttribLocation(program, 'aTextureCoord'); this.attributes = [this.aVertexPosition, this.aTextureCoord]; this.translationMatrix = gl.getUniformLocation(program, 'translationMatrix'); this.alpha = gl.getUniformLocation(program, 'alpha'); this.program = program; }; /** * Destroys the shader. * * @method destroy */ PIXI.StripShader.prototype.destroy = function() { this.gl.deleteProgram( this.program ); this.uniforms = null; this.gl = null; this.attribute = null; }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * @class PrimitiveShader * @constructor * @param gl {WebGLContext} the current WebGL drawing context */ PIXI.PrimitiveShader = function(gl) { /** * @property _UID * @type Number * @private */ this._UID = PIXI._UID++; /** * @property gl * @type WebGLContext */ this.gl = gl; /** * The WebGL program. * @property program * @type Any */ this.program = null; /** * The fragment shader. * @property fragmentSrc * @type Array */ this.fragmentSrc = [ 'precision mediump float;', 'varying vec4 vColor;', 'void main(void) {', ' gl_FragColor = vColor;', '}' ]; /** * The vertex shader. * @property vertexSrc * @type Array */ this.vertexSrc = [ 'attribute vec2 aVertexPosition;', 'attribute vec4 aColor;', 'uniform mat3 translationMatrix;', 'uniform vec2 projectionVector;', 'uniform vec2 offsetVector;', 'uniform float alpha;', 'uniform float flipY;', 'uniform vec3 tint;', 'varying vec4 vColor;', 'void main(void) {', ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', ' v -= offsetVector.xyx;', ' gl_Position = vec4( v.x / projectionVector.x -1.0, (v.y / projectionVector.y * -flipY) + flipY , 0.0, 1.0);', ' vColor = aColor * vec4(tint * alpha, alpha);', '}' ]; this.init(); }; PIXI.PrimitiveShader.prototype.constructor = PIXI.PrimitiveShader; /** * Initialises the shader. * * @method init */ PIXI.PrimitiveShader.prototype.init = function() { var gl = this.gl; var program = PIXI.compileProgram(gl, this.vertexSrc, this.fragmentSrc); gl.useProgram(program); // get and store the uniforms for the shader this.projectionVector = gl.getUniformLocation(program, 'projectionVector'); this.offsetVector = gl.getUniformLocation(program, 'offsetVector'); this.tintColor = gl.getUniformLocation(program, 'tint'); this.flipY = gl.getUniformLocation(program, 'flipY'); // get and store the attributes this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition'); this.colorAttribute = gl.getAttribLocation(program, 'aColor'); this.attributes = [this.aVertexPosition, this.colorAttribute]; this.translationMatrix = gl.getUniformLocation(program, 'translationMatrix'); this.alpha = gl.getUniformLocation(program, 'alpha'); this.program = program; }; /** * Destroys the shader. * * @method destroy */ PIXI.PrimitiveShader.prototype.destroy = function() { this.gl.deleteProgram( this.program ); this.uniforms = null; this.gl = null; this.attributes = null; }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * @class ComplexPrimitiveShader * @constructor * @param gl {WebGLContext} the current WebGL drawing context */ PIXI.ComplexPrimitiveShader = function(gl) { /** * @property _UID * @type Number * @private */ this._UID = PIXI._UID++; /** * @property gl * @type WebGLContext */ this.gl = gl; /** * The WebGL program. * @property program * @type Any */ this.program = null; /** * The fragment shader. * @property fragmentSrc * @type Array */ this.fragmentSrc = [ 'precision mediump float;', 'varying vec4 vColor;', 'void main(void) {', ' gl_FragColor = vColor;', '}' ]; /** * The vertex shader. * @property vertexSrc * @type Array */ this.vertexSrc = [ 'attribute vec2 aVertexPosition;', //'attribute vec4 aColor;', 'uniform mat3 translationMatrix;', 'uniform vec2 projectionVector;', 'uniform vec2 offsetVector;', 'uniform vec3 tint;', 'uniform float alpha;', 'uniform vec3 color;', 'uniform float flipY;', 'varying vec4 vColor;', 'void main(void) {', ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', ' v -= offsetVector.xyx;', ' gl_Position = vec4( v.x / projectionVector.x -1.0, (v.y / projectionVector.y * -flipY) + flipY , 0.0, 1.0);', ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', '}' ]; this.init(); }; PIXI.ComplexPrimitiveShader.prototype.constructor = PIXI.ComplexPrimitiveShader; /** * Initialises the shader. * * @method init */ PIXI.ComplexPrimitiveShader.prototype.init = function() { var gl = this.gl; var program = PIXI.compileProgram(gl, this.vertexSrc, this.fragmentSrc); gl.useProgram(program); // get and store the uniforms for the shader this.projectionVector = gl.getUniformLocation(program, 'projectionVector'); this.offsetVector = gl.getUniformLocation(program, 'offsetVector'); this.tintColor = gl.getUniformLocation(program, 'tint'); this.color = gl.getUniformLocation(program, 'color'); this.flipY = gl.getUniformLocation(program, 'flipY'); // get and store the attributes this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition'); // this.colorAttribute = gl.getAttribLocation(program, 'aColor'); this.attributes = [this.aVertexPosition, this.colorAttribute]; this.translationMatrix = gl.getUniformLocation(program, 'translationMatrix'); this.alpha = gl.getUniformLocation(program, 'alpha'); this.program = program; }; /** * Destroys the shader. * * @method destroy */ PIXI.ComplexPrimitiveShader.prototype.destroy = function() { this.gl.deleteProgram( this.program ); this.uniforms = null; this.gl = null; this.attribute = null; }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ PIXI.glContexts = []; // this is where we store the webGL contexts for easy access. PIXI.instances = []; /** * The WebGLRenderer draws the stage and all its content onto a webGL enabled canvas. This renderer * should be used for browsers that support webGL. This Render works by automatically managing webGLBatchs. * So no need for Sprite Batches or Sprite Clouds. * Don't forget to add the view to your DOM or you will not see anything :) * * @class WebGLRenderer * @constructor * @param game {Phaser.Game} A reference to the Phaser Game instance */ PIXI.WebGLRenderer = function(game) { /** * @property {Phaser.Game} game - A reference to the Phaser Game instance. */ this.game = game; if (!PIXI.defaultRenderer) { PIXI.defaultRenderer = this; } /** * @property type * @type Number */ this.type = PIXI.WEBGL_RENDERER; /** * The resolution of the renderer * * @property resolution * @type Number * @default 1 */ this.resolution = game.resolution; /** * Whether the render view is transparent * * @property transparent * @type Boolean */ this.transparent = game.transparent; /** * Whether the render view should be resized automatically * * @property autoResize * @type Boolean */ this.autoResize = false; /** * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. * * @property preserveDrawingBuffer * @type Boolean */ this.preserveDrawingBuffer = game.preserveDrawingBuffer; /** * This sets if the WebGLRenderer will clear the context texture or not before the new render pass. If true: * If the Stage is NOT transparent, Pixi will clear to alpha (0, 0, 0, 0). * If the Stage is transparent, Pixi will clear to the target Stage's background color. * Disable this by setting this to false. For example: if your game has a canvas filling background image, you often don't need this set. * * @property clearBeforeRender * @type Boolean * @default */ this.clearBeforeRender = game.clearBeforeRender; /** * The width of the canvas view * * @property width * @type Number */ this.width = game.width; /** * The height of the canvas view * * @property height * @type Number */ this.height = game.height; /** * The canvas element that everything is drawn to * * @property view * @type HTMLCanvasElement */ this.view = game.canvas; /** * @property _contextOptions * @type Object * @private */ this._contextOptions = { alpha: this.transparent, antialias: game.antialias, premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', stencil: true, preserveDrawingBuffer: this.preserveDrawingBuffer }; /** * @property projection * @type Point */ this.projection = new PIXI.Point(); /** * @property offset * @type Point */ this.offset = new PIXI.Point(); // time to create the render managers! each one focuses on managing a state in webGL /** * Deals with managing the shader programs and their attribs * @property shaderManager * @type WebGLShaderManager */ this.shaderManager = new PIXI.WebGLShaderManager(); /** * Manages the rendering of sprites * @property spriteBatch * @type WebGLSpriteBatch */ this.spriteBatch = new PIXI.WebGLSpriteBatch(); /** * Manages the masks using the stencil buffer * @property maskManager * @type WebGLMaskManager */ this.maskManager = new PIXI.WebGLMaskManager(); /** * Manages the filters * @property filterManager * @type WebGLFilterManager */ this.filterManager = new PIXI.WebGLFilterManager(); /** * Manages the stencil buffer * @property stencilManager * @type WebGLStencilManager */ this.stencilManager = new PIXI.WebGLStencilManager(); /** * Manages the blendModes * @property blendModeManager * @type WebGLBlendModeManager */ this.blendModeManager = new PIXI.WebGLBlendModeManager(); /** * @property renderSession * @type Object */ this.renderSession = {}; // Needed? this.renderSession.game = this.game; this.renderSession.gl = this.gl; this.renderSession.drawCount = 0; this.renderSession.shaderManager = this.shaderManager; this.renderSession.maskManager = this.maskManager; this.renderSession.filterManager = this.filterManager; this.renderSession.blendModeManager = this.blendModeManager; this.renderSession.spriteBatch = this.spriteBatch; this.renderSession.stencilManager = this.stencilManager; this.renderSession.renderer = this; this.renderSession.resolution = this.resolution; // time init the context.. this.initContext(); // map some webGL blend modes.. this.mapBlendModes(); }; // constructor PIXI.WebGLRenderer.prototype.constructor = PIXI.WebGLRenderer; /** * @method initContext */ PIXI.WebGLRenderer.prototype.initContext = function() { var gl = this.view.getContext('webgl', this._contextOptions) || this.view.getContext('experimental-webgl', this._contextOptions); this.gl = gl; if (!gl) { // fail, not able to get a context throw new Error('This browser does not support webGL. Try using the canvas renderer'); } this.glContextId = gl.id = PIXI.WebGLRenderer.glContextId++; PIXI.glContexts[this.glContextId] = gl; PIXI.instances[this.glContextId] = this; // set up the default pixi settings.. gl.disable(gl.DEPTH_TEST); gl.disable(gl.CULL_FACE); gl.enable(gl.BLEND); // need to set the context for all the managers... this.shaderManager.setContext(gl); this.spriteBatch.setContext(gl); this.maskManager.setContext(gl); this.filterManager.setContext(gl); this.blendModeManager.setContext(gl); this.stencilManager.setContext(gl); this.renderSession.gl = this.gl; // now resize and we are good to go! this.resize(this.width, this.height); }; /** * Renders the stage to its webGL view * * @method render * @param stage {Stage} the Stage element to be rendered */ PIXI.WebGLRenderer.prototype.render = function(stage) { // no point rendering if our context has been blown up! if (this.contextLost) { return; } var gl = this.gl; // -- Does this need to be set every frame? -- // gl.viewport(0, 0, this.width, this.height); // make sure we are bound to the main frame buffer gl.bindFramebuffer(gl.FRAMEBUFFER, null); if (this.game.clearBeforeRender) { gl.clearColor(stage._bgColor.r, stage._bgColor.g, stage._bgColor.b, stage._bgColor.a); gl.clear(gl.COLOR_BUFFER_BIT); } this.offset.x = this.game.camera._shake.x; this.offset.y = this.game.camera._shake.y; this.renderDisplayObject(stage, this.projection); }; /** * Renders a Display Object. * * @method renderDisplayObject * @param displayObject {DisplayObject} The DisplayObject to render * @param projection {Point} The projection * @param buffer {Array} a standard WebGL buffer */ PIXI.WebGLRenderer.prototype.renderDisplayObject = function(displayObject, projection, buffer, matrix) { this.renderSession.blendModeManager.setBlendMode(PIXI.blendModes.NORMAL); // reset the render session data.. this.renderSession.drawCount = 0; // make sure to flip the Y if using a render texture.. this.renderSession.flipY = buffer ? -1 : 1; // set the default projection this.renderSession.projection = projection; //set the default offset this.renderSession.offset = this.offset; // start the sprite batch this.spriteBatch.begin(this.renderSession); // start the filter manager this.filterManager.begin(this.renderSession, buffer); // render the scene! displayObject._renderWebGL(this.renderSession, matrix); // finish the sprite batch this.spriteBatch.end(); }; /** * Resizes the webGL view to the specified width and height. * * @method resize * @param width {Number} the new width of the webGL view * @param height {Number} the new height of the webGL view */ PIXI.WebGLRenderer.prototype.resize = function(width, height) { this.width = width * this.resolution; this.height = height * this.resolution; this.view.width = this.width; this.view.height = this.height; if (this.autoResize) { this.view.style.width = this.width / this.resolution + 'px'; this.view.style.height = this.height / this.resolution + 'px'; } this.gl.viewport(0, 0, this.width, this.height); this.projection.x = this.width / 2 / this.resolution; this.projection.y = -this.height / 2 / this.resolution; }; /** * Updates and Creates a WebGL texture for the renderers context. * * @method updateTexture * @param texture {Texture} the texture to update * @return {boolean} True if the texture was successfully bound, otherwise false. */ PIXI.WebGLRenderer.prototype.updateTexture = function(texture) { if (!texture.hasLoaded) { return false; } var gl = this.gl; if (!texture._glTextures[gl.id]) { texture._glTextures[gl.id] = gl.createTexture(); } gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultipliedAlpha); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.source); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); if (texture.mipmap && PIXI.isPowerOfTwo(texture.width, texture.height)) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); gl.generateMipmap(gl.TEXTURE_2D); } else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); } if (!texture._powerOf2) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); } else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); } texture._dirty[gl.id] = false; // return texture._glTextures[gl.id]; return true; }; /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @method destroy */ PIXI.WebGLRenderer.prototype.destroy = function() { PIXI.glContexts[this.glContextId] = null; this.projection = null; this.offset = null; this.shaderManager.destroy(); this.spriteBatch.destroy(); this.maskManager.destroy(); this.filterManager.destroy(); this.shaderManager = null; this.spriteBatch = null; this.maskManager = null; this.filterManager = null; this.gl = null; this.renderSession = null; PIXI.CanvasPool.remove(this); PIXI.instances[this.glContextId] = null; PIXI.WebGLRenderer.glContextId--; }; /** * Maps Pixi blend modes to WebGL blend modes. * * @method mapBlendModes */ PIXI.WebGLRenderer.prototype.mapBlendModes = function() { var gl = this.gl; if (!PIXI.blendModesWebGL) { var b = []; var modes = PIXI.blendModes; b[modes.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; b[modes.ADD] = [gl.SRC_ALPHA, gl.DST_ALPHA]; b[modes.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; b[modes.SCREEN] = [gl.SRC_ALPHA, gl.ONE]; b[modes.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; b[modes.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; b[modes.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; b[modes.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; b[modes.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; b[modes.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; b[modes.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; b[modes.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; b[modes.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; b[modes.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; b[modes.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; b[modes.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; b[modes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL = b; } }; PIXI.WebGLRenderer.glContextId = 0; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * @class WebGLBlendModeManager * @constructor * @param gl {WebGLContext} the current WebGL drawing context */ PIXI.WebGLBlendModeManager = function() { /** * @property currentBlendMode * @type Number */ this.currentBlendMode = 99999; }; PIXI.WebGLBlendModeManager.prototype.constructor = PIXI.WebGLBlendModeManager; /** * Sets the WebGL Context. * * @method setContext * @param gl {WebGLContext} the current WebGL drawing context */ PIXI.WebGLBlendModeManager.prototype.setContext = function(gl) { this.gl = gl; }; /** * Sets-up the given blendMode from WebGL's point of view. * * @method setBlendMode * @param blendMode {Number} the blendMode, should be a Pixi const, such as PIXI.BlendModes.ADD */ PIXI.WebGLBlendModeManager.prototype.setBlendMode = function(blendMode) { if(this.currentBlendMode === blendMode)return false; this.currentBlendMode = blendMode; var blendModeWebGL = PIXI.blendModesWebGL[this.currentBlendMode]; if (blendModeWebGL) { this.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); } return true; }; /** * Destroys this object. * * @method destroy */ PIXI.WebGLBlendModeManager.prototype.destroy = function() { this.gl = null; }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * @class WebGLMaskManager * @constructor * @private */ PIXI.WebGLMaskManager = function() { }; PIXI.WebGLMaskManager.prototype.constructor = PIXI.WebGLMaskManager; /** * Sets the drawing context to the one given in parameter. * * @method setContext * @param gl {WebGLContext} the current WebGL drawing context */ PIXI.WebGLMaskManager.prototype.setContext = function(gl) { this.gl = gl; }; /** * Applies the Mask and adds it to the current filter stack. * * @method pushMask * @param maskData {Array} * @param renderSession {Object} */ PIXI.WebGLMaskManager.prototype.pushMask = function(maskData, renderSession) { var gl = renderSession.gl; if (maskData.dirty) { PIXI.WebGLGraphics.updateGraphics(maskData, gl); } if (maskData._webGL[gl.id] === undefined || maskData._webGL[gl.id].data === undefined || maskData._webGL[gl.id].data.length === 0) { return; } renderSession.stencilManager.pushStencil(maskData, maskData._webGL[gl.id].data[0], renderSession); }; /** * Removes the last filter from the filter stack and doesn't return it. * * @method popMask * @param maskData {Array} * @param renderSession {Object} an object containing all the useful parameters */ PIXI.WebGLMaskManager.prototype.popMask = function(maskData, renderSession) { var gl = this.gl; if (maskData._webGL[gl.id] === undefined || maskData._webGL[gl.id].data === undefined || maskData._webGL[gl.id].data.length === 0) { return; } renderSession.stencilManager.popStencil(maskData, maskData._webGL[gl.id].data[0], renderSession); }; /** * Destroys the mask stack. * * @method destroy */ PIXI.WebGLMaskManager.prototype.destroy = function() { this.gl = null; }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * @class WebGLStencilManager * @constructor * @private */ PIXI.WebGLStencilManager = function() { this.stencilStack = []; this.reverse = true; this.count = 0; }; /** * Sets the drawing context to the one given in parameter. * * @method setContext * @param gl {WebGLContext} the current WebGL drawing context */ PIXI.WebGLStencilManager.prototype.setContext = function(gl) { this.gl = gl; }; /** * Applies the Mask and adds it to the current filter stack. * * @method pushMask * @param graphics {Graphics} * @param webGLData {Array} * @param renderSession {Object} */ PIXI.WebGLStencilManager.prototype.pushStencil = function(graphics, webGLData, renderSession) { var gl = this.gl; this.bindGraphics(graphics, webGLData, renderSession); if(this.stencilStack.length === 0) { gl.enable(gl.STENCIL_TEST); gl.clear(gl.STENCIL_BUFFER_BIT); this.reverse = true; this.count = 0; } this.stencilStack.push(webGLData); var level = this.count; gl.colorMask(false, false, false, false); gl.stencilFunc(gl.ALWAYS,0,0xFF); gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); // draw the triangle strip! if(webGLData.mode === 1) { gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); if(this.reverse) { gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); } else { gl.stencilFunc(gl.EQUAL,level, 0xFF); gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); } // draw a quad to increment.. gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); if(this.reverse) { gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); } else { gl.stencilFunc(gl.EQUAL,level+1, 0xFF); } this.reverse = !this.reverse; } else { if(!this.reverse) { gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); } else { gl.stencilFunc(gl.EQUAL,level, 0xFF); gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); } gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); if(!this.reverse) { gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); } else { gl.stencilFunc(gl.EQUAL,level+1, 0xFF); } } gl.colorMask(true, true, true, true); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); this.count++; }; /** * TODO this does not belong here! * * @method bindGraphics * @param graphics {Graphics} * @param webGLData {Array} * @param renderSession {Object} */ PIXI.WebGLStencilManager.prototype.bindGraphics = function(graphics, webGLData, renderSession) { //if(this._currentGraphics === graphics)return; this._currentGraphics = graphics; var gl = this.gl; // bind the graphics object.. var projection = renderSession.projection, offset = renderSession.offset, shader;// = renderSession.shaderManager.primitiveShader; if(webGLData.mode === 1) { shader = renderSession.shaderManager.complexPrimitiveShader; renderSession.shaderManager.setShader( shader ); gl.uniform1f(shader.flipY, renderSession.flipY); gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); gl.uniform2f(shader.projectionVector, projection.x, -projection.y); gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); gl.uniform3fv(shader.tintColor, PIXI.hex2rgb(graphics.tint)); gl.uniform3fv(shader.color, webGLData.color); gl.uniform1f(shader.alpha, graphics.worldAlpha * webGLData.alpha); gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0); // now do the rest.. // set the index buffer! gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); } else { //renderSession.shaderManager.activatePrimitiveShader(); shader = renderSession.shaderManager.primitiveShader; renderSession.shaderManager.setShader( shader ); gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); gl.uniform1f(shader.flipY, renderSession.flipY); gl.uniform2f(shader.projectionVector, projection.x, -projection.y); gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); gl.uniform3fv(shader.tintColor, PIXI.hex2rgb(graphics.tint)); gl.uniform1f(shader.alpha, graphics.worldAlpha); gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); // set the index buffer! gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); } }; /** * @method popStencil * @param graphics {Graphics} * @param webGLData {Array} * @param renderSession {Object} */ PIXI.WebGLStencilManager.prototype.popStencil = function(graphics, webGLData, renderSession) { var gl = this.gl; this.stencilStack.pop(); this.count--; if(this.stencilStack.length === 0) { // the stack is empty! gl.disable(gl.STENCIL_TEST); } else { var level = this.count; this.bindGraphics(graphics, webGLData, renderSession); gl.colorMask(false, false, false, false); if(webGLData.mode === 1) { this.reverse = !this.reverse; if(this.reverse) { gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); } else { gl.stencilFunc(gl.EQUAL,level+1, 0xFF); gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); } // draw a quad to increment.. gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); gl.stencilFunc(gl.ALWAYS,0,0xFF); gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); // draw the triangle strip! gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); if(!this.reverse) { gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); } else { gl.stencilFunc(gl.EQUAL,level, 0xFF); } } else { // console.log("<<>>") if(!this.reverse) { gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); } else { gl.stencilFunc(gl.EQUAL,level+1, 0xFF); gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); } gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); if(!this.reverse) { gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); } else { gl.stencilFunc(gl.EQUAL,level, 0xFF); } } gl.colorMask(true, true, true, true); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } }; /** * Destroys the mask stack. * * @method destroy */ PIXI.WebGLStencilManager.prototype.destroy = function() { this.stencilStack = null; this.gl = null; }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * @class WebGLShaderManager * @constructor * @private */ PIXI.WebGLShaderManager = function() { /** * @property maxAttibs * @type Number */ this.maxAttibs = 10; /** * @property attribState * @type Array */ this.attribState = []; /** * @property tempAttribState * @type Array */ this.tempAttribState = []; for (var i = 0; i < this.maxAttibs; i++) { this.attribState[i] = false; } /** * @property stack * @type Array */ this.stack = []; }; PIXI.WebGLShaderManager.prototype.constructor = PIXI.WebGLShaderManager; /** * Initialises the context and the properties. * * @method setContext * @param gl {WebGLContext} the current WebGL drawing context */ PIXI.WebGLShaderManager.prototype.setContext = function(gl) { this.gl = gl; // the next one is used for rendering primitives this.primitiveShader = new PIXI.PrimitiveShader(gl); // the next one is used for rendering triangle strips this.complexPrimitiveShader = new PIXI.ComplexPrimitiveShader(gl); // this shader is used for the default sprite rendering this.defaultShader = new PIXI.PixiShader(gl); // this shader is used for the fast sprite rendering this.fastShader = new PIXI.PixiFastShader(gl); // the next one is used for rendering triangle strips this.stripShader = new PIXI.StripShader(gl); this.setShader(this.defaultShader); }; /** * Takes the attributes given in parameters. * * @method setAttribs * @param attribs {Array} attribs */ PIXI.WebGLShaderManager.prototype.setAttribs = function(attribs) { // reset temp state var i; for (i = 0; i < this.tempAttribState.length; i++) { this.tempAttribState[i] = false; } // set the new attribs for (i = 0; i < attribs.length; i++) { var attribId = attribs[i]; this.tempAttribState[attribId] = true; } var gl = this.gl; for (i = 0; i < this.attribState.length; i++) { if(this.attribState[i] !== this.tempAttribState[i]) { this.attribState[i] = this.tempAttribState[i]; if(this.tempAttribState[i]) { gl.enableVertexAttribArray(i); } else { gl.disableVertexAttribArray(i); } } } }; /** * Sets the current shader. * * @method setShader * @param shader {Any} */ PIXI.WebGLShaderManager.prototype.setShader = function(shader) { if(this._currentId === shader._UID)return false; this._currentId = shader._UID; this.currentShader = shader; this.gl.useProgram(shader.program); this.setAttribs(shader.attributes); return true; }; /** * Destroys this object. * * @method destroy */ PIXI.WebGLShaderManager.prototype.destroy = function() { this.attribState = null; this.tempAttribState = null; this.primitiveShader.destroy(); this.complexPrimitiveShader.destroy(); this.defaultShader.destroy(); this.fastShader.destroy(); this.stripShader.destroy(); this.gl = null; }; /** * @author Mat Groves * * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ * for creating the original pixi version! * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer * * Heavily inspired by LibGDX's WebGLSpriteBatch: * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/WebGLSpriteBatch.java */ /** * * @class WebGLSpriteBatch * @private * @constructor */ PIXI.WebGLSpriteBatch = function() { /** * @property vertSize * @type Number */ this.vertSize = 5; /** * The number of images in the SpriteBatch before it flushes * @property size * @type Number */ this.size = 2000;//Math.pow(2, 16) / this.vertSize; //the total number of bytes in our batch var numVerts = this.size * 4 * 4 * this.vertSize; //the total number of indices in our batch var numIndices = this.size * 6; /** * Holds the vertices * * @property vertices * @type ArrayBuffer */ this.vertices = new PIXI.ArrayBuffer(numVerts); /** * View on the vertices as a Float32Array * * @property positions * @type Float32Array */ this.positions = new PIXI.Float32Array(this.vertices); /** * View on the vertices as a Uint32Array * * @property colors * @type Uint32Array */ this.colors = new PIXI.Uint32Array(this.vertices); /** * Holds the indices * * @property indices * @type Uint16Array */ this.indices = new PIXI.Uint16Array(numIndices); /** * @property lastIndexCount * @type Number */ this.lastIndexCount = 0; for (var i=0, j=0; i < numIndices; i += 6, j += 4) { this.indices[i + 0] = j + 0; this.indices[i + 1] = j + 1; this.indices[i + 2] = j + 2; this.indices[i + 3] = j + 0; this.indices[i + 4] = j + 2; this.indices[i + 5] = j + 3; } /** * @property drawing * @type Boolean */ this.drawing = false; /** * @property currentBatchSize * @type Number */ this.currentBatchSize = 0; /** * @property currentBaseTexture * @type BaseTexture */ this.currentBaseTexture = null; /** * @property dirty * @type Boolean */ this.dirty = true; /** * @property textures * @type Array */ this.textures = []; /** * @property blendModes * @type Array */ this.blendModes = []; /** * @property shaders * @type Array */ this.shaders = []; /** * @property sprites * @type Array */ this.sprites = []; /** * @property defaultShader * @type AbstractFilter */ this.defaultShader = new PIXI.AbstractFilter([ 'precision lowp float;', 'varying vec2 vTextureCoord;', 'varying vec4 vColor;', 'uniform sampler2D uSampler;', 'void main(void) {', ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', '}' ]); }; /** * @method setContext * @param gl {WebGLContext} the current WebGL drawing context */ PIXI.WebGLSpriteBatch.prototype.setContext = function(gl) { this.gl = gl; // create a couple of buffers this.vertexBuffer = gl.createBuffer(); this.indexBuffer = gl.createBuffer(); // 65535 is max index, so 65535 / 6 = 10922. //upload the index data gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); this.currentBlendMode = 99999; var shader = new PIXI.PixiShader(gl); shader.fragmentSrc = this.defaultShader.fragmentSrc; shader.uniforms = {}; shader.init(); this.defaultShader.shaders[gl.id] = shader; }; /** * @method begin * @param renderSession {Object} The RenderSession object */ PIXI.WebGLSpriteBatch.prototype.begin = function(renderSession) { this.renderSession = renderSession; this.shader = this.renderSession.shaderManager.defaultShader; this.start(); }; /** * @method end */ PIXI.WebGLSpriteBatch.prototype.end = function() { this.flush(); }; /** * @method render * @param sprite {Sprite} the sprite to render when using this spritebatch * @param {Matrix} [matrix] - Optional matrix. If provided the Display Object will be rendered using this matrix, otherwise it will use its worldTransform. */ PIXI.WebGLSpriteBatch.prototype.render = function(sprite, matrix) { var texture = sprite.texture; // They provided an alternative rendering matrix, so use it var wt = sprite.worldTransform; if (matrix) { wt = matrix; } // check texture.. if (this.currentBatchSize >= this.size) { this.flush(); this.currentBaseTexture = texture.baseTexture; } // get the uvs for the texture var uvs = texture._uvs; // if the uvs have not updated then no point rendering just yet! if (!uvs) { return; } var aX = sprite.anchor.x; var aY = sprite.anchor.y; var w0, w1, h0, h1; if (texture.trim) { // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords. var trim = texture.trim; w1 = trim.x - aX * trim.width; w0 = w1 + texture.crop.width; h1 = trim.y - aY * trim.height; h0 = h1 + texture.crop.height; } else { w0 = (texture.frame.width) * (1-aX); w1 = (texture.frame.width) * -aX; h0 = texture.frame.height * (1-aY); h1 = texture.frame.height * -aY; } var i = this.currentBatchSize * 4 * this.vertSize; var resolution = texture.baseTexture.resolution; var a = wt.a / resolution; var b = wt.b / resolution; var c = wt.c / resolution; var d = wt.d / resolution; var tx = wt.tx; var ty = wt.ty; var colors = this.colors; var positions = this.positions; if (this.renderSession.roundPixels) { // xy positions[i] = a * w1 + c * h1 + tx | 0; positions[i+1] = d * h1 + b * w1 + ty | 0; // xy positions[i+5] = a * w0 + c * h1 + tx | 0; positions[i+6] = d * h1 + b * w0 + ty | 0; // xy positions[i+10] = a * w0 + c * h0 + tx | 0; positions[i+11] = d * h0 + b * w0 + ty | 0; // xy positions[i+15] = a * w1 + c * h0 + tx | 0; positions[i+16] = d * h0 + b * w1 + ty | 0; } else { // xy positions[i] = a * w1 + c * h1 + tx; positions[i+1] = d * h1 + b * w1 + ty; // xy positions[i+5] = a * w0 + c * h1 + tx; positions[i+6] = d * h1 + b * w0 + ty; // xy positions[i+10] = a * w0 + c * h0 + tx; positions[i+11] = d * h0 + b * w0 + ty; // xy positions[i+15] = a * w1 + c * h0 + tx; positions[i+16] = d * h0 + b * w1 + ty; } // uv positions[i+2] = uvs.x0; positions[i+3] = uvs.y0; // uv positions[i+7] = uvs.x1; positions[i+8] = uvs.y1; // uv positions[i+12] = uvs.x2; positions[i+13] = uvs.y2; // uv positions[i+17] = uvs.x3; positions[i+18] = uvs.y3; // color and alpha var tint = sprite.tint; colors[i+4] = colors[i+9] = colors[i+14] = colors[i+19] = (tint >> 16) + (tint & 0xff00) + ((tint & 0xff) << 16) + (sprite.worldAlpha * 255 << 24); // increment the batchsize this.sprites[this.currentBatchSize++] = sprite; }; /** * Renders a TilingSprite using the spriteBatch. * * @method renderTilingSprite * @param sprite {TilingSprite} the sprite to render */ PIXI.WebGLSpriteBatch.prototype.renderTilingSprite = function(sprite) { var texture = sprite.tilingTexture; // check texture.. if (this.currentBatchSize >= this.size) { this.flush(); this.currentBaseTexture = texture.baseTexture; } // set the textures uvs temporarily if (!sprite._uvs) { sprite._uvs = new PIXI.TextureUvs(); } var uvs = sprite._uvs; var w = texture.baseTexture.width; var h = texture.baseTexture.height; // var w = sprite._frame.sourceSizeW; // var h = sprite._frame.sourceSizeH; // w = 16; // h = 16; sprite.tilePosition.x %= w * sprite.tileScaleOffset.x; sprite.tilePosition.y %= h * sprite.tileScaleOffset.y; var offsetX = sprite.tilePosition.x / (w * sprite.tileScaleOffset.x); var offsetY = sprite.tilePosition.y / (h * sprite.tileScaleOffset.y); var scaleX = (sprite.width / w) / (sprite.tileScale.x * sprite.tileScaleOffset.x); var scaleY = (sprite.height / h) / (sprite.tileScale.y * sprite.tileScaleOffset.y); uvs.x0 = 0 - offsetX; uvs.y0 = 0 - offsetY; uvs.x1 = (1 * scaleX) - offsetX; uvs.y1 = 0 - offsetY; uvs.x2 = (1 * scaleX) - offsetX; uvs.y2 = (1 * scaleY) - offsetY; uvs.x3 = 0 - offsetX; uvs.y3 = (1 * scaleY) - offsetY; // Get the sprites current alpha and tint and combine them into a single color var tint = sprite.tint; var color = (tint >> 16) + (tint & 0xff00) + ((tint & 0xff) << 16) + (sprite.worldAlpha * 255 << 24); var positions = this.positions; var colors = this.colors; var width = sprite.width; var height = sprite.height; // TODO trim?? var aX = sprite.anchor.x; var aY = sprite.anchor.y; var w0 = width * (1-aX); var w1 = width * -aX; var h0 = height * (1-aY); var h1 = height * -aY; var i = this.currentBatchSize * 4 * this.vertSize; var resolution = texture.baseTexture.resolution; var wt = sprite.worldTransform; var a = wt.a / resolution; var b = wt.b / resolution; var c = wt.c / resolution; var d = wt.d / resolution; var tx = wt.tx; var ty = wt.ty; // xy positions[i++] = a * w1 + c * h1 + tx; positions[i++] = d * h1 + b * w1 + ty; // uv positions[i++] = uvs.x0; positions[i++] = uvs.y0; // color colors[i++] = color; // xy positions[i++] = (a * w0 + c * h1 + tx); positions[i++] = d * h1 + b * w0 + ty; // uv positions[i++] = uvs.x1; positions[i++] = uvs.y1; // color colors[i++] = color; // xy positions[i++] = a * w0 + c * h0 + tx; positions[i++] = d * h0 + b * w0 + ty; // uv positions[i++] = uvs.x2; positions[i++] = uvs.y2; // color colors[i++] = color; // xy positions[i++] = a * w1 + c * h0 + tx; positions[i++] = d * h0 + b * w1 + ty; // uv positions[i++] = uvs.x3; positions[i++] = uvs.y3; // color colors[i++] = color; // increment the batchsize this.sprites[this.currentBatchSize++] = sprite; }; /** * Renders the content and empties the current batch. * * @method flush */ PIXI.WebGLSpriteBatch.prototype.flush = function() { // If the batch is length 0 then return as there is nothing to draw if (this.currentBatchSize === 0) { return; } var gl = this.gl; var shader; if (this.dirty) { this.dirty = false; // bind the main texture gl.activeTexture(gl.TEXTURE0); // bind the buffers gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); shader = this.defaultShader.shaders[gl.id]; // this is the same for each shader? var stride = this.vertSize * 4; gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, stride, 0); gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, stride, 2 * 4); // color attributes will be interpreted as unsigned bytes and normalized gl.vertexAttribPointer(shader.colorAttribute, 4, gl.UNSIGNED_BYTE, true, stride, 4 * 4); } // upload the verts to the buffer if (this.currentBatchSize > (this.size * 0.5)) { gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertices); } else { var view = this.positions.subarray(0, this.currentBatchSize * 4 * this.vertSize); gl.bufferSubData(gl.ARRAY_BUFFER, 0, view); } var nextTexture, nextBlendMode, nextShader; var batchSize = 0; var start = 0; var currentBaseTexture = null; var currentBlendMode = this.renderSession.blendModeManager.currentBlendMode; var currentShader = null; var blendSwap = false; var shaderSwap = false; var sprite; for (var i = 0, j = this.currentBatchSize; i < j; i++) { sprite = this.sprites[i]; if (sprite.tilingTexture) { nextTexture = sprite.tilingTexture.baseTexture; } else { nextTexture = sprite.texture.baseTexture; } nextBlendMode = sprite.blendMode; nextShader = sprite.shader || this.defaultShader; blendSwap = currentBlendMode !== nextBlendMode; shaderSwap = currentShader !== nextShader; // should I use _UIDS??? var skip = nextTexture.skipRender; if (skip && sprite.children.length > 0) { skip = false; } if ((currentBaseTexture !== nextTexture && !skip) || blendSwap || shaderSwap) { this.renderBatch(currentBaseTexture, batchSize, start); start = i; batchSize = 0; currentBaseTexture = nextTexture; if (blendSwap) { currentBlendMode = nextBlendMode; this.renderSession.blendModeManager.setBlendMode(currentBlendMode); } if (shaderSwap) { currentShader = nextShader; shader = currentShader.shaders[gl.id]; if (!shader) { shader = new PIXI.PixiShader(gl); shader.fragmentSrc = currentShader.fragmentSrc; shader.uniforms = currentShader.uniforms; shader.init(); currentShader.shaders[gl.id] = shader; } // set shader function??? this.renderSession.shaderManager.setShader(shader); if (shader.dirty) { shader.syncUniforms(); } // both these only need to be set if they are changing.. // set the projection var projection = this.renderSession.projection; gl.uniform2f(shader.projectionVector, projection.x, projection.y); // TODO - this is temporary! var offsetVector = this.renderSession.offset; gl.uniform2f(shader.offsetVector, offsetVector.x, offsetVector.y); // set the pointers } } batchSize++; } this.renderBatch(currentBaseTexture, batchSize, start); // then reset the batch! this.currentBatchSize = 0; }; /** * @method renderBatch * @param texture {Texture} * @param size {Number} * @param startIndex {Number} */ PIXI.WebGLSpriteBatch.prototype.renderBatch = function(texture, size, startIndex) { if (size === 0) { return; } var gl = this.gl; // check if a texture is dirty.. if (texture._dirty[gl.id]) { if (!this.renderSession.renderer.updateTexture(texture)) { // If updateTexture returns false then we cannot render it, so bail out now return; } } else { // bind the current texture gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); } // now draw those suckas! gl.drawElements(gl.TRIANGLES, size * 6, gl.UNSIGNED_SHORT, startIndex * 6 * 2); // increment the draw count this.renderSession.drawCount++; }; /** * @method stop */ PIXI.WebGLSpriteBatch.prototype.stop = function() { this.flush(); this.dirty = true; }; /** * @method start */ PIXI.WebGLSpriteBatch.prototype.start = function() { this.dirty = true; }; /** * Destroys the SpriteBatch. * * @method destroy */ PIXI.WebGLSpriteBatch.prototype.destroy = function() { this.vertices = null; this.indices = null; this.gl.deleteBuffer(this.vertexBuffer); this.gl.deleteBuffer(this.indexBuffer); this.currentBaseTexture = null; this.gl = null; }; /** * @author Mat Groves * * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ * for creating the original pixi version! * * Heavily inspired by LibGDX's WebGLSpriteBatch: * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/WebGLSpriteBatch.java */ /** * @class WebGLFastSpriteBatch * @constructor */ PIXI.WebGLFastSpriteBatch = function(gl) { /** * @property vertSize * @type Number */ this.vertSize = 10; /** * @property maxSize * @type Number */ this.maxSize = 6000;//Math.pow(2, 16) / this.vertSize; /** * @property size * @type Number */ this.size = this.maxSize; //the total number of floats in our batch var numVerts = this.size * 4 * this.vertSize; //the total number of indices in our batch var numIndices = this.maxSize * 6; /** * Vertex data * @property vertices * @type Float32Array */ this.vertices = new PIXI.Float32Array(numVerts); /** * Index data * @property indices * @type Uint16Array */ this.indices = new PIXI.Uint16Array(numIndices); /** * @property vertexBuffer * @type Object */ this.vertexBuffer = null; /** * @property indexBuffer * @type Object */ this.indexBuffer = null; /** * @property lastIndexCount * @type Number */ this.lastIndexCount = 0; for (var i=0, j=0; i < numIndices; i += 6, j += 4) { this.indices[i + 0] = j + 0; this.indices[i + 1] = j + 1; this.indices[i + 2] = j + 2; this.indices[i + 3] = j + 0; this.indices[i + 4] = j + 2; this.indices[i + 5] = j + 3; } /** * @property drawing * @type Boolean */ this.drawing = false; /** * @property currentBatchSize * @type Number */ this.currentBatchSize = 0; /** * @property currentBaseTexture * @type BaseTexture */ this.currentBaseTexture = null; /** * @property currentBlendMode * @type Number */ this.currentBlendMode = 0; /** * @property renderSession * @type Object */ this.renderSession = null; /** * @property shader * @type Object */ this.shader = null; /** * @property matrix * @type Matrix */ this.matrix = null; this.setContext(gl); }; PIXI.WebGLFastSpriteBatch.prototype.constructor = PIXI.WebGLFastSpriteBatch; /** * Sets the WebGL Context. * * @method setContext * @param gl {WebGLContext} the current WebGL drawing context */ PIXI.WebGLFastSpriteBatch.prototype.setContext = function(gl) { this.gl = gl; // create a couple of buffers this.vertexBuffer = gl.createBuffer(); this.indexBuffer = gl.createBuffer(); // 65535 is max index, so 65535 / 6 = 10922. //upload the index data gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); }; /** * @method begin * @param spriteBatch {WebGLSpriteBatch} * @param renderSession {Object} */ PIXI.WebGLFastSpriteBatch.prototype.begin = function(spriteBatch, renderSession) { this.renderSession = renderSession; this.shader = this.renderSession.shaderManager.fastShader; this.matrix = spriteBatch.worldTransform.toArray(true); this.start(); }; /** * @method end */ PIXI.WebGLFastSpriteBatch.prototype.end = function() { this.flush(); }; /** * @method render * @param spriteBatch {WebGLSpriteBatch} */ PIXI.WebGLFastSpriteBatch.prototype.render = function(spriteBatch) { var children = spriteBatch.children; var sprite = children[0]; // if the uvs have not updated then no point rendering just yet! // check texture. if(!sprite.texture._uvs)return; this.currentBaseTexture = sprite.texture.baseTexture; // check blend mode if(sprite.blendMode !== this.renderSession.blendModeManager.currentBlendMode) { this.flush(); this.renderSession.blendModeManager.setBlendMode(sprite.blendMode); } for(var i=0,j= children.length; i= this.size) { this.flush(); } }; /** * @method flush */ PIXI.WebGLFastSpriteBatch.prototype.flush = function() { // If the batch is length 0 then return as there is nothing to draw if (this.currentBatchSize===0)return; var gl = this.gl; // bind the current texture if(!this.currentBaseTexture._glTextures[gl.id])this.renderSession.renderer.updateTexture(this.currentBaseTexture, gl); gl.bindTexture(gl.TEXTURE_2D, this.currentBaseTexture._glTextures[gl.id]); // upload the verts to the buffer if(this.currentBatchSize > ( this.size * 0.5 ) ) { gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertices); } else { var view = this.vertices.subarray(0, this.currentBatchSize * 4 * this.vertSize); gl.bufferSubData(gl.ARRAY_BUFFER, 0, view); } // now draw those suckas! gl.drawElements(gl.TRIANGLES, this.currentBatchSize * 6, gl.UNSIGNED_SHORT, 0); // then reset the batch! this.currentBatchSize = 0; // increment the draw count this.renderSession.drawCount++; }; /** * @method stop */ PIXI.WebGLFastSpriteBatch.prototype.stop = function() { this.flush(); }; /** * @method start */ PIXI.WebGLFastSpriteBatch.prototype.start = function() { var gl = this.gl; // bind the main texture gl.activeTexture(gl.TEXTURE0); // bind the buffers gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); // set the projection var projection = this.renderSession.projection; gl.uniform2f(this.shader.projectionVector, projection.x, projection.y); // set the matrix gl.uniformMatrix3fv(this.shader.uMatrix, false, this.matrix); // set the pointers var stride = this.vertSize * 4; gl.vertexAttribPointer(this.shader.aVertexPosition, 2, gl.FLOAT, false, stride, 0); gl.vertexAttribPointer(this.shader.aPositionCoord, 2, gl.FLOAT, false, stride, 2 * 4); gl.vertexAttribPointer(this.shader.aScale, 2, gl.FLOAT, false, stride, 4 * 4); gl.vertexAttribPointer(this.shader.aRotation, 1, gl.FLOAT, false, stride, 6 * 4); gl.vertexAttribPointer(this.shader.aTextureCoord, 2, gl.FLOAT, false, stride, 7 * 4); gl.vertexAttribPointer(this.shader.colorAttribute, 1, gl.FLOAT, false, stride, 9 * 4); }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * @class WebGLFilterManager * @constructor */ PIXI.WebGLFilterManager = function() { /** * @property filterStack * @type Array */ this.filterStack = []; /** * @property offsetX * @type Number */ this.offsetX = 0; /** * @property offsetY * @type Number */ this.offsetY = 0; }; PIXI.WebGLFilterManager.prototype.constructor = PIXI.WebGLFilterManager; /** * Initialises the context and the properties. * * @method setContext * @param gl {WebGLContext} the current WebGL drawing context */ PIXI.WebGLFilterManager.prototype.setContext = function(gl) { this.gl = gl; this.texturePool = []; this.initShaderBuffers(); }; /** * @method begin * @param renderSession {RenderSession} * @param buffer {ArrayBuffer} */ PIXI.WebGLFilterManager.prototype.begin = function(renderSession, buffer) { this.renderSession = renderSession; this.defaultShader = renderSession.shaderManager.defaultShader; var projection = this.renderSession.projection; this.width = projection.x * 2; this.height = -projection.y * 2; this.buffer = buffer; }; /** * Applies the filter and adds it to the current filter stack. * * @method pushFilter * @param filterBlock {Object} the filter that will be pushed to the current filter stack */ PIXI.WebGLFilterManager.prototype.pushFilter = function(filterBlock) { var gl = this.gl; var projection = this.renderSession.projection; var offset = this.renderSession.offset; filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); // >>> modify by nextht filterBlock._previous_stencil_mgr = this.renderSession.stencilManager; this.renderSession.stencilManager = new PIXI.WebGLStencilManager(); this.renderSession.stencilManager.setContext(gl); gl.disable(gl.STENCIL_TEST); // <<< modify by nextht // filter program // OPTIMISATION - the first filter is free if its a simple color change? this.filterStack.push(filterBlock); var filter = filterBlock.filterPasses[0]; this.offsetX += filterBlock._filterArea.x; this.offsetY += filterBlock._filterArea.y; var texture = this.texturePool.pop(); if(!texture) { texture = new PIXI.FilterTexture(this.gl, this.width * this.renderSession.resolution, this.height * this.renderSession.resolution); } else { texture.resize(this.width * this.renderSession.resolution, this.height * this.renderSession.resolution); } gl.bindTexture(gl.TEXTURE_2D, texture.texture); var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; var padding = filter.padding; filterArea.x -= padding; filterArea.y -= padding; filterArea.width += padding * 2; filterArea.height += padding * 2; // cap filter to screen size.. if(filterArea.x < 0)filterArea.x = 0; if(filterArea.width > this.width)filterArea.width = this.width; if(filterArea.y < 0)filterArea.y = 0; if(filterArea.height > this.height)filterArea.height = this.height; //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); // set view port gl.viewport(0, 0, filterArea.width * this.renderSession.resolution, filterArea.height * this.renderSession.resolution); projection.x = filterArea.width/2; projection.y = -filterArea.height/2; offset.x = -filterArea.x; offset.y = -filterArea.y; // update projection // now restore the regular shader.. // this.renderSession.shaderManager.setShader(this.defaultShader); //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); gl.colorMask(true, true, true, true); gl.clearColor(0,0,0, 0); gl.clear(gl.COLOR_BUFFER_BIT); filterBlock._glFilterTexture = texture; }; /** * Removes the last filter from the filter stack and doesn't return it. * * @method popFilter */ PIXI.WebGLFilterManager.prototype.popFilter = function() { var gl = this.gl; var filterBlock = this.filterStack.pop(); var filterArea = filterBlock._filterArea; var texture = filterBlock._glFilterTexture; var projection = this.renderSession.projection; var offset = this.renderSession.offset; if(filterBlock.filterPasses.length > 1) { gl.viewport(0, 0, filterArea.width * this.renderSession.resolution, filterArea.height * this.renderSession.resolution); gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); this.vertexArray[0] = 0; this.vertexArray[1] = filterArea.height; this.vertexArray[2] = filterArea.width; this.vertexArray[3] = filterArea.height; this.vertexArray[4] = 0; this.vertexArray[5] = 0; this.vertexArray[6] = filterArea.width; this.vertexArray[7] = 0; gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); // now set the uvs.. this.uvArray[2] = filterArea.width/this.width; this.uvArray[5] = filterArea.height/this.height; this.uvArray[6] = filterArea.width/this.width; this.uvArray[7] = filterArea.height/this.height; gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); var inputTexture = texture; var outputTexture = this.texturePool.pop(); if(!outputTexture)outputTexture = new PIXI.FilterTexture(this.gl, this.width * this.renderSession.resolution, this.height * this.renderSession.resolution); outputTexture.resize(this.width * this.renderSession.resolution, this.height * this.renderSession.resolution); // need to clear this FBO as it may have some left over elements from a previous filter. gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); gl.clear(gl.COLOR_BUFFER_BIT); gl.disable(gl.BLEND); for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { var filterPass = filterBlock.filterPasses[i]; gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); // set texture gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); // draw texture.. //filterPass.applyFilterPass(filterArea.width, filterArea.height); this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); // swap the textures.. var temp = inputTexture; inputTexture = outputTexture; outputTexture = temp; } gl.enable(gl.BLEND); texture = inputTexture; this.texturePool.push(outputTexture); } var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; this.offsetX -= filterArea.x; this.offsetY -= filterArea.y; var sizeX = this.width; var sizeY = this.height; var offsetX = 0; var offsetY = 0; var buffer = this.buffer; // time to render the filters texture to the previous scene if(this.filterStack.length === 0) { gl.colorMask(true, true, true, true);//this.transparent); } else { var currentFilter = this.filterStack[this.filterStack.length-1]; filterArea = currentFilter._filterArea; sizeX = filterArea.width; sizeY = filterArea.height; offsetX = filterArea.x; offsetY = filterArea.y; buffer = currentFilter._glFilterTexture.frameBuffer; } // TODO need to remove these global elements.. projection.x = sizeX/2; projection.y = -sizeY/2; offset.x = offsetX; offset.y = offsetY; filterArea = filterBlock._filterArea; var x = filterArea.x-offsetX; var y = filterArea.y-offsetY; // update the buffers.. // make sure to flip the y! gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); this.vertexArray[0] = x; this.vertexArray[1] = y + filterArea.height; this.vertexArray[2] = x + filterArea.width; this.vertexArray[3] = y + filterArea.height; this.vertexArray[4] = x; this.vertexArray[5] = y; this.vertexArray[6] = x + filterArea.width; this.vertexArray[7] = y; gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); this.uvArray[2] = filterArea.width/this.width; this.uvArray[5] = filterArea.height/this.height; this.uvArray[6] = filterArea.width/this.width; this.uvArray[7] = filterArea.height/this.height; gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); gl.viewport(0, 0, sizeX * this.renderSession.resolution, sizeY * this.renderSession.resolution); // bind the buffer gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); // set the blend mode! //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) // set texture gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, texture.texture); // >>> modify by nextht if (this.renderSession.stencilManager) { this.renderSession.stencilManager.destroy(); } this.renderSession.stencilManager = filterBlock._previous_stencil_mgr; filterBlock._previous_stencil_mgr = null; if (this.renderSession.stencilManager.count > 0) { gl.enable(gl.STENCIL_TEST); } else { gl.disable(gl.STENCIL_TEST); } // <<< modify by nextht // apply! this.applyFilterPass(filter, filterArea, sizeX, sizeY); // now restore the regular shader.. should happen automatically now.. // this.renderSession.shaderManager.setShader(this.defaultShader); // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); // return the texture to the pool this.texturePool.push(texture); filterBlock._glFilterTexture = null; }; /** * Applies the filter to the specified area. * * @method applyFilterPass * @param filter {AbstractFilter} the filter that needs to be applied * @param filterArea {Texture} TODO - might need an update * @param width {Number} the horizontal range of the filter * @param height {Number} the vertical range of the filter */ PIXI.WebGLFilterManager.prototype.applyFilterPass = function(filter, filterArea, width, height) { // use program var gl = this.gl; var shader = filter.shaders[gl.id]; if(!shader) { shader = new PIXI.PixiShader(gl); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; shader.init(); filter.shaders[gl.id] = shader; } // set the shader this.renderSession.shaderManager.setShader(shader); // gl.useProgram(shader.program); gl.uniform2f(shader.projectionVector, width/2, -height/2); gl.uniform2f(shader.offsetVector, 0,0); if(filter.uniforms.dimensions) { filter.uniforms.dimensions.value[0] = this.width;//width; filter.uniforms.dimensions.value[1] = this.height;//height; filter.uniforms.dimensions.value[2] = this.vertexArray[0]; filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; } shader.syncUniforms(); gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); // draw the filter... gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); this.renderSession.drawCount++; }; /** * Initialises the shader buffers. * * @method initShaderBuffers */ PIXI.WebGLFilterManager.prototype.initShaderBuffers = function() { var gl = this.gl; // create some buffers this.vertexBuffer = gl.createBuffer(); this.uvBuffer = gl.createBuffer(); this.colorBuffer = gl.createBuffer(); this.indexBuffer = gl.createBuffer(); // bind and upload the vertexs.. // keep a reference to the vertexFloatData.. this.vertexArray = new PIXI.Float32Array([0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0]); gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); // bind and upload the uv buffer this.uvArray = new PIXI.Float32Array([0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0]); gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); this.colorArray = new PIXI.Float32Array([1.0, 0xFFFFFF, 1.0, 0xFFFFFF, 1.0, 0xFFFFFF, 1.0, 0xFFFFFF]); gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); // bind and upload the index gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); }; /** * Destroys the filter and removes it from the filter stack. * * @method destroy */ PIXI.WebGLFilterManager.prototype.destroy = function() { var gl = this.gl; this.filterStack = null; this.offsetX = 0; this.offsetY = 0; // destroy textures for (var i = 0; i < this.texturePool.length; i++) { this.texturePool[i].destroy(); } this.texturePool = null; //destroy buffers.. gl.deleteBuffer(this.vertexBuffer); gl.deleteBuffer(this.uvBuffer); gl.deleteBuffer(this.colorBuffer); gl.deleteBuffer(this.indexBuffer); }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * @class FilterTexture * @constructor * @param gl {WebGLContext} the current WebGL drawing context * @param width {Number} the horizontal range of the filter * @param height {Number} the vertical range of the filter * @param scaleMode {Number} See {{#crossLink "PIXI/scaleModes:property"}}PIXI.scaleModes{{/crossLink}} for possible values */ PIXI.FilterTexture = function(gl, width, height, scaleMode) { /** * @property gl * @type WebGLContext */ this.gl = gl; // next time to create a frame buffer and texture /** * @property frameBuffer * @type Any */ this.frameBuffer = gl.createFramebuffer(); /** * @property texture * @type Any */ this.texture = gl.createTexture(); /** * @property scaleMode * @type Number */ scaleMode = scaleMode || PIXI.scaleModes.DEFAULT; gl.bindTexture(gl.TEXTURE_2D, this.texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer ); gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer ); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0); // required for masking a mask?? this.renderBuffer = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, this.renderBuffer); gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, this.renderBuffer); this.resize(width, height); }; PIXI.FilterTexture.prototype.constructor = PIXI.FilterTexture; /** * Clears the filter texture. * * @method clear */ PIXI.FilterTexture.prototype.clear = function() { var gl = this.gl; gl.clearColor(0,0,0, 0); gl.clear(gl.COLOR_BUFFER_BIT); }; /** * Resizes the texture to the specified width and height * * @method resize * @param width {Number} the new width of the texture * @param height {Number} the new height of the texture */ PIXI.FilterTexture.prototype.resize = function(width, height) { if(this.width === width && this.height === height) return; this.width = width; this.height = height; var gl = this.gl; gl.bindTexture(gl.TEXTURE_2D, this.texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width , height , 0, gl.RGBA, gl.UNSIGNED_BYTE, null); // update the stencil buffer width and height gl.bindRenderbuffer(gl.RENDERBUFFER, this.renderBuffer); gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, width , height ); }; /** * Destroys the filter texture. * * @method destroy */ PIXI.FilterTexture.prototype.destroy = function() { var gl = this.gl; gl.deleteFramebuffer( this.frameBuffer ); gl.deleteTexture( this.texture ); this.frameBuffer = null; this.texture = null; }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * Creates a Canvas element of the given size. * * @class CanvasBuffer * @constructor * @param width {Number} the width for the newly created canvas * @param height {Number} the height for the newly created canvas */ PIXI.CanvasBuffer = function(width, height) { /** * The width of the Canvas in pixels. * * @property width * @type Number */ this.width = width; /** * The height of the Canvas in pixels. * * @property height * @type Number */ this.height = height; /** * The Canvas object that belongs to this CanvasBuffer. * * @property canvas * @type HTMLCanvasElement */ this.canvas = PIXI.CanvasPool.create(this, this.width, this.height); /** * A CanvasRenderingContext2D object representing a two-dimensional rendering context. * * @property context * @type CanvasRenderingContext2D */ this.context = this.canvas.getContext("2d"); this.canvas.width = width; this.canvas.height = height; }; PIXI.CanvasBuffer.prototype.constructor = PIXI.CanvasBuffer; /** * Clears the canvas that was created by the CanvasBuffer class. * * @method clear * @private */ PIXI.CanvasBuffer.prototype.clear = function() { this.context.setTransform(1, 0, 0, 1, 0, 0); this.context.clearRect(0,0, this.width, this.height); }; /** * Resizes the canvas to the specified width and height. * * @method resize * @param width {Number} the new width of the canvas * @param height {Number} the new height of the canvas */ PIXI.CanvasBuffer.prototype.resize = function(width, height) { this.width = this.canvas.width = width; this.height = this.canvas.height = height; }; /** * Frees the canvas up for use again. * * @method destroy */ PIXI.CanvasBuffer.prototype.destroy = function() { PIXI.CanvasPool.remove(this); }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * A set of functions used to handle masking. * * @class CanvasMaskManager * @constructor */ PIXI.CanvasMaskManager = function() { }; PIXI.CanvasMaskManager.prototype.constructor = PIXI.CanvasMaskManager; /** * This method adds it to the current stack of masks. * * @method pushMask * @param maskData {Object} the maskData that will be pushed * @param renderSession {Object} The renderSession whose context will be used for this mask manager. */ PIXI.CanvasMaskManager.prototype.pushMask = function(maskData, renderSession) { var context = renderSession.context; context.save(); var cacheAlpha = maskData.alpha; var transform = maskData.worldTransform; var resolution = renderSession.resolution; context.setTransform(transform.a * resolution, transform.b * resolution, transform.c * resolution, transform.d * resolution, transform.tx * resolution, transform.ty * resolution); PIXI.CanvasGraphics.renderGraphicsMask(maskData, context); context.clip(); maskData.worldAlpha = cacheAlpha; }; /** * Restores the current drawing context to the state it was before the mask was applied. * * @method popMask * @param renderSession {Object} The renderSession whose context will be used for this mask manager. */ PIXI.CanvasMaskManager.prototype.popMask = function(renderSession) { renderSession.context.restore(); }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * Utility methods for Sprite/Texture tinting. * * @class CanvasTinter * @static */ PIXI.CanvasTinter = function() {}; /** * Basically this method just needs a sprite and a color and tints the sprite with the given color. * * @method getTintedTexture * @static * @param sprite {Sprite} the sprite to tint * @param color {Number} the color to use to tint the sprite with * @return {HTMLCanvasElement} The tinted canvas */ PIXI.CanvasTinter.getTintedTexture = function(sprite, color) { var canvas = sprite.tintedTexture || PIXI.CanvasPool.create(this); PIXI.CanvasTinter.tintMethod(sprite.texture, color, canvas); return canvas; }; /** * Tint a texture using the "multiply" operation. * * @method tintWithMultiply * @static * @param texture {Texture} the texture to tint * @param color {Number} the color to use to tint the sprite with * @param canvas {HTMLCanvasElement} the current canvas */ PIXI.CanvasTinter.tintWithMultiply = function(texture, color, canvas) { var context = canvas.getContext("2d"); var crop = texture.crop; if (canvas.width !== crop.width || canvas.height !== crop.height) { canvas.width = crop.width; canvas.height = crop.height; } context.clearRect(0, 0, crop.width, crop.height); context.fillStyle = "#" + ("00000" + (color | 0).toString(16)).substr(-6); context.fillRect(0, 0, crop.width, crop.height); context.globalCompositeOperation = "multiply"; context.drawImage(texture.baseTexture.source, crop.x, crop.y, crop.width, crop.height, 0, 0, crop.width, crop.height); context.globalCompositeOperation = "destination-atop"; context.drawImage(texture.baseTexture.source, crop.x, crop.y, crop.width, crop.height, 0, 0, crop.width, crop.height); }; /** * Tint a texture pixel per pixel. * * @method tintPerPixel * @static * @param texture {Texture} the texture to tint * @param color {Number} the color to use to tint the sprite with * @param canvas {HTMLCanvasElement} the current canvas */ PIXI.CanvasTinter.tintWithPerPixel = function(texture, color, canvas) { var context = canvas.getContext("2d"); var crop = texture.crop; canvas.width = crop.width; canvas.height = crop.height; context.globalCompositeOperation = "copy"; context.drawImage(texture.baseTexture.source, crop.x, crop.y, crop.width, crop.height, 0, 0, crop.width, crop.height); var rgbValues = PIXI.hex2rgb(color); var r = rgbValues[0], g = rgbValues[1], b = rgbValues[2]; var pixelData = context.getImageData(0, 0, crop.width, crop.height); var pixels = pixelData.data; for (var i = 0; i < pixels.length; i += 4) { pixels[i + 0] *= r; pixels[i + 1] *= g; pixels[i + 2] *= b; if (!PIXI.CanvasTinter.canHandleAlpha) { var alpha = pixels[i + 3]; pixels[i + 0] /= 255 / alpha; pixels[i + 1] /= 255 / alpha; pixels[i + 2] /= 255 / alpha; } } context.putImageData(pixelData, 0, 0); }; /** * Checks if the browser correctly supports putImageData alpha channels. * * @method checkInverseAlpha * @static */ PIXI.CanvasTinter.checkInverseAlpha = function() { var canvas = new PIXI.CanvasBuffer(2, 1); canvas.context.fillStyle = "rgba(10, 20, 30, 0.5)"; // Draw a single pixel canvas.context.fillRect(0, 0, 1, 1); // Get the color values var s1 = canvas.context.getImageData(0, 0, 1, 1); if (s1 === null) { return false; } // Plot them to x2 canvas.context.putImageData(s1, 1, 0); // Get those values var s2 = canvas.context.getImageData(1, 0, 1, 1); // Compare and return return (s2.data[0] === s1.data[0] && s2.data[1] === s1.data[1] && s2.data[2] === s1.data[2] && s2.data[3] === s1.data[3]); }; /** * If the browser isn't capable of handling tinting with alpha this will be false. * This property is only applicable if using tintWithPerPixel. * * @property canHandleAlpha * @type Boolean * @static */ PIXI.CanvasTinter.canHandleAlpha = PIXI.CanvasTinter.checkInverseAlpha(); /** * Whether or not the Canvas BlendModes are supported, consequently the ability to tint using the multiply method. * * @property canUseMultiply * @type Boolean * @static */ PIXI.CanvasTinter.canUseMultiply = PIXI.canUseNewCanvasBlendModes(); /** * The tinting method that will be used. * * @method tintMethod * @static */ PIXI.CanvasTinter.tintMethod = PIXI.CanvasTinter.canUseMultiply ? PIXI.CanvasTinter.tintWithMultiply : PIXI.CanvasTinter.tintWithPerPixel; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * The CanvasRenderer draws the Stage and all its content onto a 2d canvas. This renderer should be used for browsers that do not support webGL. * Don't forget to add the CanvasRenderer.view to your DOM or you will not see anything :) * * @class CanvasRenderer * @constructor * @param game {Phaser.Game} A reference to the Phaser Game instance */ PIXI.CanvasRenderer = function (game) { /** * @property {Phaser.Game} game - A reference to the Phaser Game instance. */ this.game = game; if (!PIXI.defaultRenderer) { PIXI.defaultRenderer = this; } /** * The renderer type. * * @property type * @type Number */ this.type = PIXI.CANVAS_RENDERER; /** * The resolution of the canvas. * * @property resolution * @type Number */ this.resolution = game.resolution; /** * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. * If the Stage is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. * If the Stage is transparent Pixi will use clearRect to clear the canvas every frame. * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. * * @property clearBeforeRender * @type Boolean * @default */ this.clearBeforeRender = game.clearBeforeRender; /** * Whether the render view is transparent * * @property transparent * @type Boolean */ this.transparent = game.transparent; /** * Whether the render view should be resized automatically * * @property autoResize * @type Boolean */ this.autoResize = false; /** * The width of the canvas view * * @property width * @type Number * @default 800 */ this.width = game.width * this.resolution; /** * The height of the canvas view * * @property height * @type Number * @default 600 */ this.height = game.height * this.resolution; /** * The canvas element that everything is drawn to. * * @property view * @type HTMLCanvasElement */ this.view = game.canvas; /** * The canvas 2d context that everything is drawn with * @property context * @type CanvasRenderingContext2D */ this.context = this.view.getContext("2d", { alpha: this.transparent } ); /** * Boolean flag controlling canvas refresh. * * @property refresh * @type Boolean */ this.refresh = true; /** * Internal var. * * @property count * @type Number */ this.count = 0; /** * Instance of a PIXI.CanvasMaskManager, handles masking when using the canvas renderer * @property CanvasMaskManager * @type CanvasMaskManager */ this.maskManager = new PIXI.CanvasMaskManager(); /** * The render session is just a bunch of parameter used for rendering * @property renderSession * @type Object */ this.renderSession = { context: this.context, maskManager: this.maskManager, scaleMode: null, smoothProperty: Phaser.Canvas.getSmoothingPrefix(this.context), /** * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. * Handy for crisp pixel art and speed on legacy devices. */ roundPixels: false }; this.mapBlendModes(); this.resize(this.width, this.height); }; // constructor PIXI.CanvasRenderer.prototype.constructor = PIXI.CanvasRenderer; /** * Renders the Stage to this canvas view * * @method render * @param stage {Stage} the Stage element to be rendered */ PIXI.CanvasRenderer.prototype.render = function (stage) { this.context.setTransform(1, 0, 0, 1, 0, 0); this.context.globalAlpha = 1; this.renderSession.currentBlendMode = 0; this.renderSession.shakeX = this.game.camera._shake.x; this.renderSession.shakeY = this.game.camera._shake.y; this.context.globalCompositeOperation = 'source-over'; if (navigator.isCocoonJS && this.view.screencanvas) { this.context.fillStyle = "black"; this.context.clear(); } if (this.clearBeforeRender) { if (this.transparent) { this.context.clearRect(0, 0, this.width, this.height); } else { this.context.fillStyle = stage._bgColor.rgba; this.context.fillRect(0, 0, this.width , this.height); } } this.renderDisplayObject(stage); }; /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * * @method destroy * @param [removeView=true] {boolean} Removes the Canvas element from the DOM. */ PIXI.CanvasRenderer.prototype.destroy = function (removeView) { if (removeView === undefined) { removeView = true; } if (removeView && this.view.parent) { this.view.parent.removeChild(this.view); } this.view = null; this.context = null; this.maskManager = null; this.renderSession = null; }; /** * Resizes the canvas view to the specified width and height * * @method resize * @param width {Number} the new width of the canvas view * @param height {Number} the new height of the canvas view */ PIXI.CanvasRenderer.prototype.resize = function (width, height) { this.width = width * this.resolution; this.height = height * this.resolution; this.view.width = this.width; this.view.height = this.height; if (this.autoResize) { this.view.style.width = this.width / this.resolution + "px"; this.view.style.height = this.height / this.resolution + "px"; } if (this.renderSession.smoothProperty) { this.context[this.renderSession.smoothProperty] = (this.renderSession.scaleMode === PIXI.scaleModes.LINEAR); } }; /** * Renders a display object * * @method renderDisplayObject * @param displayObject {DisplayObject} The displayObject to render * @param context {CanvasRenderingContext2D} the context 2d method of the canvas * @param [matrix] {Matrix} Optional matrix to apply to the display object before rendering. * @private */ PIXI.CanvasRenderer.prototype.renderDisplayObject = function (displayObject, context, matrix) { this.renderSession.context = context || this.context; this.renderSession.resolution = this.resolution; displayObject._renderCanvas(this.renderSession, matrix); }; /** * Maps Pixi blend modes to canvas blend modes. * * @method mapBlendModes * @private */ PIXI.CanvasRenderer.prototype.mapBlendModes = function () { if (!PIXI.blendModesCanvas) { var b = []; var modes = PIXI.blendModes; var useNew = PIXI.canUseNewCanvasBlendModes(); b[modes.NORMAL] = 'source-over'; b[modes.ADD] = 'lighter'; b[modes.MULTIPLY] = (useNew) ? 'multiply' : 'source-over'; b[modes.SCREEN] = (useNew) ? 'screen' : 'source-over'; b[modes.OVERLAY] = (useNew) ? 'overlay' : 'source-over'; b[modes.DARKEN] = (useNew) ? 'darken' : 'source-over'; b[modes.LIGHTEN] = (useNew) ? 'lighten' : 'source-over'; b[modes.COLOR_DODGE] = (useNew) ? 'color-dodge' : 'source-over'; b[modes.COLOR_BURN] = (useNew) ? 'color-burn' : 'source-over'; b[modes.HARD_LIGHT] = (useNew) ? 'hard-light' : 'source-over'; b[modes.SOFT_LIGHT] = (useNew) ? 'soft-light' : 'source-over'; b[modes.DIFFERENCE] = (useNew) ? 'difference' : 'source-over'; b[modes.EXCLUSION] = (useNew) ? 'exclusion' : 'source-over'; b[modes.HUE] = (useNew) ? 'hue' : 'source-over'; b[modes.SATURATION] = (useNew) ? 'saturation' : 'source-over'; b[modes.COLOR] = (useNew) ? 'color' : 'source-over'; b[modes.LUMINOSITY] = (useNew) ? 'luminosity' : 'source-over'; PIXI.blendModesCanvas = b; } }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ PIXI.BaseTextureCache = {}; PIXI.BaseTextureCacheIdGenerator = 0; /** * A texture stores the information that represents an image. All textures have a base texture. * * @class BaseTexture * @uses EventTarget * @constructor * @param source {String} the source object (image or canvas) * @param scaleMode {Number} See {{#crossLink "PIXI/scaleModes:property"}}PIXI.scaleModes{{/crossLink}} for possible values */ PIXI.BaseTexture = function(source, scaleMode) { /** * The Resolution of the texture. * * @property resolution * @type Number */ this.resolution = 1; /** * [read-only] The width of the base texture set when the image has loaded * * @property width * @type Number * @readOnly */ this.width = 100; /** * [read-only] The height of the base texture set when the image has loaded * * @property height * @type Number * @readOnly */ this.height = 100; /** * The scale mode to apply when scaling this texture * * @property scaleMode * @type {Number} * @default PIXI.scaleModes.LINEAR */ this.scaleMode = scaleMode || PIXI.scaleModes.DEFAULT; /** * [read-only] Set to true once the base texture has loaded * * @property hasLoaded * @type Boolean * @readOnly */ this.hasLoaded = false; /** * The image source that is used to create the texture. * * @property source * @type Image */ this.source = source; this._UID = PIXI._UID++; /** * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) * * @property premultipliedAlpha * @type Boolean * @default true */ this.premultipliedAlpha = true; // used for webGL /** * @property _glTextures * @type Array * @private */ this._glTextures = []; /** * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used * Also the texture must be a power of two size to work * * @property mipmap * @type {Boolean} */ this.mipmap = false; /** * @property _dirty * @type Array * @private */ this._dirty = [true, true, true, true]; if (!source) { return; } if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) { this.hasLoaded = true; this.width = this.source.naturalWidth || this.source.width; this.height = this.source.naturalHeight || this.source.height; this.dirty(); } /** * A BaseTexture can be set to skip the rendering phase in the WebGL Sprite Batch. * * You may want to do this if you have a parent Sprite with no visible texture (i.e. uses the internal `__default` texture) * that has children that you do want to render, without causing a batch flush in the process. * * @property skipRender * @type Boolean */ this.skipRender = false; /** * @property imageUrl * @type String */ this.imageUrl = null; /** * @property _powerOf2 * @type Boolean * @private */ this._powerOf2 = false; }; PIXI.BaseTexture.prototype.constructor = PIXI.BaseTexture; /** * Forces this BaseTexture to be set as loaded, with the given width and height. * Then calls BaseTexture.dirty. * Important for when you don't want to modify the source object by forcing in `complete` or dimension properties it may not have. * * @method forceLoaded * @param {number} width - The new width to force the BaseTexture to be. * @param {number} height - The new height to force the BaseTexture to be. */ PIXI.BaseTexture.prototype.forceLoaded = function(width, height) { this.hasLoaded = true; this.width = width; this.height = height; this.dirty(); }; /** * Destroys this base texture * * @method destroy */ PIXI.BaseTexture.prototype.destroy = function() { if (this.imageUrl) { delete PIXI.BaseTextureCache[this.imageUrl]; delete PIXI.TextureCache[this.imageUrl]; this.imageUrl = null; if (!navigator.isCocoonJS) this.source.src = ''; } else if (this.source) { PIXI.CanvasPool.removeByCanvas(this.source); if (this.source._pixiId) { delete PIXI.BaseTextureCache[this.source._pixiId]; } } this.source = null; this.unloadFromGPU(); }; /** * Changes the source image of the texture * * @method updateSourceImage * @param newSrc {String} the path of the image */ PIXI.BaseTexture.prototype.updateSourceImage = function(newSrc) { this.hasLoaded = false; this.source.src = null; this.source.src = newSrc; }; /** * Sets all glTextures to be dirty. * * @method dirty */ PIXI.BaseTexture.prototype.dirty = function() { for (var i = 0; i < this._glTextures.length; i++) { this._dirty[i] = true; } }; /** * Removes the base texture from the GPU, useful for managing resources on the GPU. * Atexture is still 100% usable and will simply be reuploaded if there is a sprite on screen that is using it. * * @method unloadFromGPU */ PIXI.BaseTexture.prototype.unloadFromGPU = function() { this.dirty(); // delete the webGL textures if any. for (var i = this._glTextures.length - 1; i >= 0; i--) { var glTexture = this._glTextures[i]; var gl = PIXI.glContexts[i]; if(gl && glTexture) { gl.deleteTexture(glTexture); } } this._glTextures.length = 0; this.dirty(); }; /** * Helper function that creates a base texture from the given image url. * If the image is not in the base texture cache it will be created and loaded. * * @static * @method fromImage * @param imageUrl {String} The image url of the texture * @param crossorigin {Boolean} * @param scaleMode {Number} See {{#crossLink "PIXI/scaleModes:property"}}PIXI.scaleModes{{/crossLink}} for possible values * @return {BaseTexture} */ PIXI.BaseTexture.fromImage = function(imageUrl, crossorigin, scaleMode) { var baseTexture = PIXI.BaseTextureCache[imageUrl]; if(crossorigin === undefined && imageUrl.indexOf('data:') === -1) crossorigin = true; if(!baseTexture) { // new Image() breaks tex loading in some versions of Chrome. // See https://code.google.com/p/chromium/issues/detail?id=238071 var image = new Image(); if (crossorigin) { image.crossOrigin = ''; } image.src = imageUrl; baseTexture = new PIXI.BaseTexture(image, scaleMode); baseTexture.imageUrl = imageUrl; PIXI.BaseTextureCache[imageUrl] = baseTexture; // if there is an @2x at the end of the url we are going to assume its a highres image if( imageUrl.indexOf(PIXI.RETINA_PREFIX + '.') !== -1) { baseTexture.resolution = 2; } } return baseTexture; }; /** * Helper function that creates a base texture from the given canvas element. * * @static * @method fromCanvas * @param canvas {Canvas} The canvas element source of the texture * @param scaleMode {Number} See {{#crossLink "PIXI/scaleModes:property"}}PIXI.scaleModes{{/crossLink}} for possible values * @return {BaseTexture} */ PIXI.BaseTexture.fromCanvas = function(canvas, scaleMode) { if (!canvas._pixiId) { canvas._pixiId = 'canvas_' + PIXI.TextureCacheIdGenerator++; } if (canvas.width === 0) { canvas.width = 1; } if (canvas.height === 0) { canvas.height = 1; } var baseTexture = PIXI.BaseTextureCache[canvas._pixiId]; if (!baseTexture) { baseTexture = new PIXI.BaseTexture(canvas, scaleMode); PIXI.BaseTextureCache[canvas._pixiId] = baseTexture; } return baseTexture; }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ PIXI.TextureCache = {}; PIXI.FrameCache = {}; /** * TextureSilentFail is a boolean that defaults to `false`. * If `true` then `PIXI.Texture.setFrame` will no longer throw an error if the texture dimensions are incorrect. * Instead `Texture.valid` will be set to `false` (#1556) * * @type {boolean} */ PIXI.TextureSilentFail = false; PIXI.TextureCacheIdGenerator = 0; /** * A texture stores the information that represents an image or part of an image. It cannot be added * to the display list directly. Instead use it as the texture for a PIXI.Sprite. If no frame is provided then the whole image is used. * * @class Texture * @uses EventTarget * @constructor * @param baseTexture {BaseTexture} The base texture source to create the texture from * @param frame {Rectangle} The rectangle frame of the texture to show * @param [crop] {Rectangle} The area of original texture * @param [trim] {Rectangle} Trimmed texture rectangle */ PIXI.Texture = function(baseTexture, frame, crop, trim) { /** * Does this Texture have any frame data assigned to it? * * @property noFrame * @type Boolean */ this.noFrame = false; if (!frame) { this.noFrame = true; frame = new PIXI.Rectangle(0,0,1,1); } if (baseTexture instanceof PIXI.Texture) { baseTexture = baseTexture.baseTexture; } /** * The base texture that this texture uses. * * @property baseTexture * @type BaseTexture */ this.baseTexture = baseTexture; /** * The frame specifies the region of the base texture that this texture uses * * @property frame * @type Rectangle */ this.frame = frame; /** * The texture trim data. * * @property trim * @type Rectangle */ this.trim = trim; /** * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. * * @property valid * @type Boolean */ this.valid = false; /** * Is this a tiling texture? As used by the likes of a TilingSprite. * * @property isTiling * @type Boolean */ this.isTiling = false; /** * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) * * @property requiresUpdate * @type Boolean */ this.requiresUpdate = false; /** * This will let a renderer know that a tinted parent has updated its texture. * * @property requiresReTint * @type Boolean */ this.requiresReTint = false; /** * The WebGL UV data cache. * * @property _uvs * @type Object * @private */ this._uvs = null; /** * The width of the Texture in pixels. * * @property width * @type Number */ this.width = 0; /** * The height of the Texture in pixels. * * @property height * @type Number */ this.height = 0; /** * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) * * @property crop * @type Rectangle */ this.crop = crop || new PIXI.Rectangle(0, 0, 1, 1); if (baseTexture.hasLoaded) { if (this.noFrame) frame = new PIXI.Rectangle(0, 0, baseTexture.width, baseTexture.height); this.setFrame(frame); } }; PIXI.Texture.prototype.constructor = PIXI.Texture; /** * Called when the base texture is loaded * * @method onBaseTextureLoaded * @private */ PIXI.Texture.prototype.onBaseTextureLoaded = function() { var baseTexture = this.baseTexture; if (this.noFrame) { this.frame = new PIXI.Rectangle(0, 0, baseTexture.width, baseTexture.height); } this.setFrame(this.frame); }; /** * Destroys this texture * * @method destroy * @param destroyBase {Boolean} Whether to destroy the base texture as well */ PIXI.Texture.prototype.destroy = function(destroyBase) { if (destroyBase) this.baseTexture.destroy(); this.valid = false; }; /** * Specifies the region of the baseTexture that this texture will use. * * @method setFrame * @param frame {Rectangle} The frame of the texture to set it to */ PIXI.Texture.prototype.setFrame = function(frame) { this.noFrame = false; this.frame = frame; this.width = frame.width; this.height = frame.height; this.crop.x = frame.x; this.crop.y = frame.y; this.crop.width = frame.width; this.crop.height = frame.height; if (!this.trim && (frame.x + frame.width > this.baseTexture.width || frame.y + frame.height > this.baseTexture.height)) { if (!PIXI.TextureSilentFail) { throw new Error('Texture Error: frame does not fit inside the base Texture dimensions ' + this); } this.valid = false; return; } this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; if (this.trim) { this.width = this.trim.width; this.height = this.trim.height; this.frame.width = this.trim.width; this.frame.height = this.trim.height; } if (this.valid) this._updateUvs(); }; /** * Updates the internal WebGL UV cache. * * @method _updateUvs * @private */ PIXI.Texture.prototype._updateUvs = function() { if(!this._uvs)this._uvs = new PIXI.TextureUvs(); var frame = this.crop; var tw = this.baseTexture.width; var th = this.baseTexture.height; this._uvs.x0 = frame.x / tw; this._uvs.y0 = frame.y / th; this._uvs.x1 = (frame.x + frame.width) / tw; this._uvs.y1 = frame.y / th; this._uvs.x2 = (frame.x + frame.width) / tw; this._uvs.y2 = (frame.y + frame.height) / th; this._uvs.x3 = frame.x / tw; this._uvs.y3 = (frame.y + frame.height) / th; }; /** * Helper function that creates a Texture object from the given image url. * If the image is not in the texture cache it will be created and loaded. * * @static * @method fromImage * @param imageUrl {String} The image url of the texture * @param crossorigin {Boolean} Whether requests should be treated as crossorigin * @param scaleMode {Number} See {{#crossLink "PIXI/scaleModes:property"}}PIXI.scaleModes{{/crossLink}} for possible values * @return {Texture} */ PIXI.Texture.fromImage = function(imageUrl, crossorigin, scaleMode) { var texture = PIXI.TextureCache[imageUrl]; if(!texture) { texture = new PIXI.Texture(PIXI.BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); PIXI.TextureCache[imageUrl] = texture; } return texture; }; /** * Helper function that returns a Texture objected based on the given frame id. * If the frame id is not in the texture cache an error will be thrown. * * @static * @method fromFrame * @param frameId {String} The frame id of the texture * @return {Texture} */ PIXI.Texture.fromFrame = function(frameId) { var texture = PIXI.TextureCache[frameId]; if(!texture) throw new Error('The frameId "' + frameId + '" does not exist in the texture cache '); return texture; }; /** * Helper function that creates a new a Texture based on the given canvas element. * * @static * @method fromCanvas * @param canvas {Canvas} The canvas element source of the texture * @param scaleMode {Number} See {{#crossLink "PIXI/scaleModes:property"}}PIXI.scaleModes{{/crossLink}} for possible values * @return {Texture} */ PIXI.Texture.fromCanvas = function(canvas, scaleMode) { var baseTexture = PIXI.BaseTexture.fromCanvas(canvas, scaleMode); return new PIXI.Texture(baseTexture); }; /** * Adds a texture to the global PIXI.TextureCache. This cache is shared across the whole PIXI object. * * @static * @method addTextureToCache * @param texture {Texture} The Texture to add to the cache. * @param id {String} The id that the texture will be stored against. */ PIXI.Texture.addTextureToCache = function(texture, id) { PIXI.TextureCache[id] = texture; }; /** * Remove a texture from the global PIXI.TextureCache. * * @static * @method removeTextureFromCache * @param id {String} The id of the texture to be removed * @return {Texture} The texture that was removed */ PIXI.Texture.removeTextureFromCache = function(id) { var texture = PIXI.TextureCache[id]; delete PIXI.TextureCache[id]; delete PIXI.BaseTextureCache[id]; return texture; }; PIXI.TextureUvs = function() { this.x0 = 0; this.y0 = 0; this.x1 = 0; this.y1 = 0; this.x2 = 0; this.y2 = 0; this.x3 = 0; this.y3 = 0; }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * A RenderTexture is a special texture that allows any Pixi display object to be rendered to it. * * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded otherwise black rectangles will be drawn instead. * * A RenderTexture takes a snapshot of any Display Object given to its render method. The position and rotation of the given Display Objects is ignored. For example: * * var renderTexture = new PIXI.RenderTexture(800, 600); * var sprite = PIXI.Sprite.fromImage("spinObj_01.png"); * sprite.position.x = 800/2; * sprite.position.y = 600/2; * sprite.anchor.x = 0.5; * sprite.anchor.y = 0.5; * renderTexture.render(sprite); * * The Sprite in this case will be rendered to a position of 0,0. To render this sprite at its actual position a DisplayObjectContainer should be used: * * var doc = new PIXI.DisplayObjectContainer(); * doc.addChild(sprite); * renderTexture.render(doc); // Renders to center of renderTexture * * @class RenderTexture * @extends Texture * @constructor * @param width {Number} The width of the render texture * @param height {Number} The height of the render texture * @param renderer {CanvasRenderer|WebGLRenderer} The renderer used for this RenderTexture * @param scaleMode {Number} See {{#crossLink "PIXI/scaleModes:property"}}PIXI.scaleModes{{/crossLink}} for possible values * @param resolution {Number} The resolution of the texture being generated */ PIXI.RenderTexture = function(width, height, renderer, scaleMode, resolution) { /** * The with of the render texture * * @property width * @type Number */ this.width = width || 100; /** * The height of the render texture * * @property height * @type Number */ this.height = height || 100; /** * The Resolution of the texture. * * @property resolution * @type Number */ this.resolution = resolution || 1; /** * The framing rectangle of the render texture * * @property frame * @type Rectangle */ this.frame = new PIXI.Rectangle(0, 0, this.width * this.resolution, this.height * this.resolution); /** * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) * * @property crop * @type Rectangle */ this.crop = new PIXI.Rectangle(0, 0, this.width * this.resolution, this.height * this.resolution); /** * The base texture object that this texture uses * * @property baseTexture * @type BaseTexture */ this.baseTexture = new PIXI.BaseTexture(); this.baseTexture.width = this.width * this.resolution; this.baseTexture.height = this.height * this.resolution; this.baseTexture._glTextures = []; this.baseTexture.resolution = this.resolution; this.baseTexture.scaleMode = scaleMode || PIXI.scaleModes.DEFAULT; this.baseTexture.hasLoaded = true; PIXI.Texture.call(this, this.baseTexture, new PIXI.Rectangle(0, 0, this.width * this.resolution, this.height * this.resolution) ); /** * The renderer this RenderTexture uses. A RenderTexture can only belong to one renderer at the moment if its webGL. * * @property renderer * @type CanvasRenderer|WebGLRenderer */ this.renderer = renderer || PIXI.defaultRenderer; if (this.renderer.type === PIXI.WEBGL_RENDERER) { var gl = this.renderer.gl; this.baseTexture._dirty[gl.id] = false; this.textureBuffer = new PIXI.FilterTexture(gl, this.width, this.height, this.baseTexture.scaleMode); this.baseTexture._glTextures[gl.id] = this.textureBuffer.texture; this.render = this.renderWebGL; this.projection = new PIXI.Point(this.width * 0.5, -this.height * 0.5); } else { this.render = this.renderCanvas; this.textureBuffer = new PIXI.CanvasBuffer(this.width * this.resolution, this.height * this.resolution); this.baseTexture.source = this.textureBuffer.canvas; } /** * @property valid * @type Boolean */ this.valid = true; this.tempMatrix = new Phaser.Matrix(); this._updateUvs(); }; PIXI.RenderTexture.prototype = Object.create(PIXI.Texture.prototype); PIXI.RenderTexture.prototype.constructor = PIXI.RenderTexture; /** * Resizes the RenderTexture. * * @method resize * @param width {Number} The width to resize to. * @param height {Number} The height to resize to. * @param updateBase {Boolean} Should the baseTexture.width and height values be resized as well? */ PIXI.RenderTexture.prototype.resize = function(width, height, updateBase) { if (width === this.width && height === this.height)return; this.valid = (width > 0 && height > 0); this.width = width; this.height = height; this.frame.width = this.crop.width = width * this.resolution; this.frame.height = this.crop.height = height * this.resolution; if (updateBase) { this.baseTexture.width = this.width * this.resolution; this.baseTexture.height = this.height * this.resolution; } if (this.renderer.type === PIXI.WEBGL_RENDERER) { this.projection.x = this.width / 2; this.projection.y = -this.height / 2; } if(!this.valid)return; this.textureBuffer.resize(this.width, this.height); }; /** * Clears the RenderTexture. * * @method clear */ PIXI.RenderTexture.prototype.clear = function() { if (!this.valid) { return; } if (this.renderer.type === PIXI.WEBGL_RENDERER) { this.renderer.gl.bindFramebuffer(this.renderer.gl.FRAMEBUFFER, this.textureBuffer.frameBuffer); } this.textureBuffer.clear(); }; /** * This function will draw the display object to the texture. * * @method renderWebGL * @param displayObject {DisplayObject} The display object to render this texture on * @param [matrix] {Matrix} Optional matrix to apply to the display object before rendering. * @param [clear] {Boolean} If true the texture will be cleared before the displayObject is drawn * @private */ PIXI.RenderTexture.prototype.renderWebGL = function(displayObject, matrix, clear) { if (!this.valid || displayObject.alpha === 0) { return; } // Let's create a nice matrix to apply to our display object. // Frame buffers come in upside down so we need to flip the matrix. var wt = displayObject.worldTransform; wt.identity(); wt.translate(0, this.projection.y * 2); if (matrix) { wt.append(matrix); } wt.scale(1, -1); // Time to update all the children of the displayObject with the new matrix. for (var i = 0; i < displayObject.children.length; i++) { displayObject.children[i].updateTransform(); } // Time for the webGL fun stuff! var gl = this.renderer.gl; gl.viewport(0, 0, this.width * this.resolution, this.height * this.resolution); gl.bindFramebuffer(gl.FRAMEBUFFER, this.textureBuffer.frameBuffer ); if (clear) { this.textureBuffer.clear(); } this.renderer.spriteBatch.dirty = true; this.renderer.renderDisplayObject(displayObject, this.projection, this.textureBuffer.frameBuffer, matrix); this.renderer.spriteBatch.dirty = true; }; /** * This function will draw the display object to the texture. * * @method renderCanvas * @param displayObject {DisplayObject} The display object to render this texture on * @param [matrix] {Matrix} Optional matrix to apply to the display object before rendering. * @param [clear] {Boolean} If true the texture will be cleared before the displayObject is drawn * @private */ PIXI.RenderTexture.prototype.renderCanvas = function(displayObject, matrix, clear) { if (!this.valid || displayObject.alpha === 0) { return; } // Let's create a nice matrix to apply to our display object. // Frame buffers come in upside down so we need to flip the matrix. var wt = displayObject.worldTransform; wt.identity(); if (matrix) { wt.append(matrix); } // Time to update all the children of the displayObject with the new matrix (what new matrix? there isn't one!) for (var i = 0; i < displayObject.children.length; i++) { displayObject.children[i].updateTransform(); } if (clear) { this.textureBuffer.clear(); } var realResolution = this.renderer.resolution; this.renderer.resolution = this.resolution; this.renderer.renderDisplayObject(displayObject, this.textureBuffer.context, matrix); this.renderer.resolution = realResolution; }; /** * Will return a HTML Image of the texture * * @method getImage * @return {Image} */ PIXI.RenderTexture.prototype.getImage = function() { var image = new Image(); image.src = this.getBase64(); return image; }; /** * Will return a base64 encoded string of this texture. It works by calling RenderTexture.getCanvas and then running toDataURL on that. * * @method getBase64 * @return {String} A base64 encoded string of the texture. */ PIXI.RenderTexture.prototype.getBase64 = function() { return this.getCanvas().toDataURL(); }; /** * Creates a Canvas element, renders this RenderTexture to it and then returns it. * * @method getCanvas * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. */ PIXI.RenderTexture.prototype.getCanvas = function() { if (this.renderer.type === PIXI.WEBGL_RENDERER) { var gl = this.renderer.gl; var width = this.textureBuffer.width; var height = this.textureBuffer.height; var webGLPixels = new Uint8Array(4 * width * height); gl.bindFramebuffer(gl.FRAMEBUFFER, this.textureBuffer.frameBuffer); gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); gl.bindFramebuffer(gl.FRAMEBUFFER, null); var tempCanvas = new PIXI.CanvasBuffer(width, height); var canvasData = tempCanvas.context.getImageData(0, 0, width, height); canvasData.data.set(webGLPixels); tempCanvas.context.putImageData(canvasData, 0, 0); return tempCanvas.canvas; } else { return this.textureBuffer.canvas; } }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * This is the base class for creating a PIXI filter. Currently only webGL supports filters. * If you want to make a custom filter this should be your base class. * @class AbstractFilter * @constructor * @param fragmentSrc {Array} The fragment source in an array of strings. * @param uniforms {Object} An object containing the uniforms for this filter. */ PIXI.AbstractFilter = function(fragmentSrc, uniforms) { /** * An array of passes - some filters contain a few steps this array simply stores the steps in a liniear fashion. * For example the blur filter has two passes blurX and blurY. * @property passes * @type Array(Filter) * @private */ this.passes = [this]; /** * @property shaders * @type Array(Shader) * @private */ this.shaders = []; /** * @property dirty * @type Boolean */ this.dirty = true; /** * @property padding * @type Number */ this.padding = 0; /** * @property uniforms * @type object * @private */ this.uniforms = uniforms || {}; /** * @property fragmentSrc * @type Array * @private */ this.fragmentSrc = fragmentSrc || []; }; PIXI.AbstractFilter.prototype.constructor = PIXI.AbstractFilter; /** * Syncs the uniforms between the class object and the shaders. * * @method syncUniforms */ PIXI.AbstractFilter.prototype.syncUniforms = function() { for(var i=0,j=this.shaders.length; i 0) { var paddingX = this.canvasPadding / this.worldTransform.a; var paddingY = this.canvasPadding / this.worldTransform.d; var centerX = (x0 + x1 + x2) / 3; var centerY = (y0 + y1 + y2) / 3; var normX = x0 - centerX; var normY = y0 - centerY; var dist = Math.sqrt(normX * normX + normY * normY); x0 = centerX + (normX / dist) * (dist + paddingX); y0 = centerY + (normY / dist) * (dist + paddingY); // normX = x1 - centerX; normY = y1 - centerY; dist = Math.sqrt(normX * normX + normY * normY); x1 = centerX + (normX / dist) * (dist + paddingX); y1 = centerY + (normY / dist) * (dist + paddingY); normX = x2 - centerX; normY = y2 - centerY; dist = Math.sqrt(normX * normX + normY * normY); x2 = centerX + (normX / dist) * (dist + paddingX); y2 = centerY + (normY / dist) * (dist + paddingY); } context.save(); context.beginPath(); context.moveTo(x0, y0); context.lineTo(x1, y1); context.lineTo(x2, y2); context.closePath(); context.clip(); // Compute matrix transform var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); context.transform(deltaA / delta, deltaD / delta, deltaB / delta, deltaE / delta, deltaC / delta, deltaF / delta); context.drawImage(textureSource, 0, 0); context.restore(); }; /** * Renders a flat strip * * @method renderStripFlat * @param strip {Strip} The Strip to render * @private */ PIXI.Strip.prototype.renderStripFlat = function(strip) { var context = this.context; var vertices = strip.vertices; var length = vertices.length/2; this.count++; context.beginPath(); for (var i=1; i < length-2; i++) { // draw some triangles! var index = i*2; var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; context.moveTo(x0, y0); context.lineTo(x1, y1); context.lineTo(x2, y2); } context.fillStyle = '#FF0000'; context.fill(); context.closePath(); }; /* PIXI.Strip.prototype.setTexture = function(texture) { //TODO SET THE TEXTURES //TODO VISIBILITY // stop current texture this.texture = texture; this.width = texture.frame.width; this.height = texture.frame.height; this.updateFrame = true; }; */ /** * When the texture is updated, this event will fire to update the scale and frame * * @method onTextureUpdate * @param event * @private */ PIXI.Strip.prototype.onTextureUpdate = function() { this.updateFrame = true; }; /** * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. * * @method getBounds * @param matrix {Matrix} the transformation matrix of the sprite * @return {Rectangle} the framing rectangle */ PIXI.Strip.prototype.getBounds = function(matrix) { var worldTransform = matrix || this.worldTransform; var a = worldTransform.a; var b = worldTransform.b; var c = worldTransform.c; var d = worldTransform.d; var tx = worldTransform.tx; var ty = worldTransform.ty; var maxX = -Infinity; var maxY = -Infinity; var minX = Infinity; var minY = Infinity; var vertices = this.vertices; for (var i = 0, n = vertices.length; i < n; i += 2) { var rawX = vertices[i], rawY = vertices[i + 1]; var x = (a * rawX) + (c * rawY) + tx; var y = (d * rawY) + (b * rawX) + ty; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; } if (minX === -Infinity || maxY === Infinity) { return PIXI.EmptyRectangle; } var bounds = this._bounds; bounds.x = minX; bounds.width = maxX - minX; bounds.y = minY; bounds.height = maxY - minY; // store a reference so that if this function gets called again in the render cycle we do not have to recalculate this._currentBounds = bounds; return bounds; }; /** * Different drawing buffer modes supported * * @property * @type {{TRIANGLE_STRIP: number, TRIANGLES: number}} * @static */ PIXI.Strip.DrawModes = { TRIANGLE_STRIP: 0, TRIANGLES: 1 }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 * @copyright Mat Groves, Rovanion Luckey */ /** * * @class Rope * @constructor * @extends Strip * @param {Texture} texture - The texture to use on the rope. * @param {Array} points - An array of {PIXI.Point}. * */ PIXI.Rope = function(texture, points) { PIXI.Strip.call( this, texture ); this.points = points; this.vertices = new PIXI.Float32Array(points.length * 4); this.uvs = new PIXI.Float32Array(points.length * 4); this.colors = new PIXI.Float32Array(points.length * 2); this.indices = new PIXI.Uint16Array(points.length * 2); this.refresh(); }; // constructor PIXI.Rope.prototype = Object.create( PIXI.Strip.prototype ); PIXI.Rope.prototype.constructor = PIXI.Rope; /* * Refreshes * * @method refresh */ PIXI.Rope.prototype.refresh = function() { var points = this.points; if(points.length < 1) return; var uvs = this.uvs; var lastPoint = points[0]; var indices = this.indices; var colors = this.colors; this.count-=0.2; uvs[0] = 0; uvs[1] = 0; uvs[2] = 0; uvs[3] = 1; colors[0] = 1; colors[1] = 1; indices[0] = 0; indices[1] = 1; var total = points.length, point, index, amount; for (var i = 1; i < total; i++) { point = points[i]; index = i * 4; // time to do some smart drawing! amount = i / (total-1); if(i%2) { uvs[index] = amount; uvs[index+1] = 0; uvs[index+2] = amount; uvs[index+3] = 1; } else { uvs[index] = amount; uvs[index+1] = 0; uvs[index+2] = amount; uvs[index+3] = 1; } index = i * 2; colors[index] = 1; colors[index+1] = 1; index = i * 2; indices[index] = index; indices[index + 1] = index + 1; lastPoint = point; } }; /* * Updates the object transform for rendering * * @method updateTransform * @private */ PIXI.Rope.prototype.updateTransform = function() { var points = this.points; if(points.length < 1)return; var lastPoint = points[0]; var nextPoint; var perp = {x:0, y:0}; this.count-=0.2; var vertices = this.vertices; var total = points.length, point, index, ratio, perpLength, num; for (var i = 0; i < total; i++) { point = points[i]; index = i * 4; if(i < points.length-1) { nextPoint = points[i+1]; } else { nextPoint = point; } perp.y = -(nextPoint.x - lastPoint.x); perp.x = nextPoint.y - lastPoint.y; ratio = (1 - (i / (total-1))) * 10; if(ratio > 1) ratio = 1; perpLength = Math.sqrt(perp.x * perp.x + perp.y * perp.y); num = this.texture.height / 2; //(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio; perp.x /= perpLength; perp.y /= perpLength; perp.x *= num; perp.y *= num; vertices[index] = point.x + perp.x; vertices[index+1] = point.y + perp.y; vertices[index+2] = point.x - perp.x; vertices[index+3] = point.y - perp.y; lastPoint = point; } PIXI.DisplayObjectContainer.prototype.updateTransform.call( this ); }; /* * Sets the texture that the Rope will use * * @method setTexture * @param texture {Texture} the texture that will be used */ PIXI.Rope.prototype.setTexture = function(texture) { // stop current texture this.texture = texture; //this.updateFrame = true; }; /** * @author Mat Groves http://matgroves.com/ */ /** * A tiling sprite is a fast way of rendering a tiling image * * @class TilingSprite * @extends Sprite * @constructor * @param texture {Texture} the texture of the tiling sprite * @param width {Number} the width of the tiling sprite * @param height {Number} the height of the tiling sprite */ PIXI.TilingSprite = function(texture, width, height) { PIXI.Sprite.call(this, texture); /** * The width of the tiling sprite * * @property width * @type Number */ this._width = width || 128; /** * The height of the tiling sprite * * @property height * @type Number */ this._height = height || 128; /** * The scaling of the image that is being tiled * * @property tileScale * @type Point */ this.tileScale = new PIXI.Point(1, 1); /** * A point that represents the scale of the texture object * * @property tileScaleOffset * @type Point */ this.tileScaleOffset = new PIXI.Point(1, 1); /** * The offset position of the image that is being tiled * * @property tilePosition * @type Point */ this.tilePosition = new PIXI.Point(); /** * Whether this sprite is renderable or not * * @property renderable * @type Boolean * @default true */ this.renderable = true; /** * The tint applied to the sprite. This is a hex value * * @property tint * @type Number * @default 0xFFFFFF */ this.tint = 0xFFFFFF; /** * If enabled a green rectangle will be drawn behind the generated tiling texture, allowing you to visually * debug the texture being used. * * @property textureDebug * @type Boolean */ this.textureDebug = false; /** * The blend mode to be applied to the sprite * * @property blendMode * @type Number * @default PIXI.blendModes.NORMAL; */ this.blendMode = PIXI.blendModes.NORMAL; /** * The CanvasBuffer object that the tiled texture is drawn to. * * @property canvasBuffer * @type PIXI.CanvasBuffer */ this.canvasBuffer = null; /** * An internal Texture object that holds the tiling texture that was generated from TilingSprite.texture. * * @property tilingTexture * @type PIXI.Texture */ this.tilingTexture = null; /** * The Context fill pattern that is used to draw the TilingSprite in Canvas mode only (will be null in WebGL). * * @property tilePattern * @type PIXI.Texture */ this.tilePattern = null; /** * If true the TilingSprite will run generateTexture on its **next** render pass. * This is set by the likes of Phaser.LoadTexture.setFrame. * * @property refreshTexture * @type Boolean * @default true */ this.refreshTexture = true; this.frameWidth = 0; this.frameHeight = 0; }; PIXI.TilingSprite.prototype = Object.create(PIXI.Sprite.prototype); PIXI.TilingSprite.prototype.constructor = PIXI.TilingSprite; PIXI.TilingSprite.prototype.setTexture = function(texture) { if (this.texture !== texture) { this.texture = texture; this.refreshTexture = true; this.cachedTint = 0xFFFFFF; } }; /** * Renders the object using the WebGL renderer * * @method _renderWebGL * @param renderSession {RenderSession} * @private */ PIXI.TilingSprite.prototype._renderWebGL = function(renderSession) { if (!this.visible || !this.renderable || this.alpha === 0) { return; } if (this._mask) { renderSession.spriteBatch.stop(); renderSession.maskManager.pushMask(this.mask, renderSession); renderSession.spriteBatch.start(); } if (this._filters) { renderSession.spriteBatch.flush(); renderSession.filterManager.pushFilter(this._filterBlock); } if (this.refreshTexture) { this.generateTilingTexture(true, renderSession); if (this.tilingTexture) { if (this.tilingTexture.needsUpdate) { renderSession.renderer.updateTexture(this.tilingTexture.baseTexture); this.tilingTexture.needsUpdate = false; } } else { return; } } renderSession.spriteBatch.renderTilingSprite(this); for (var i = 0; i < this.children.length; i++) { this.children[i]._renderWebGL(renderSession); } renderSession.spriteBatch.stop(); if (this._filters) { renderSession.filterManager.popFilter(); } if (this._mask) { renderSession.maskManager.popMask(this._mask, renderSession); } renderSession.spriteBatch.start(); }; /** * Renders the object using the Canvas renderer * * @method _renderCanvas * @param renderSession {RenderSession} * @private */ PIXI.TilingSprite.prototype._renderCanvas = function(renderSession) { if (!this.visible || !this.renderable || this.alpha === 0) { return; } var context = renderSession.context; if (this._mask) { renderSession.maskManager.pushMask(this._mask, renderSession); } context.globalAlpha = this.worldAlpha; var wt = this.worldTransform; var resolution = renderSession.resolution; var tx = (wt.tx * resolution) + renderSession.shakeX; var ty = (wt.ty * resolution) + renderSession.shakeY; context.setTransform(wt.a * resolution, wt.b * resolution, wt.c * resolution, wt.d * resolution, tx, ty); if (this.refreshTexture) { this.generateTilingTexture(false, renderSession); if (this.tilingTexture) { this.tilePattern = context.createPattern(this.tilingTexture.baseTexture.source, 'repeat'); } else { return; } } var sessionBlendMode = renderSession.currentBlendMode; // Check blend mode if (this.blendMode !== renderSession.currentBlendMode) { renderSession.currentBlendMode = this.blendMode; context.globalCompositeOperation = PIXI.blendModesCanvas[renderSession.currentBlendMode]; } var tilePosition = this.tilePosition; var tileScale = this.tileScale; tilePosition.x %= this.tilingTexture.baseTexture.width; tilePosition.y %= this.tilingTexture.baseTexture.height; // Translate context.scale(tileScale.x, tileScale.y); context.translate(tilePosition.x + (this.anchor.x * -this._width), tilePosition.y + (this.anchor.y * -this._height)); context.fillStyle = this.tilePattern; var tx = -tilePosition.x; var ty = -tilePosition.y; var tw = this._width / tileScale.x; var th = this._height / tileScale.y; // Allow for pixel rounding if (renderSession.roundPixels) { tx |= 0; ty |= 0; tw |= 0; th |= 0; } context.fillRect(tx, ty, tw, th); // Translate back again context.scale(1 / tileScale.x, 1 / tileScale.y); context.translate(-tilePosition.x + (this.anchor.x * this._width), -tilePosition.y + (this.anchor.y * this._height)); if (this._mask) { renderSession.maskManager.popMask(renderSession); } for (var i = 0; i < this.children.length; i++) { this.children[i]._renderCanvas(renderSession); } // Reset blend mode if (sessionBlendMode !== this.blendMode) { renderSession.currentBlendMode = sessionBlendMode; context.globalCompositeOperation = PIXI.blendModesCanvas[sessionBlendMode]; } }; /** * When the texture is updated, this event will fire to update the scale and frame * * @method onTextureUpdate * @param event * @private */ PIXI.TilingSprite.prototype.onTextureUpdate = function() { // overriding the sprite version of this! }; /** * * @method generateTilingTexture * * @param forcePowerOfTwo {Boolean} Whether we want to force the texture to be a power of two * @param renderSession {RenderSession} */ PIXI.TilingSprite.prototype.generateTilingTexture = function(forcePowerOfTwo, renderSession) { if (!this.texture.baseTexture.hasLoaded) { return; } var texture = this.texture; var frame = texture.frame; var targetWidth = this._frame.sourceSizeW || this._frame.width; var targetHeight = this._frame.sourceSizeH || this._frame.height; var dx = 0; var dy = 0; if (this._frame.trimmed) { dx = this._frame.spriteSourceSizeX; dy = this._frame.spriteSourceSizeY; } if (forcePowerOfTwo) { targetWidth = PIXI.getNextPowerOfTwo(targetWidth); targetHeight = PIXI.getNextPowerOfTwo(targetHeight); } if (this.canvasBuffer) { this.canvasBuffer.resize(targetWidth, targetHeight); this.tilingTexture.baseTexture.width = targetWidth; this.tilingTexture.baseTexture.height = targetHeight; this.tilingTexture.needsUpdate = true; } else { this.canvasBuffer = new PIXI.CanvasBuffer(targetWidth, targetHeight); this.tilingTexture = PIXI.Texture.fromCanvas(this.canvasBuffer.canvas); this.tilingTexture.isTiling = true; this.tilingTexture.needsUpdate = true; } if (this.textureDebug) { this.canvasBuffer.context.strokeStyle = '#00ff00'; this.canvasBuffer.context.strokeRect(0, 0, targetWidth, targetHeight); } // If a sprite sheet we need this: var w = texture.crop.width; var h = texture.crop.height; if (w !== targetWidth || h !== targetHeight) { w = targetWidth; h = targetHeight; } this.canvasBuffer.context.drawImage(texture.baseTexture.source, texture.crop.x, texture.crop.y, texture.crop.width, texture.crop.height, dx, dy, w, h); this.tileScaleOffset.x = frame.width / targetWidth; this.tileScaleOffset.y = frame.height / targetHeight; this.refreshTexture = false; this.tilingTexture.baseTexture._powerOf2 = true; }; /** * Returns the framing rectangle of the sprite as a PIXI.Rectangle object * * @method getBounds * @return {Rectangle} the framing rectangle */ PIXI.TilingSprite.prototype.getBounds = function() { var width = this._width; var height = this._height; var w0 = width * (1-this.anchor.x); var w1 = width * -this.anchor.x; var h0 = height * (1-this.anchor.y); var h1 = height * -this.anchor.y; var worldTransform = this.worldTransform; var a = worldTransform.a; var b = worldTransform.b; var c = worldTransform.c; var d = worldTransform.d; var tx = worldTransform.tx; var ty = worldTransform.ty; var x1 = a * w1 + c * h1 + tx; var y1 = d * h1 + b * w1 + ty; var x2 = a * w0 + c * h1 + tx; var y2 = d * h1 + b * w0 + ty; var x3 = a * w0 + c * h0 + tx; var y3 = d * h0 + b * w0 + ty; var x4 = a * w1 + c * h0 + tx; var y4 = d * h0 + b * w1 + ty; var maxX = -Infinity; var maxY = -Infinity; var minX = Infinity; var minY = Infinity; minX = x1 < minX ? x1 : minX; minX = x2 < minX ? x2 : minX; minX = x3 < minX ? x3 : minX; minX = x4 < minX ? x4 : minX; minY = y1 < minY ? y1 : minY; minY = y2 < minY ? y2 : minY; minY = y3 < minY ? y3 : minY; minY = y4 < minY ? y4 : minY; maxX = x1 > maxX ? x1 : maxX; maxX = x2 > maxX ? x2 : maxX; maxX = x3 > maxX ? x3 : maxX; maxX = x4 > maxX ? x4 : maxX; maxY = y1 > maxY ? y1 : maxY; maxY = y2 > maxY ? y2 : maxY; maxY = y3 > maxY ? y3 : maxY; maxY = y4 > maxY ? y4 : maxY; var bounds = this._bounds; bounds.x = minX; bounds.width = maxX - minX; bounds.y = minY; bounds.height = maxY - minY; // store a reference so that if this function gets called again in the render cycle we do not have to recalculate this._currentBounds = bounds; return bounds; }; PIXI.TilingSprite.prototype.destroy = function () { PIXI.Sprite.prototype.destroy.call(this); if (this.canvasBuffer) { this.canvasBuffer.destroy(); this.canvasBuffer = null; } this.tileScale = null; this.tileScaleOffset = null; this.tilePosition = null; if (this.tilingTexture) { this.tilingTexture.destroy(true); this.tilingTexture = null; } }; /** * The width of the sprite, setting this will actually modify the scale to achieve the value set * * @property width * @type Number */ Object.defineProperty(PIXI.TilingSprite.prototype, 'width', { get: function() { return this._width; }, set: function(value) { this._width = value; } }); /** * The height of the TilingSprite, setting this will actually modify the scale to achieve the value set * * @property height * @type Number */ Object.defineProperty(PIXI.TilingSprite.prototype, 'height', { get: function() { return this._height; }, set: function(value) { this._height = value; } }); /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { exports = module.exports = PIXI; } exports.PIXI = PIXI; } else if (typeof define !== 'undefined' && define.amd) { define('PIXI', (function() { return root.PIXI = PIXI; })() ); } else { root.PIXI = PIXI; } return PIXI; }).call(this);