/** * @license * pixi.js - v1.5.1 * Copyright (c) 2012-2014, Mat Groves * http://goodboydigital.com/ * * Compiled: 2014-02-13 * * pixi.js is licensed under the MIT License. * http://www.opensource.org/licenses/mit-license.php */ /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ (function(){ var root = this; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * @module PIXI */ var PIXI = PIXI || {}; /* * * This file contains a lot of pixi consts which are used across the rendering engine * @class Consts */ PIXI.WEBGL_RENDERER = 0; PIXI.CANVAS_RENDERER = 1; // useful for testing against if your lib is using pixi. PIXI.VERSION = "v1.5.1"; // the various blend modes supported by pixi PIXI.blendModes = { NORMAL:0, ADD:1, MULTIPLY:2, SCREEN:3, OVERLAY:4, DARKEN:5, LIGHTEN:6, COLOR_DODGE:7, COLOR_BURN:8, HARD_LIGHT:9, SOFT_LIGHT:10, DIFFERENCE:11, EXCLUSION:12, HUE:13, SATURATION:14, COLOR:15, LUMINOSITY:16 }; // the scale modes PIXI.scaleModes = { DEFAULT:0, LINEAR:0, NEAREST:1 }; // interaction frequency PIXI.INTERACTION_FREQUENCY = 30; PIXI.AUTO_PREVENT_DEFAULT = true; PIXI.RAD_TO_DEG = 180 / Math.PI; PIXI.DEG_TO_RAD = Math.PI / 180; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * The Point object represents a location in a two-dimensional coordinate system, where x represents the horizontal axis and y represents the vertical axis. * * @class Point * @constructor * @param x {Number} position of the point on the x axis * @param y {Number} position of the point on the y axis */ PIXI.Point = function(x, y) { /** * @property x * @type Number * @default 0 */ this.x = x || 0; /** * @property y * @type Number * @default 0 */ this.y = y || 0; }; /** * Creates a clone of this point * * @method clone * @return {Point} a copy of the point */ PIXI.Point.prototype.clone = function() { return new PIXI.Point(this.x, this.y); }; // constructor PIXI.Point.prototype.constructor = PIXI.Point; PIXI.Point.prototype.set = function(x, y) { this.x = x || 0; this.y = y || ( (y !== 0) ? this.x : 0 ) ; }; /** * @author Mat Groves http://matgroves.com/ */ /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * * @class Rectangle * @constructor * @param x {Number} The X coord of the upper-left corner of the rectangle * @param y {Number} The Y coord of the upper-left corner of the rectangle * @param width {Number} The overall width of this rectangle * @param height {Number} The overall height of this rectangle */ PIXI.Rectangle = function(x, y, width, height) { /** * @property x * @type Number * @default 0 */ this.x = x || 0; /** * @property y * @type Number * @default 0 */ this.y = y || 0; /** * @property width * @type Number * @default 0 */ this.width = width || 0; /** * @property height * @type Number * @default 0 */ this.height = height || 0; }; /** * Creates a clone of this Rectangle * * @method clone * @return {Rectangle} a copy of the rectangle */ PIXI.Rectangle.prototype.clone = function() { return new PIXI.Rectangle(this.x, this.y, this.width, this.height); }; /** * Checks whether the x and y coordinates passed to this function are contained within this Rectangle * * @method contains * @param x {Number} The X coordinate of the point to test * @param y {Number} The Y coordinate of the point to test * @return {Boolean} Whether the x/y coords are within this Rectangle */ PIXI.Rectangle.prototype.contains = function(x, y) { if(this.width <= 0 || this.height <= 0) return false; var x1 = this.x; if(x >= x1 && x <= x1 + this.width) { var y1 = this.y; if(y >= y1 && y <= y1 + this.height) { return true; } } return false; }; // constructor PIXI.Rectangle.prototype.constructor = PIXI.Rectangle; PIXI.EmptyRectangle = new PIXI.Rectangle(0,0,0,0); /** * @author Adrien Brault */ /** * @class Polygon * @constructor * @param points* {Array|Array|Point...|Number...} This can be an array of Points that form the polygon, * a flat array of numbers that will be interpreted as [x,y, x,y, ...], or the arguments passed can be * all the points of the polygon e.g. `new PIXI.Polygon(new PIXI.Point(), new PIXI.Point(), ...)`, or the * arguments passed can be flat x,y values e.g. `new PIXI.Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ PIXI.Polygon = function(points) { //if points isn't an array, use arguments as the array if(!(points instanceof Array)) points = Array.prototype.slice.call(arguments); //if this is a flat array of numbers, convert it to points if(typeof points[0] === 'number') { var p = []; for(var i = 0, il = points.length; i < il; i+=2) { p.push( new PIXI.Point(points[i], points[i + 1]) ); } points = p; } this.points = points; }; /** * Creates a clone of this polygon * * @method clone * @return {Polygon} a copy of the polygon */ PIXI.Polygon.prototype.clone = function() { var points = []; for (var i=0; i y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); if(intersect) inside = !inside; } return inside; }; // constructor PIXI.Polygon.prototype.constructor = PIXI.Polygon; /** * @author Chad Engler */ /** * The Circle object can be used to specify a hit area for displayObjects * * @class Circle * @constructor * @param x {Number} The X coordinate of the upper-left corner of the framing rectangle of this circle * @param y {Number} The Y coordinate of the upper-left corner of the framing rectangle of this circle * @param radius {Number} The radius of the circle */ PIXI.Circle = function(x, y, radius) { /** * @property x * @type Number * @default 0 */ this.x = x || 0; /** * @property y * @type Number * @default 0 */ this.y = y || 0; /** * @property radius * @type Number * @default 0 */ this.radius = radius || 0; }; /** * Creates a clone of this Circle instance * * @method clone * @return {Circle} a copy of the polygon */ PIXI.Circle.prototype.clone = function() { return new PIXI.Circle(this.x, this.y, this.radius); }; /** * Checks whether the x, and y coordinates passed to this function are contained within this circle * * @method contains * @param x {Number} The X coordinate of the point to test * @param y {Number} The Y coordinate of the point to test * @return {Boolean} Whether the x/y coordinates are within this polygon */ PIXI.Circle.prototype.contains = function(x, y) { if(this.radius <= 0) return false; var dx = (this.x - x), dy = (this.y - y), r2 = this.radius * this.radius; dx *= dx; dy *= dy; return (dx + dy <= r2); }; // constructor PIXI.Circle.prototype.constructor = PIXI.Circle; /** * @author Chad Engler */ /** * The Ellipse object can be used to specify a hit area for displayObjects * * @class Ellipse * @constructor * @param x {Number} The X coordinate of the upper-left corner of the framing rectangle of this ellipse * @param y {Number} The Y coordinate of the upper-left corner of the framing rectangle of this ellipse * @param width {Number} The overall width of this ellipse * @param height {Number} The overall height of this ellipse */ PIXI.Ellipse = function(x, y, width, height) { /** * @property x * @type Number * @default 0 */ this.x = x || 0; /** * @property y * @type Number * @default 0 */ this.y = y || 0; /** * @property width * @type Number * @default 0 */ this.width = width || 0; /** * @property height * @type Number * @default 0 */ this.height = height || 0; }; /** * Creates a clone of this Ellipse instance * * @method clone * @return {Ellipse} a copy of the ellipse */ PIXI.Ellipse.prototype.clone = function() { return new PIXI.Ellipse(this.x, this.y, this.width, this.height); }; /** * Checks whether the x and y coordinates passed to this function are contained within this ellipse * * @method contains * @param x {Number} The X coordinate of the point to test * @param y {Number} The Y coordinate of the point to test * @return {Boolean} Whether the x/y coords are within this ellipse */ PIXI.Ellipse.prototype.contains = function(x, y) { if(this.width <= 0 || this.height <= 0) return false; //normalize the coords to an ellipse with center 0,0 var normx = ((x - this.x) / this.width), normy = ((y - this.y) / this.height); normx *= normx; normy *= normy; return (normx + normy <= 1); }; /** * Returns the framing rectangle of the ellipse as a PIXI.Rectangle object * * @method getBounds * @return {Rectangle} the framing rectangle */ PIXI.Ellipse.prototype.getBounds = function() { return new PIXI.Rectangle(this.x, this.y, this.width, this.height); }; // constructor PIXI.Ellipse.prototype.constructor = PIXI.Ellipse; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ PIXI.determineMatrixArrayType = function() { return (typeof Float32Array !== 'undefined') ? Float32Array : Array; }; /* * @class Matrix2 * The Matrix2 class will choose the best type of array to use between * a regular javascript Array and a Float32Array if the latter is available * */ PIXI.Matrix2 = PIXI.determineMatrixArrayType(); /* * @class Matrix * The Matrix class is now an object, which makes it a lot faster, * here is a representation of it : * | a | b | tx| * | c | c | ty| * | 0 | 0 | 1 | * */ PIXI.Matrix = function() { this.a = 1; this.b = 0; this.c = 0; this.d = 1; this.tx = 0; this.ty = 0; }; /** * Creates a pixi matrix object based on the array given as a parameter * * @method fromArray * @param array {Array} The array that the matrix will be filled with */ PIXI.Matrix.prototype.fromArray = function(array) { this.a = array[0]; this.b = array[1]; this.c = array[3]; this.d = array[4]; this.tx = array[2]; this.ty = array[5]; }; /** * Creates an array from the current Matrix object * * @method toArray * @param transpose {Boolean} Whether we need to transpose the matrix or not * @return array {Array} the newly created array which contains the matrix */ PIXI.Matrix.prototype.toArray = function(transpose) { if(!this.array) this.array = new Float32Array(9); var array = this.array; if(transpose) { this.array[0] = this.a; this.array[1] = this.c; this.array[2] = 0; this.array[3] = this.b; this.array[4] = this.d; this.array[5] = 0; this.array[6] = this.tx; this.array[7] = this.ty; this.array[8] = 1; } else { this.array[0] = this.a; this.array[1] = this.b; this.array[2] = this.tx; this.array[3] = this.c; this.array[4] = this.d; this.array[5] = this.ty; this.array[6] = 0; this.array[7] = 0; this.array[8] = 1; } return array;//[this.a, this.b, this.tx, this.c, this.d, this.ty, 0, 0, 1]; }; PIXI.identityMatrix = new PIXI.Matrix(); /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * The base class for all objects that are rendered on the screen. * * @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(); /** * The scale factor of the object. * * @property scale * @type Point */ this.scale = new PIXI.Point(1,1);//{x:1, y: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; /** * This is used to indicate if the displayObject should display a mouse hand cursor on rollover * * @property buttonMode * @type Boolean */ this.buttonMode = false; /** * 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] Whether or not the object is interactive, do not toggle directly! use the `interactive` property * * @property _interactive * @type Boolean * @readOnly * @private */ this._interactive = false; /** * This is the cursor that will be used when the mouse is over this object. To enable this the element must have interaction = true and buttonMode = true * * @property defaultCursor * @type String * */ this.defaultCursor = 'pointer'; /** * [read-only] Current transform of the object based on world (parent) factors * * @property worldTransform * @type Mat3 * @readOnly * @private */ this.worldTransform = new PIXI.Matrix(); /** * [NYI] Unknown * * @property color * @type Array<> * @private */ this.color = []; /** * [NYI] Holds whether or not this object is dynamic, for rendering optimization * * @property dynamic * @type Boolean * @private */ this.dynamic = true; // cached sin rotation and cos rotation this._sr = 0; this._cr = 1; /** * The area the filter is applied to * * @property filterArea * @type Rectangle */ this.filterArea = new PIXI.Rectangle(0,0,1,1); /** * 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 _currentBounds * @type Rectangle * @private */ this._mask = null; /* * MOUSE Callbacks */ /** * A callback that is used when the users clicks on the displayObject with their mouse * @method click * @param interactionData {InteractionData} */ /** * A callback that is used when the user clicks the mouse down over the sprite * @method mousedown * @param interactionData {InteractionData} */ /** * A callback that is used when the user releases the mouse that was over the displayObject * for this callback to be fired the mouse must have been pressed down over the displayObject * @method mouseup * @param interactionData {InteractionData} */ /** * A callback that is used when the user releases the mouse that was over the displayObject but is no longer over the displayObject * for this callback to be fired, The touch must have started over the displayObject * @method mouseupoutside * @param interactionData {InteractionData} */ /** * A callback that is used when the users mouse rolls over the displayObject * @method mouseover * @param interactionData {InteractionData} */ /** * A callback that is used when the users mouse leaves the displayObject * @method mouseout * @param interactionData {InteractionData} */ /* * TOUCH Callbacks */ /** * A callback that is used when the users taps on the sprite with their finger * basically a touch version of click * @method tap * @param interactionData {InteractionData} */ /** * A callback that is used when the user touches over the displayObject * @method touchstart * @param interactionData {InteractionData} */ /** * A callback that is used when the user releases a touch over the displayObject * @method touchend * @param interactionData {InteractionData} */ /** * A callback that is used when the user releases the touch that was over the displayObject * for this callback to be fired, The touch must have started over the sprite * @method touchendoutside * @param interactionData {InteractionData} */ }; // constructor PIXI.DisplayObject.prototype.constructor = PIXI.DisplayObject; /** * [Deprecated] Indicates if the sprite will have touch and mouse interactivity. It is false by default * Instead of using this function you can now simply set the interactive property to true or false * * @method setInteractive * @param interactive {Boolean} * @deprecated Simply set the `interactive` property directly */ PIXI.DisplayObject.prototype.setInteractive = function(interactive) { this.interactive = interactive; }; /** * Indicates if the sprite will have touch and mouse interactivity. It is false by default * * @property interactive * @type Boolean * @default false */ Object.defineProperty(PIXI.DisplayObject.prototype, 'interactive', { get: function() { return this._interactive; }, set: function(value) { this._interactive = value; // TODO more to be done here.. // need to sort out a re-crawl! if(this.stage)this.stage.dirty = true; } }); /** * [read-only] Indicates if the sprite is globaly 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' * @property filters * @type Array An array of filters */ 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; } }); /* * Updates the object transform for rendering * * @method updateTransform * @private */ PIXI.DisplayObject.prototype.updateTransform = function() { // TODO OPTIMIZE THIS!! with dirty if(this.rotation !== this.rotationCache) { this.rotationCache = this.rotation; this._sr = Math.sin(this.rotation); this._cr = Math.cos(this.rotation); } // var localTransform = this.localTransform//.toArray(); var parentTransform = this.parent.worldTransform;//.toArray(); var worldTransform = this.worldTransform;//.toArray(); var px = this.pivot.x; var py = this.pivot.y; var a00 = this._cr * this.scale.x, a01 = -this._sr * this.scale.y, a10 = this._sr * this.scale.x, a11 = this._cr * this.scale.y, a02 = this.position.x - a00 * px - py * a01, a12 = this.position.y - a11 * py - px * a10, b00 = parentTransform.a, b01 = parentTransform.b, b10 = parentTransform.c, b11 = parentTransform.d; worldTransform.a = b00 * a00 + b01 * a10; worldTransform.b = b00 * a01 + b01 * a11; worldTransform.tx = b00 * a02 + b01 * a12 + parentTransform.tx; worldTransform.c = b10 * a00 + b11 * a10; worldTransform.d = b10 * a01 + b11 * a11; worldTransform.ty = b10 * a02 + b11 * a12 + parentTransform.ty; this.worldAlpha = this.alpha * this.parent.worldAlpha; }; /** * Retrieves the bounds of the displayObject as a rectangle object * * @method getBounds * @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() { //var matrixCache = this.worldTransform; 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; if(this._interactive)this.stage.dirty = true; }; /** * 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 * @readOnly */ this.children = []; }; // 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) { this.scale.x = value / (this.getLocalBounds().width/this.scale.x); 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) { this.scale.y = value / (this.getLocalBounds().height/this.scale.y); this._height = value; } }); */ /** * Adds a child to the container. * * @method addChild * @param child {DisplayObject} The DisplayObject to add to the container */ PIXI.DisplayObjectContainer.prototype.addChild = function(child) { 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 */ 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); } else { throw new Error(child + ' The index '+ index +' supplied is out of bounds ' + this.children.length); } }; /** * [NYI] Swaps the depth of 2 displayObjects * * @method swapChildren * @param child {DisplayObject} * @param child2 {DisplayObject} * @private */ PIXI.DisplayObjectContainer.prototype.swapChildren = function(child, child2) { if(child === child2) { return; } var index1 = this.children.indexOf(child); var index2 = this.children.indexOf(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 child at the specified index * * @method getChildAt * @param index {Number} The index to get the child from */ PIXI.DisplayObjectContainer.prototype.getChildAt = function(index) { if(index >= 0 && index < this.children.length) { return this.children[index]; } else { throw new Error('The supplied DisplayObjects must be a child of the caller ' + this); } }; /** * Removes a child from the container. * * @method removeChild * @param child {DisplayObject} The DisplayObject to remove */ PIXI.DisplayObjectContainer.prototype.removeChild = function(child) { var index = this.children.indexOf( child ); if ( index !== -1 ) { // update the stage reference.. if(this.stage)child.removeStageReference(); child.parent = undefined; this.children.splice( index, 1 ); } else { throw new Error(child + ' The supplied DisplayObject must be a child of the caller ' + this); } }; /** * Removes all the children * * @method removeAll * NOT tested yet */ /* PIXI.DisplayObjectContainer.prototype.removeAll = function() { for(var i = 0 , j = this.children.length; i < j; i++) { this.removeChild(this.children[i]); } }; */ /* * Updates the container's childrens transform for rendering * * @method updateTransform * @private */ PIXI.DisplayObjectContainer.prototype.updateTransform = function() { //this._currentBounds = null; if(!this.visible)return; PIXI.DisplayObject.prototype.updateTransform.call( this ); for(var i=0,j=this.children.length; i 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; // TODO: 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.DisplayObjectContainer.prototype.getLocalBounds = function() { var matrixCache = this.worldTransform; this.worldTransform = PIXI.identityMatrix; for(var i=0,j=this.children.length; i 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} * @private */ PIXI.Sprite.prototype._renderWebGL = function(renderSession) { // if the sprite is not visible or the alpha is 0 then no need to render this element if(!this.visible || this.alpha <= 0)return; var i,j; // do a quick check to see if this element has a mask or a filter. if(this._mask || this._filters) { var spriteBatch = renderSession.spriteBatch; if(this._mask) { spriteBatch.stop(); renderSession.maskManager.pushMask(this.mask, renderSession); spriteBatch.start(); } if(this._filters) { spriteBatch.flush(); renderSession.filterManager.pushFilter(this._filterBlock); } // add this sprite to the batch spriteBatch.render(this); // now loop through the children and make sure they get rendered for(i=0,j=this.children.length; i} an array of {Texture} objects that make up the animation */ PIXI.MovieClip = function(textures) { PIXI.Sprite.call(this, textures[0]); /** * The array of textures that make up the animation * * @property textures * @type Array */ this.textures = textures; /** * The speed that the MovieClip will play at. Higher is faster, lower is slower * * @property animationSpeed * @type Number * @default 1 */ this.animationSpeed = 1; /** * Whether or not the movie clip repeats after playing. * * @property loop * @type Boolean * @default true */ this.loop = true; /** * Function to call when a MovieClip finishes playing * * @property onComplete * @type Function */ this.onComplete = null; /** * [read-only] The MovieClips current frame index (this may not have to be a whole number) * * @property currentFrame * @type Number * @default 0 * @readOnly */ this.currentFrame = 0; /** * [read-only] Indicates if the MovieClip is currently playing * * @property playing * @type Boolean * @readOnly */ this.playing = false; }; // constructor PIXI.MovieClip.prototype = Object.create( PIXI.Sprite.prototype ); PIXI.MovieClip.prototype.constructor = PIXI.MovieClip; /** * [read-only] totalFrames is the total number of frames in the MovieClip. This is the same as number of textures * assigned to the MovieClip. * * @property totalFrames * @type Number * @default 0 * @readOnly */ Object.defineProperty( PIXI.MovieClip.prototype, 'totalFrames', { get: function() { return this.textures.length; } }); /** * Stops the MovieClip * * @method stop */ PIXI.MovieClip.prototype.stop = function() { this.playing = false; }; /** * Plays the MovieClip * * @method play */ PIXI.MovieClip.prototype.play = function() { this.playing = true; }; /** * Stops the MovieClip and goes to a specific frame * * @method gotoAndStop * @param frameNumber {Number} frame index to stop at */ PIXI.MovieClip.prototype.gotoAndStop = function(frameNumber) { this.playing = false; this.currentFrame = frameNumber; var round = (this.currentFrame + 0.5) | 0; this.setTexture(this.textures[round % this.textures.length]); }; /** * Goes to a specific frame and begins playing the MovieClip * * @method gotoAndPlay * @param frameNumber {Number} frame index to start at */ PIXI.MovieClip.prototype.gotoAndPlay = function(frameNumber) { this.currentFrame = frameNumber; this.playing = true; }; /* * Updates the object transform for rendering * * @method updateTransform * @private */ PIXI.MovieClip.prototype.updateTransform = function() { PIXI.Sprite.prototype.updateTransform.call(this); if(!this.playing)return; this.currentFrame += this.animationSpeed; var round = (this.currentFrame + 0.5) | 0; if(this.loop || round < this.textures.length) { this.setTexture(this.textures[round % this.textures.length]); } else if(round >= this.textures.length) { this.gotoAndStop(this.textures.length - 1); if(this.onComplete) { this.onComplete(); } } }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ PIXI.FilterBlock = function() { this.visible = true; this.renderable = true; }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * A Text Object will create a line(s) of text. To split a line you can use '\n' * or add a wordWrap property set to true and and wordWrapWidth property with a value * in the style object * * @class Text * @extends Sprite * @constructor * @param text {String} The copy that you would like the text to display * @param [style] {Object} The style parameters * @param [style.font] {String} default 'bold 20px Arial' The style and size of the font * @param [style.fill='black'] {String|Number} A canvas fillstyle that will be used on the text e.g 'red', '#00FF00' * @param [style.align='left'] {String} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text * @param [style.stroke] {String|Number} A canvas fillstyle that will be used on the text stroke e.g 'blue', '#FCFF00' * @param [style.strokeThickness=0] {Number} A number that represents the thickness of the stroke. Default is 0 (no stroke) * @param [style.wordWrap=false] {Boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {Number} The width at which text will wrap, it needs wordWrap to be set to true */ PIXI.Text = function(text, style) { /** * The canvas element that everything is drawn to * * @property canvas * @type HTMLCanvasElement */ this.canvas = document.createElement('canvas'); /** * The canvas 2d context that everything is drawn with * @property context * @type HTMLCanvasElement 2d Context */ this.context = this.canvas.getContext('2d'); PIXI.Sprite.call(this, PIXI.Texture.fromCanvas(this.canvas)); this.setText(text); this.setStyle(style); this.updateText(); this.dirty = false; }; // constructor PIXI.Text.prototype = Object.create(PIXI.Sprite.prototype); PIXI.Text.prototype.constructor = PIXI.Text; /** * Set the style of the text * * @method setStyle * @param [style] {Object} The style parameters * @param [style.font='bold 20pt Arial'] {String} The style and size of the font * @param [style.fill='black'] {Object} A canvas fillstyle that will be used on the text eg 'red', '#00FF00' * @param [style.align='left'] {String} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text * @param [style.stroke='black'] {String} A canvas fillstyle that will be used on the text stroke eg 'blue', '#FCFF00' * @param [style.strokeThickness=0] {Number} A number that represents the thickness of the stroke. Default is 0 (no stroke) * @param [style.wordWrap=false] {Boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {Number} The width at which text will wrap */ PIXI.Text.prototype.setStyle = function(style) { style = style || {}; style.font = style.font || 'bold 20pt Arial'; style.fill = style.fill || 'black'; style.align = style.align || 'left'; style.stroke = style.stroke || 'black'; //provide a default, see: https://github.com/GoodBoyDigital/pixi.js/issues/136 style.strokeThickness = style.strokeThickness || 0; style.wordWrap = style.wordWrap || false; style.wordWrapWidth = style.wordWrapWidth || 100; this.style = style; this.dirty = true; }; /** * Set the copy for the text object. To split a line you can use '\n' * * @method setText * @param {String} text The copy that you would like the text to display */ PIXI.Text.prototype.setText = function(text) { this.text = text.toString() || ' '; this.dirty = true; }; /** * Renders text and updates it when needed * * @method updateText * @private */ PIXI.Text.prototype.updateText = function() { this.context.font = this.style.font; var outputText = this.text; // word wrap // preserve original text if(this.style.wordWrap)outputText = this.wordWrap(this.text); //split text into lines var lines = outputText.split(/(?:\r\n|\r|\n)/); //calculate text width var lineWidths = []; var maxLineWidth = 0; for (var i = 0; i < lines.length; i++) { var lineWidth = this.context.measureText(lines[i]).width; lineWidths[i] = lineWidth; maxLineWidth = Math.max(maxLineWidth, lineWidth); } this.canvas.width = maxLineWidth + this.style.strokeThickness; //calculate text height var lineHeight = this.determineFontHeight('font: ' + this.style.font + ';') + this.style.strokeThickness; this.canvas.height = lineHeight * lines.length; if(navigator.isCocoonJS) this.context.clearRect(0,0,this.canvas.width,this.canvas.height); //set canvas text styles this.context.fillStyle = this.style.fill; this.context.font = this.style.font; this.context.strokeStyle = this.style.stroke; this.context.lineWidth = this.style.strokeThickness; this.context.textBaseline = 'top'; //draw lines line by line for (i = 0; i < lines.length; i++) { var linePosition = new PIXI.Point(this.style.strokeThickness / 2, this.style.strokeThickness / 2 + i * lineHeight); if(this.style.align === 'right') { linePosition.x += maxLineWidth - lineWidths[i]; } else if(this.style.align === 'center') { linePosition.x += (maxLineWidth - lineWidths[i]) / 2; } if(this.style.stroke && this.style.strokeThickness) { this.context.strokeText(lines[i], linePosition.x, linePosition.y); } if(this.style.fill) { this.context.fillText(lines[i], linePosition.x, linePosition.y); } } this.updateTexture(); }; /** * Updates texture size based on canvas size * * @method updateTexture * @private */ PIXI.Text.prototype.updateTexture = function() { this.texture.baseTexture.width = this.canvas.width; this.texture.baseTexture.height = this.canvas.height; this.texture.frame.width = this.canvas.width; this.texture.frame.height = this.canvas.height; this._width = this.canvas.width; this._height = this.canvas.height; this.requiresUpdate = true; }; /** * Renders the object using the WebGL renderer * * @method _renderWebGL * @param renderSession {RenderSession} * @private */ PIXI.Text.prototype._renderWebGL = function(renderSession) { if(this.requiresUpdate) { this.requiresUpdate = false; PIXI.updateWebGLTexture(this.texture.baseTexture, renderSession.gl); } PIXI.Sprite.prototype._renderWebGL.call(this, renderSession); }; /** * Updates the transform of this object * * @method updateTransform * @private */ PIXI.Text.prototype.updateTransform = function() { if(this.dirty) { this.updateText(); this.dirty = false; } PIXI.Sprite.prototype.updateTransform.call(this); }; /* * http://stackoverflow.com/users/34441/ellisbben * great solution to the problem! * returns the height of the given font * * @method determineFontHeight * @param fontStyle {Object} * @private */ PIXI.Text.prototype.determineFontHeight = function(fontStyle) { // build a little reference dictionary so if the font style has been used return a // cached version... var result = PIXI.Text.heightCache[fontStyle]; if(!result) { var body = document.getElementsByTagName('body')[0]; var dummy = document.createElement('div'); var dummyText = document.createTextNode('M'); dummy.appendChild(dummyText); dummy.setAttribute('style', fontStyle + ';position:absolute;top:0;left:0'); body.appendChild(dummy); result = dummy.offsetHeight; PIXI.Text.heightCache[fontStyle] = result; body.removeChild(dummy); } return result; }; /** * Applies newlines to a string to have it optimally fit into the horizontal * bounds set by the Text object's wordWrapWidth property. * * @method wordWrap * @param text {String} * @private */ PIXI.Text.prototype.wordWrap = function(text) { // Greedy wrapping algorithm that will wrap words as the line grows longer // than its horizontal bounds. var result = ''; var lines = text.split('\n'); for (var i = 0; i < lines.length; i++) { var spaceLeft = this.style.wordWrapWidth; var words = lines[i].split(' '); for (var j = 0; j < words.length; j++) { var wordWidth = this.context.measureText(words[j]).width; var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; if(wordWidthWithSpace > spaceLeft) { // Skip printing the newline if it's the first word of the line that is // greater than the word wrap width. if(j > 0) { result += '\n'; } result += words[j] + ' '; spaceLeft = this.style.wordWrapWidth - wordWidth; } else { spaceLeft -= wordWidthWithSpace; result += words[j] + ' '; } } if (i < lines.length-1) { result += '\n'; } } return result; }; /** * Destroys this text object * * @method destroy * @param destroyTexture {Boolean} */ PIXI.Text.prototype.destroy = function(destroyTexture) { if(destroyTexture) { this.texture.destroy(); } }; PIXI.Text.heightCache = {}; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * A Text Object will create a line(s) of text using bitmap font. To split a line you can use '\n', '\r' or '\r\n' * You can generate the fnt files using * http://www.angelcode.com/products/bmfont/ for windows or * http://www.bmglyph.com/ for mac. * * @class BitmapText * @extends DisplayObjectContainer * @constructor * @param text {String} The copy that you would like the text to display * @param style {Object} The style parameters * @param style.font {String} The size (optional) and bitmap font id (required) eq 'Arial' or '20px Arial' (must have loaded previously) * @param [style.align='left'] {String} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text */ PIXI.BitmapText = function(text, style) { PIXI.DisplayObjectContainer.call(this); this._pool = []; this.setText(text); this.setStyle(style); this.updateText(); this.dirty = false; }; // constructor PIXI.BitmapText.prototype = Object.create(PIXI.DisplayObjectContainer.prototype); PIXI.BitmapText.prototype.constructor = PIXI.BitmapText; /** * Set the copy for the text object * * @method setText * @param text {String} The copy that you would like the text to display */ PIXI.BitmapText.prototype.setText = function(text) { this.text = text || ' '; this.dirty = true; }; /** * Set the style of the text * style.font {String} The size (optional) and bitmap font id (required) eq 'Arial' or '20px Arial' (must have loaded previously) * [style.align='left'] {String} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text * * @method setStyle * @param style {Object} The style parameters, contained as properties of an object */ PIXI.BitmapText.prototype.setStyle = function(style) { style = style || {}; style.align = style.align || 'left'; this.style = style; var font = style.font.split(' '); this.fontName = font[font.length - 1]; this.fontSize = font.length >= 2 ? parseInt(font[font.length - 2], 10) : PIXI.BitmapText.fonts[this.fontName].size; this.dirty = true; this.tint = style.tint; }; /** * Renders text and updates it when needed * * @method updateText * @private */ PIXI.BitmapText.prototype.updateText = function() { var data = PIXI.BitmapText.fonts[this.fontName]; var pos = new PIXI.Point(); var prevCharCode = null; var chars = []; var maxLineWidth = 0; var lineWidths = []; var line = 0; var scale = this.fontSize / data.size; for(var i = 0; i < this.text.length; i++) { var charCode = this.text.charCodeAt(i); if(/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) { lineWidths.push(pos.x); maxLineWidth = Math.max(maxLineWidth, pos.x); line++; pos.x = 0; pos.y += data.lineHeight; prevCharCode = null; continue; } var charData = data.chars[charCode]; if(!charData) continue; if(prevCharCode && charData[prevCharCode]) { pos.x += charData.kerning[prevCharCode]; } chars.push({texture:charData.texture, line: line, charCode: charCode, position: new PIXI.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); pos.x += charData.xAdvance; prevCharCode = charCode; } lineWidths.push(pos.x); maxLineWidth = Math.max(maxLineWidth, pos.x); var lineAlignOffsets = []; for(i = 0; i <= line; i++) { var alignOffset = 0; if(this.style.align === 'right') { alignOffset = maxLineWidth - lineWidths[i]; } else if(this.style.align === 'center') { alignOffset = (maxLineWidth - lineWidths[i]) / 2; } lineAlignOffsets.push(alignOffset); } var lenChildren = this.children.length; var lenChars = chars.length; var tint = this.tint || 0xFFFFFF; for(i = 0; i < lenChars; i++) { var c = i < lenChildren ? this.children[i] : this._pool.pop(); // get old child if have. if not - take from pool. if (c) c.setTexture(chars[i].texture); // check if got one before. else c = new PIXI.Sprite(chars[i].texture); // if no create new one. c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; c.position.y = chars[i].position.y * scale; c.scale.x = c.scale.y = scale; c.tint = tint; if (!c.parent) this.addChild(c); } // remove unnecessary children. // and put their into the pool. while(this.children.length > lenChars) { var child = this.getChildAt(this.children.length - 1); this._pool.push(child); this.removeChild(child); } /** * [read-only] The width of the overall text, different from fontSize, * which is defined in the style object * * @property textWidth * @type Number */ this.textWidth = maxLineWidth * scale; /** * [read-only] The height of the overall text, different from fontSize, * which is defined in the style object * * @property textHeight * @type Number */ this.textHeight = (pos.y + data.lineHeight) * scale; }; /** * Updates the transform of this object * * @method updateTransform * @private */ PIXI.BitmapText.prototype.updateTransform = function() { if(this.dirty) { this.updateText(); this.dirty = false; } PIXI.DisplayObjectContainer.prototype.updateTransform.call(this); }; PIXI.BitmapText.fonts = {}; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * Holds all information related to an Interaction event * * @class InteractionData * @constructor */ PIXI.InteractionData = function() { /** * This point stores the global coords of where the touch/mouse event happened * * @property global * @type Point */ this.global = new PIXI.Point(); // this is here for legacy... but will remove this.local = new PIXI.Point(); /** * The target Sprite that was interacted with * * @property target * @type Sprite */ this.target = null; /** * When passed to an event handler, this will be the original DOM Event that was captured * * @property originalEvent * @type Event */ this.originalEvent = null; }; /** * This will return the local coordinates of the specified displayObject for this InteractionData * * @method getLocalPosition * @param displayObject {DisplayObject} The DisplayObject that you would like the local coords off * @return {Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject */ PIXI.InteractionData.prototype.getLocalPosition = function(displayObject) { var worldTransform = displayObject.worldTransform; var global = this.global; // do a cheeky transform to get the mouse coords; var a00 = worldTransform.a, a01 = worldTransform.b, a02 = worldTransform.tx, a10 = worldTransform.c, a11 = worldTransform.d, a12 = worldTransform.ty, id = 1 / (a00 * a11 + a01 * -a10); // set the mouse coords... return new PIXI.Point(a11 * id * global.x + -a01 * id * global.y + (a12 * a01 - a02 * a11) * id, a00 * id * global.y + -a10 * id * global.x + (-a12 * a00 + a02 * a10) * id); }; // constructor PIXI.InteractionData.prototype.constructor = PIXI.InteractionData; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * The interaction manager deals with mouse and touch events. Any DisplayObject can be interactive * if its interactive parameter is set to true * This manager also supports multitouch. * * @class InteractionManager * @constructor * @param stage {Stage} The stage to handle interactions */ PIXI.InteractionManager = function(stage) { /** * a reference to the stage * * @property stage * @type Stage */ this.stage = stage; /** * the mouse data * * @property mouse * @type InteractionData */ this.mouse = new PIXI.InteractionData(); /** * an object that stores current touches (InteractionData) by id reference * * @property touchs * @type Object */ this.touchs = {}; // helpers this.tempPoint = new PIXI.Point(); /** * * @property mouseoverEnabled * @type Boolean * @default */ this.mouseoverEnabled = true; /** * tiny little interactiveData pool ! * * @property pool * @type Array */ this.pool = []; /** * An array containing all the iterative items from the our interactive tree * @property interactiveItems * @type Array * @private * */ this.interactiveItems = []; /** * Our canvas * @property interactionDOMElement * @type HTMLCanvasElement * @private */ this.interactionDOMElement = null; //this will make it so that you dont have to call bind all the time this.onMouseMove = this.onMouseMove.bind( this ); this.onMouseDown = this.onMouseDown.bind(this); this.onMouseOut = this.onMouseOut.bind(this); this.onMouseUp = this.onMouseUp.bind(this); this.onTouchStart = this.onTouchStart.bind(this); this.onTouchEnd = this.onTouchEnd.bind(this); this.onTouchMove = this.onTouchMove.bind(this); this.last = 0; /** * The css style of the cursor that is being used * @property currentCursorStyle * @type String * */ this.currentCursorStyle = 'inherit'; /** * Is set to true when the mouse is moved out of the canvas * @property mouseOut * @type Boolean * */ this.mouseOut = false; }; // constructor PIXI.InteractionManager.prototype.constructor = PIXI.InteractionManager; /** * Collects an interactive sprite recursively to have their interactions managed * * @method collectInteractiveSprite * @param displayObject {DisplayObject} the displayObject to collect * @param iParent {DisplayObject} the display object's parent * @private */ PIXI.InteractionManager.prototype.collectInteractiveSprite = function(displayObject, iParent) { var children = displayObject.children; var length = children.length; // make an interaction tree... {item.__interactiveParent} for (var i = length-1; i >= 0; i--) { var child = children[i]; // push all interactive bits if(child.interactive) { iParent.interactiveChildren = true; //child.__iParent = iParent; this.interactiveItems.push(child); if(child.children.length > 0) { this.collectInteractiveSprite(child, child); } } else { child.__iParent = null; if(child.children.length > 0) { this.collectInteractiveSprite(child, iParent); } } } }; /** * Sets the target for event delegation * * @method setTarget * @param target {WebGLRenderer|CanvasRenderer} the renderer to bind events to * @private */ PIXI.InteractionManager.prototype.setTarget = function(target) { this.target = target; //check if the dom element has been set. If it has don't do anything if( this.interactionDOMElement === null ) { this.setTargetDomElement( target.view ); } }; /** * Sets the DOM element which will receive mouse/touch events. This is useful for when you have other DOM * elements on top of the renderers Canvas element. With this you'll be able to delegate another DOM element * to receive those events * * @method setTargetDomElement * @param domElement {DOMElement} the DOM element which will receive mouse and touch events * @private */ PIXI.InteractionManager.prototype.setTargetDomElement = function(domElement) { this.removeEvents(); if (window.navigator.msPointerEnabled) { // time to remove some of that zoom in ja.. domElement.style['-ms-content-zooming'] = 'none'; domElement.style['-ms-touch-action'] = 'none'; // DO some window specific touch! } this.interactionDOMElement = domElement; domElement.addEventListener('mousemove', this.onMouseMove, true); domElement.addEventListener('mousedown', this.onMouseDown, true); domElement.addEventListener('mouseout', this.onMouseOut, true); // aint no multi touch just yet! domElement.addEventListener('touchstart', this.onTouchStart, true); domElement.addEventListener('touchend', this.onTouchEnd, true); domElement.addEventListener('touchmove', this.onTouchMove, true); document.body.addEventListener('mouseup', this.onMouseUp, true); }; PIXI.InteractionManager.prototype.removeEvents = function() { if(!this.interactionDOMElement)return; this.interactionDOMElement.style['-ms-content-zooming'] = ''; this.interactionDOMElement.style['-ms-touch-action'] = ''; this.interactionDOMElement.removeEventListener('mousemove', this.onMouseMove, true); this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); // aint no multi touch just yet! this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); this.interactionDOMElement = null; document.body.removeEventListener('mouseup', this.onMouseUp, true); }; /** * updates the state of interactive objects * * @method update * @private */ PIXI.InteractionManager.prototype.update = function() { if(!this.target)return; // frequency of 30fps?? var now = Date.now(); var diff = now - this.last; diff = (diff * PIXI.INTERACTION_FREQUENCY ) / 1000; if(diff < 1)return; this.last = now; var i = 0; // ok.. so mouse events?? // yes for now :) // OPTIMISE - how often to check?? if(this.dirty) { this.dirty = false; var len = this.interactiveItems.length; for (i = 0; i < len; i++) { this.interactiveItems[i].interactiveChildren = false; } this.interactiveItems = []; if(this.stage.interactive)this.interactiveItems.push(this.stage); // go through and collect all the objects that are interactive.. this.collectInteractiveSprite(this.stage, this.stage); } // loop through interactive objects! var length = this.interactiveItems.length; var cursor = 'inherit'; var over = false; for (i = 0; i < length; i++) { var item = this.interactiveItems[i]; // OPTIMISATION - only calculate every time if the mousemove function exists.. // OK so.. does the object have any other interactive functions? // hit-test the clip! // if(item.mouseover || item.mouseout || item.buttonMode) // { // ok so there are some functions so lets hit test it.. item.__hit = this.hitTest(item, this.mouse); this.mouse.target = item; // ok so deal with interactions.. // looks like there was a hit! if(item.__hit && !over) { if(item.buttonMode) cursor = item.defaultCursor; if(!item.interactiveChildren)over = true; if(!item.__isOver) { if(item.mouseover)item.mouseover(this.mouse); item.__isOver = true; } } else { if(item.__isOver) { // roll out! if(item.mouseout)item.mouseout(this.mouse); item.__isOver = false; } } } if( this.currentCursorStyle !== cursor ) { this.currentCursorStyle = cursor; this.interactionDOMElement.style.cursor = cursor; } }; /** * Is called when the mouse moves across the renderer element * * @method onMouseMove * @param event {Event} The DOM event of the mouse moving * @private */ PIXI.InteractionManager.prototype.onMouseMove = function(event) { this.mouse.originalEvent = event || window.event; //IE uses window.event // TODO optimize by not check EVERY TIME! maybe half as often? // var rect = this.interactionDOMElement.getBoundingClientRect(); this.mouse.global.x = (event.clientX - rect.left) * (this.target.width / rect.width); this.mouse.global.y = (event.clientY - rect.top) * ( this.target.height / rect.height); var length = this.interactiveItems.length; for (var i = 0; i < length; i++) { var item = this.interactiveItems[i]; if(item.mousemove) { //call the function! item.mousemove(this.mouse); } } }; /** * Is called when the mouse button is pressed down on the renderer element * * @method onMouseDown * @param event {Event} The DOM event of a mouse button being pressed down * @private */ PIXI.InteractionManager.prototype.onMouseDown = function(event) { this.mouse.originalEvent = event || window.event; //IE uses window.event if(PIXI.AUTO_PREVENT_DEFAULT)this.mouse.originalEvent.preventDefault(); // loop through interaction tree... // hit test each item! -> // get interactive items under point?? //stage.__i var length = this.interactiveItems.length; // while // hit test for (var i = 0; i < length; i++) { var item = this.interactiveItems[i]; if(item.mousedown || item.click) { item.__mouseIsDown = true; item.__hit = this.hitTest(item, this.mouse); if(item.__hit) { //call the function! if(item.mousedown)item.mousedown(this.mouse); item.__isDown = true; // just the one! if(!item.interactiveChildren)break; } } } }; /** * Is called when the mouse button is moved out of the renderer element * * @method onMouseOut * @param event {Event} The DOM event of a mouse button being moved out * @private */ PIXI.InteractionManager.prototype.onMouseOut = function() { var length = this.interactiveItems.length; this.interactionDOMElement.style.cursor = 'inherit'; for (var i = 0; i < length; i++) { var item = this.interactiveItems[i]; if(item.__isOver) { this.mouse.target = item; if(item.mouseout)item.mouseout(this.mouse); item.__isOver = false; } } this.mouseOut = true; // move the mouse to an impossible position this.mouse.global.x = -10000; this.mouse.global.y = -10000; }; /** * Is called when the mouse button is released on the renderer element * * @method onMouseUp * @param event {Event} The DOM event of a mouse button being released * @private */ PIXI.InteractionManager.prototype.onMouseUp = function(event) { this.mouse.originalEvent = event || window.event; //IE uses window.event var length = this.interactiveItems.length; var up = false; for (var i = 0; i < length; i++) { var item = this.interactiveItems[i]; item.__hit = this.hitTest(item, this.mouse); if(item.__hit && !up) { //call the function! if(item.mouseup) { item.mouseup(this.mouse); } if(item.__isDown) { if(item.click)item.click(this.mouse); } if(!item.interactiveChildren)up = true; } else { if(item.__isDown) { if(item.mouseupoutside)item.mouseupoutside(this.mouse); } } item.__isDown = false; //} } }; /** * Tests if the current mouse coordinates hit a sprite * * @method hitTest * @param item {DisplayObject} The displayObject to test for a hit * @param interactionData {InteractionData} The interactionData object to update in the case there is a hit * @private */ PIXI.InteractionManager.prototype.hitTest = function(item, interactionData) { var global = interactionData.global; if( !item.worldVisible )return false; // temp fix for if the element is in a non visible var isSprite = (item instanceof PIXI.Sprite), worldTransform = item.worldTransform, a00 = worldTransform.a, a01 = worldTransform.b, a02 = worldTransform.tx, a10 = worldTransform.c, a11 = worldTransform.d, a12 = worldTransform.ty, id = 1 / (a00 * a11 + a01 * -a10), x = a11 * id * global.x + -a01 * id * global.y + (a12 * a01 - a02 * a11) * id, y = a00 * id * global.y + -a10 * id * global.x + (-a12 * a00 + a02 * a10) * id; interactionData.target = item; //a sprite or display object with a hit area defined if(item.hitArea && item.hitArea.contains) { if(item.hitArea.contains(x, y)) { //if(isSprite) interactionData.target = item; return true; } return false; } // a sprite with no hitarea defined else if(isSprite) { var width = item.texture.frame.width, height = item.texture.frame.height, x1 = -width * item.anchor.x, y1; if(x > x1 && x < x1 + width) { y1 = -height * item.anchor.y; if(y > y1 && y < y1 + height) { // set the target property if a hit is true! interactionData.target = item; return true; } } } var length = item.children.length; for (var i = 0; i < length; i++) { var tempItem = item.children[i]; var hit = this.hitTest(tempItem, interactionData); if(hit) { // hmm.. TODO SET CORRECT TARGET? interactionData.target = item; return true; } } return false; }; /** * Is called when a touch is moved across the renderer element * * @method onTouchMove * @param event {Event} The DOM event of a touch moving across the renderer view * @private */ PIXI.InteractionManager.prototype.onTouchMove = function(event) { var rect = this.interactionDOMElement.getBoundingClientRect(); var changedTouches = event.changedTouches; var touchData; var i = 0; for (i = 0; i < changedTouches.length; i++) { var touchEvent = changedTouches[i]; touchData = this.touchs[touchEvent.identifier]; touchData.originalEvent = event || window.event; // update the touch position touchData.global.x = (touchEvent.clientX - rect.left) * (this.target.width / rect.width); touchData.global.y = (touchEvent.clientY - rect.top) * (this.target.height / rect.height); if(navigator.isCocoonJS) { touchData.global.x = touchEvent.clientX; touchData.global.y = touchEvent.clientY; } } var length = this.interactiveItems.length; for (i = 0; i < length; i++) { var item = this.interactiveItems[i]; if(item.touchmove) item.touchmove(touchData); } }; /** * Is called when a touch is started on the renderer element * * @method onTouchStart * @param event {Event} The DOM event of a touch starting on the renderer view * @private */ PIXI.InteractionManager.prototype.onTouchStart = function(event) { var rect = this.interactionDOMElement.getBoundingClientRect(); if(PIXI.AUTO_PREVENT_DEFAULT)event.preventDefault(); var changedTouches = event.changedTouches; for (var i=0; i < changedTouches.length; i++) { var touchEvent = changedTouches[i]; var touchData = this.pool.pop(); if(!touchData)touchData = new PIXI.InteractionData(); touchData.originalEvent = event || window.event; this.touchs[touchEvent.identifier] = touchData; touchData.global.x = (touchEvent.clientX - rect.left) * (this.target.width / rect.width); touchData.global.y = (touchEvent.clientY - rect.top) * (this.target.height / rect.height); if(navigator.isCocoonJS) { touchData.global.x = touchEvent.clientX; touchData.global.y = touchEvent.clientY; } var length = this.interactiveItems.length; for (var j = 0; j < length; j++) { var item = this.interactiveItems[j]; if(item.touchstart || item.tap) { item.__hit = this.hitTest(item, touchData); if(item.__hit) { //call the function! if(item.touchstart)item.touchstart(touchData); item.__isDown = true; item.__touchData = touchData; if(!item.interactiveChildren)break; } } } } }; /** * Is called when a touch is ended on the renderer element * * @method onTouchEnd * @param event {Event} The DOM event of a touch ending on the renderer view * @private */ PIXI.InteractionManager.prototype.onTouchEnd = function(event) { //this.mouse.originalEvent = event || window.event; //IE uses window.event var rect = this.interactionDOMElement.getBoundingClientRect(); var changedTouches = event.changedTouches; for (var i=0; i < changedTouches.length; i++) { var touchEvent = changedTouches[i]; var touchData = this.touchs[touchEvent.identifier]; var up = false; touchData.global.x = (touchEvent.clientX - rect.left) * (this.target.width / rect.width); touchData.global.y = (touchEvent.clientY - rect.top) * (this.target.height / rect.height); if(navigator.isCocoonJS) { touchData.global.x = touchEvent.clientX; touchData.global.y = touchEvent.clientY; } var length = this.interactiveItems.length; for (var j = 0; j < length; j++) { var item = this.interactiveItems[j]; var itemTouchData = item.__touchData; // <-- Here! item.__hit = this.hitTest(item, touchData); if(itemTouchData === touchData) { // so this one WAS down... touchData.originalEvent = event || window.event; // hitTest?? if(item.touchend || item.tap) { if(item.__hit && !up) { if(item.touchend)item.touchend(touchData); if(item.__isDown) { if(item.tap)item.tap(touchData); } if(!item.interactiveChildren)up = true; } else { if(item.__isDown) { if(item.touchendoutside)item.touchendoutside(touchData); } } item.__isDown = false; } item.__touchData = null; } /* else { } */ } // remove the touch.. this.pool.push(touchData); this.touchs[touchEvent.identifier] = null; } }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * A Stage represents the root of the display tree. Everything connected to the stage is rendered * * @class Stage * @extends DisplayObjectContainer * @constructor * @param backgroundColor {Number} the background color of the stage, you have to pass this in is in hex format * like: 0xFFFFFF for white * * Creating a stage is a mandatory process when you use Pixi, which is as simple as this : * var stage = new PIXI.Stage(0xFFFFFF); * where the parameter given is the background colour of the stage, in hex * you will use this stage instance to add your sprites to it and therefore to the renderer * Here is how to add a sprite to the stage : * stage.addChild(sprite); */ PIXI.Stage = function(backgroundColor) { PIXI.DisplayObjectContainer.call( this ); /** * [read-only] Current transform of the object based on world (parent) factors * * @property worldTransform * @type Mat3 * @readOnly * @private */ this.worldTransform = new PIXI.Matrix(); /** * Whether or not the stage is interactive * * @property interactive * @type Boolean */ this.interactive = true; /** * The interaction manage for this stage, manages all interactive activity on the stage * * @property interactive * @type InteractionManager */ this.interactionManager = new PIXI.InteractionManager(this); /** * Whether the stage is dirty and needs to have interactions updated * * @property dirty * @type Boolean * @private */ this.dirty = true; //the stage is its own stage this.stage = this; //optimize hit detection a bit this.stage.hitArea = new PIXI.Rectangle(0,0,100000, 100000); this.setBackgroundColor(backgroundColor); }; // constructor PIXI.Stage.prototype = Object.create( PIXI.DisplayObjectContainer.prototype ); PIXI.Stage.prototype.constructor = PIXI.Stage; /** * Sets another DOM element which can receive mouse/touch interactions instead of the default Canvas element. * This is useful for when you have other DOM elements on top of the Canvas element. * * @method setInteractionDelegate * @param domElement {DOMElement} This new domElement which will receive mouse/touch events */ PIXI.Stage.prototype.setInteractionDelegate = function(domElement) { this.interactionManager.setTargetDomElement( domElement ); }; /* * Updates the object transform for rendering * * @method updateTransform * @private */ PIXI.Stage.prototype.updateTransform = function() { this.worldAlpha = 1; for(var i=0,j=this.children.length; i> 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); }; /** * A polyfill for Function.prototype.bind * * @method bind */ if (typeof Function.prototype.bind !== 'function') { Function.prototype.bind = (function () { var slice = Array.prototype.slice; return function (thisArg) { var target = this, boundArgs = slice.call(arguments, 1); if (typeof target !== 'function') throw new TypeError(); function bound() { var args = boundArgs.concat(slice.call(arguments)); target.apply(this instanceof bound ? this : thisArg, args); } bound.prototype = (function F(proto) { if (proto) F.prototype = proto; if (!(this instanceof F)) return new F(); })(target.prototype); return bound; }; })(); } /** * A wrapper for ajax requests to be handled cross browser * * @class AjaxRequest * @constructor */ PIXI.AjaxRequest = function() { var activexmodes = ['Msxml2.XMLHTTP.6.0', 'Msxml2.XMLHTTP.3.0', 'Microsoft.XMLHTTP']; //activeX versions to check for in IE if (window.ActiveXObject) { //Test for support for ActiveXObject in IE first (as XMLHttpRequest in IE7 is broken) for (var i=0; i 0 && (number & (number - 1)) === 0) // see: http://goo.gl/D9kPj return number; else { var result = 1; while (result < number) result <<= 1; return result; } }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * https://github.com/mrdoob/eventtarget.js/ * THankS mr DOob! */ /** * Adds event emitter functionality to a class * * @class EventTarget * @example * function MyEmitter() { * PIXI.EventTarget.call(this); //mixes in event target stuff * } * * var em = new MyEmitter(); * em.emit({ type: 'eventName', data: 'some data' }); */ PIXI.EventTarget = function () { /** * Holds all the listeners * * @property listeneners * @type Object */ var listeners = {}; /** * Adds a listener for a specific event * * @method addEventListener * @param type {string} A string representing the event type to listen for. * @param listener {function} The callback function that will be fired when the event occurs */ this.addEventListener = this.on = function ( type, listener ) { if ( listeners[ type ] === undefined ) { listeners[ type ] = []; } if ( listeners[ type ].indexOf( listener ) === - 1 ) { listeners[ type ].push( listener ); } }; /** * Fires the event, ie pretends that the event has happened * * @method dispatchEvent * @param event {Event} the event object */ this.dispatchEvent = this.emit = function ( event ) { if ( !listeners[ event.type ] || !listeners[ event.type ].length ) { return; } for(var i = 0, l = listeners[ event.type ].length; i < l; i++) { listeners[ event.type ][ i ]( event ); } }; /** * Removes the specified listener that was assigned to the specified event type * * @method removeEventListener * @param type {string} A string representing the event type which will have its listener removed * @param listener {function} The callback function that was be fired when the event occured */ this.removeEventListener = this.off = function ( type, listener ) { var index = listeners[ type ].indexOf( listener ); if ( index !== - 1 ) { listeners[ type ].splice( index, 1 ); } }; /** * Removes all the listeners that were active for the specified event type * * @method removeAllEventListeners * @param type {string} A string representing the event type which will have all its listeners removed */ this.removeAllEventListeners = function( type ) { var a = listeners[type]; if (a) a.length = 0; }; }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * This helper function will automatically detect which renderer you should be using. * WebGL is the preferred renderer as it is a lot faster. If webGL is not supported by * the browser then this function will return a canvas renderer * @class autoDetectRenderer * @static * @param width=800 {Number} the width of the renderers view * @param height=600 {Number} the height of the renderers view * @param [view] {Canvas} the canvas to use as a view, optional * @param [transparent=false] {Boolean} the transparency of the render view, default false * @param [antialias=false] {Boolean} sets antialias (only applicable in webGL chrome at the moment) * */ PIXI.autoDetectRenderer = function(width, height, view, transparent, antialias) { if(!width)width = 800; if(!height)height = 600; // BORROWED from Mr Doob (mrdoob.com) var webgl = ( function () { try { var canvas = document.createElement( 'canvas' ); return !! window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ); } catch( e ) { return false; } } )(); if( webgl ) { return new PIXI.WebGLRenderer(width, height, view, transparent, antialias); } return new PIXI.CanvasRenderer(width, height, view, transparent); }; /* PolyK library url: http://polyk.ivank.net Released under MIT licence. Copyright (c) 2012 Ivan Kuckir Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This is an amazing lib! slightly modified by Mat Groves (matgroves.com); */ /** * Based on the Polyk library http://polyk.ivank.net released under MIT licence. * This is an amazing lib! * slightly modified by Mat Groves (matgroves.com); * @class PolyK * */ PIXI.PolyK = {}; /** * Triangulates shapes for webGL graphic fills * * @method Triangulate * */ PIXI.PolyK.Triangulate = function(p) { var sign = true; var n = p.length >> 1; if(n < 3) return []; var tgs = []; var avl = []; for(var i = 0; i < n; i++) avl.push(i); i = 0; var al = n; while(al > 3) { var i0 = avl[(i+0)%al]; var i1 = avl[(i+1)%al]; var i2 = avl[(i+2)%al]; var ax = p[2*i0], ay = p[2*i0+1]; var bx = p[2*i1], by = p[2*i1+1]; var cx = p[2*i2], cy = p[2*i2+1]; var earFound = false; if(PIXI.PolyK._convex(ax, ay, bx, by, cx, cy, sign)) { earFound = true; for(var j = 0; j < al; j++) { var vi = avl[j]; if(vi === i0 || vi === i1 || vi === i2) continue; if(PIXI.PolyK._PointInTriangle(p[2*vi], p[2*vi+1], ax, ay, bx, by, cx, cy)) { earFound = false; break; } } } if(earFound) { tgs.push(i0, i1, i2); avl.splice((i+1)%al, 1); al--; i = 0; } else if(i++ > 3*al) { // need to flip flip reverse it! // reset! if(sign) { tgs = []; avl = []; for(i = 0; i < n; i++) avl.push(i); i = 0; al = n; sign = false; } else { window.console.log("PIXI Warning: shape too complex to fill"); return []; } } } tgs.push(avl[0], avl[1], avl[2]); return tgs; }; /** * Checks whether a point is within a triangle * * @method _PointInTriangle * @param px {Number} x coordinate of the point to test * @param py {Number} y coordinate of the point to test * @param ax {Number} x coordinate of the a point of the triangle * @param ay {Number} y coordinate of the a point of the triangle * @param bx {Number} x coordinate of the b point of the triangle * @param by {Number} y coordinate of the b point of the triangle * @param cx {Number} x coordinate of the c point of the triangle * @param cy {Number} y coordinate of the c point of the triangle * @private */ PIXI.PolyK._PointInTriangle = function(px, py, ax, ay, bx, by, cx, cy) { var v0x = cx-ax; var v0y = cy-ay; var v1x = bx-ax; var v1y = by-ay; var v2x = px-ax; var v2y = py-ay; var dot00 = v0x*v0x+v0y*v0y; var dot01 = v0x*v1x+v0y*v1y; var dot02 = v0x*v2x+v0y*v2y; var dot11 = v1x*v1x+v1y*v1y; var dot12 = v1x*v2x+v1y*v2y; var invDenom = 1 / (dot00 * dot11 - dot01 * dot01); var u = (dot11 * dot02 - dot01 * dot12) * invDenom; var v = (dot00 * dot12 - dot01 * dot02) * invDenom; // Check if point is in triangle return (u >= 0) && (v >= 0) && (u + v < 1); }; /** * Checks whether a shape is convex * * @method _convex * * @private */ PIXI.PolyK._convex = function(ax, ay, bx, by, cx, cy, sign) { return ((ay-by)*(cx-bx) + (bx-ax)*(cy-by) >= 0) === sign; }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ // TODO Alvin and Mat // Should we eventually create a Utils class ? // Or just move this file to the pixi.js file ? PIXI.initDefaultShaders = function() { // PIXI.stripShader = new PIXI.StripShader(); // PIXI.stripShader.init(); }; PIXI.CompileVertexShader = function(gl, shaderSrc) { return PIXI._CompileShader(gl, shaderSrc, gl.VERTEX_SHADER); }; PIXI.CompileFragmentShader = function(gl, shaderSrc) { return PIXI._CompileShader(gl, shaderSrc, gl.FRAGMENT_SHADER); }; PIXI._CompileShader = function(gl, shaderSrc, shaderType) { var 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; }; 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("Could not initialise shaders"); } return shaderProgram; }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 * @author Richard Davey http://www.photonstorm.com @photonstorm */ /** * @class PixiShader * @constructor */ PIXI.PixiShader = function(gl) { /** * @property gl * @type WebGLContext */ this.gl = gl; /** * @property {any} program - The WebGL program. */ this.program = null; /** * @property {array} fragmentSrc - The fragment shader. */ this.fragmentSrc = [ 'precision lowp float;', 'varying vec2 vTextureCoord;', 'varying vec4 vColor;', 'uniform sampler2D uSampler;', 'void main(void) {', ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', '}' ]; /** * @property {number} textureCount - A local texture counter for multi-texture shaders. */ this.textureCount = 0; this.attributes = []; this.init(); }; /** * 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. // 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.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._glTexture); // 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]); 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; }; /** * * @property defaultVertexSrc * @type String */ PIXI.PixiShader.defaultVertexSrc = [ 'attribute vec2 aVertexPosition;', 'attribute vec2 aTextureCoord;', 'attribute vec2 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;', ' vec3 color = mod(vec3(aColor.y/65536.0, aColor.y/256.0, aColor.y), 256.0) / 256.0;', ' vColor = vec4(color * aColor.x, aColor.x);', '}' ]; /** * @author Mat Groves http://matgroves.com/ @Doormat23 * @author Richard Davey http://www.photonstorm.com @photonstorm */ /** * @class PixiFastShader * @constructor * @param gl {WebGLContext} the current WebGL drawing context */ PIXI.PixiFastShader = function(gl) { /** * @property gl * @type WebGLContext */ this.gl = gl; /** * @property {any} program - The WebGL program. */ this.program = null; /** * @property {array} fragmentSrc - The fragment shader. */ this.fragmentSrc = [ 'precision lowp float;', 'varying vec2 vTextureCoord;', 'varying float vColor;', 'uniform sampler2D uSampler;', 'void main(void) {', ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', '}' ]; /** * @property {array} vertexSrc - The vertex shader */ 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;', '}' ]; /** * @property {number} textureCount - A local texture counter for multi-texture shaders. */ this.textureCount = 0; this.init(); }; /** * 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 */ PIXI.StripShader = function() { /** * @property {any} program - The WebGL program. */ this.program = null; /** * @property {array} fragmentSrc - The fragment shader. */ 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));', ' gl_FragColor = gl_FragColor * alpha;', '}' ]; /** * @property {array} fragmentSrc - The fragment shader. */ this.vertexSrc = [ 'attribute vec2 aVertexPosition;', 'attribute vec2 aTextureCoord;', 'attribute float aColor;', 'uniform mat3 translationMatrix;', 'uniform vec2 projectionVector;', 'varying vec2 vTextureCoord;', 'uniform vec2 offsetVector;', 'varying float 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;', '}' ]; }; /** * Initialises the shader * @method init * */ PIXI.StripShader.prototype.init = function() { var gl = PIXI.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.translationMatrix = gl.getUniformLocation(program, 'translationMatrix'); this.alpha = gl.getUniformLocation(program, 'alpha'); this.program = program; }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * @class PrimitiveShader * @constructor * @param gl {WebGLContext} the current WebGL drawing context */ PIXI.PrimitiveShader = function(gl) { /** * @property gl * @type WebGLContext */ this.gl = gl; /** * @property {any} program - The WebGL program. */ this.program = null; /** * @property fragmentSrc * @type Array */ this.fragmentSrc = [ 'precision mediump float;', 'varying vec4 vColor;', 'void main(void) {', ' gl_FragColor = vColor;', '}' ]; /** * @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 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 + 1.0 , 0.0, 1.0);', ' vColor = aColor * vec4(tint * alpha, alpha);', '}' ]; this.init(); }; /** * 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'); // 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.attribute = null; }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * A set of functions used by the webGL renderer to draw the primitive graphics data * * @class WebGLGraphics * @private * @static */ PIXI.WebGLGraphics = function() { }; /** * Renders the graphics object * * @static * @private * @method renderGraphics * @param graphics {Graphics} * @param renderSession {Object} */ PIXI.WebGLGraphics.renderGraphics = function(graphics, renderSession)//projection, offset) { var gl = renderSession.gl; var projection = renderSession.projection, offset = renderSession.offset, shader = renderSession.shaderManager.primitiveShader; if(!graphics._webGL[gl.id])graphics._webGL[gl.id] = {points:[], indices:[], lastIndex:0, buffer:gl.createBuffer(), indexBuffer:gl.createBuffer()}; var webGL = graphics._webGL[gl.id]; if(graphics.dirty) { graphics.dirty = false; if(graphics.clearDirty) { graphics.clearDirty = false; webGL.lastIndex = 0; webGL.points = []; webGL.indices = []; } PIXI.WebGLGraphics.updateGraphics(graphics, gl); } renderSession.shaderManager.activatePrimitiveShader(); // This could be speeded up for sure! // set the matrix transform gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); 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.uniform1f(shader.alpha, graphics.worldAlpha); gl.bindBuffer(gl.ARRAY_BUFFER, webGL.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, webGL.indexBuffer); gl.drawElements(gl.TRIANGLE_STRIP, webGL.indices.length, gl.UNSIGNED_SHORT, 0 ); renderSession.shaderManager.deactivatePrimitiveShader(); // return to default shader... // PIXI.activateShader(PIXI.defaultShader); }; /** * Updates the graphics object * * @static * @private * @method updateGraphics * @param graphicsData {Graphics} The graphics object to update * @param gl {WebGLContext} the current WebGL drawing context */ PIXI.WebGLGraphics.updateGraphics = function(graphics, gl) { var webGL = graphics._webGL[gl.id]; for (var i = webGL.lastIndex; i < graphics.graphicsData.length; i++) { var data = graphics.graphicsData[i]; if(data.type === PIXI.Graphics.POLY) { if(data.fill) { if(data.points.length>3) PIXI.WebGLGraphics.buildPoly(data, webGL); } if(data.lineWidth > 0) { PIXI.WebGLGraphics.buildLine(data, webGL); } } else if(data.type === PIXI.Graphics.RECT) { PIXI.WebGLGraphics.buildRectangle(data, webGL); } else if(data.type === PIXI.Graphics.CIRC || data.type === PIXI.Graphics.ELIP) { PIXI.WebGLGraphics.buildCircle(data, webGL); } } webGL.lastIndex = graphics.graphicsData.length; webGL.glPoints = new Float32Array(webGL.points); gl.bindBuffer(gl.ARRAY_BUFFER, webGL.buffer); gl.bufferData(gl.ARRAY_BUFFER, webGL.glPoints, gl.STATIC_DRAW); webGL.glIndicies = new Uint16Array(webGL.indices); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGL.indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, webGL.glIndicies, gl.STATIC_DRAW); }; /** * Builds a rectangle to draw * * @static * @private * @method buildRectangle * @param graphicsData {Graphics} The graphics object containing all the necessary properties * @param webGLData {Object} */ PIXI.WebGLGraphics.buildRectangle = function(graphicsData, webGLData) { // --- // // need to convert points to a nice regular data // var rectData = graphicsData.points; var x = rectData[0]; var y = rectData[1]; var width = rectData[2]; var height = rectData[3]; if(graphicsData.fill) { var color = PIXI.hex2rgb(graphicsData.fillColor); var alpha = graphicsData.fillAlpha; var r = color[0] * alpha; var g = color[1] * alpha; var b = color[2] * alpha; var verts = webGLData.points; var indices = webGLData.indices; var vertPos = verts.length/6; // start verts.push(x, y); verts.push(r, g, b, alpha); verts.push(x + width, y); verts.push(r, g, b, alpha); verts.push(x , y + height); verts.push(r, g, b, alpha); verts.push(x + width, y + height); verts.push(r, g, b, alpha); // insert 2 dead triangles.. indices.push(vertPos, vertPos, vertPos+1, vertPos+2, vertPos+3, vertPos+3); } if(graphicsData.lineWidth) { var tempPoints = graphicsData.points; graphicsData.points = [x, y, x + width, y, x + width, y + height, x, y + height, x, y]; PIXI.WebGLGraphics.buildLine(graphicsData, webGLData); graphicsData.points = tempPoints; } }; /** * Builds a circle to draw * * @static * @private * @method buildCircle * @param graphicsData {Graphics} The graphics object to draw * @param webGLData {Object} */ PIXI.WebGLGraphics.buildCircle = function(graphicsData, webGLData) { // need to convert points to a nice regular data var rectData = graphicsData.points; var x = rectData[0]; var y = rectData[1]; var width = rectData[2]; var height = rectData[3]; var totalSegs = 40; var seg = (Math.PI * 2) / totalSegs ; var i = 0; if(graphicsData.fill) { var color = PIXI.hex2rgb(graphicsData.fillColor); var alpha = graphicsData.fillAlpha; var r = color[0] * alpha; var g = color[1] * alpha; var b = color[2] * alpha; var verts = webGLData.points; var indices = webGLData.indices; var vecPos = verts.length/6; indices.push(vecPos); for (i = 0; i < totalSegs + 1 ; i++) { verts.push(x,y, r, g, b, alpha); verts.push(x + Math.sin(seg * i) * width, y + Math.cos(seg * i) * height, r, g, b, alpha); indices.push(vecPos++, vecPos++); } indices.push(vecPos-1); } if(graphicsData.lineWidth) { var tempPoints = graphicsData.points; graphicsData.points = []; for (i = 0; i < totalSegs + 1; i++) { graphicsData.points.push(x + Math.sin(seg * i) * width, y + Math.cos(seg * i) * height); } PIXI.WebGLGraphics.buildLine(graphicsData, webGLData); graphicsData.points = tempPoints; } }; /** * Builds a line to draw * * @static * @private * @method buildLine * @param graphicsData {Graphics} The graphics object containing all the necessary properties * @param webGLData {Object} */ PIXI.WebGLGraphics.buildLine = function(graphicsData, webGLData) { // TODO OPTIMISE! var i = 0; var points = graphicsData.points; if(points.length === 0)return; // if the line width is an odd number add 0.5 to align to a whole pixel if(graphicsData.lineWidth%2) { for (i = 0; i < points.length; i++) { points[i] += 0.5; } } // get first and last point.. figure out the middle! var firstPoint = new PIXI.Point( points[0], points[1] ); var lastPoint = new PIXI.Point( points[points.length - 2], points[points.length - 1] ); // if the first point is the last point - gonna have issues :) if(firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y) { points.pop(); points.pop(); lastPoint = new PIXI.Point( points[points.length - 2], points[points.length - 1] ); var midPointX = lastPoint.x + (firstPoint.x - lastPoint.x) *0.5; var midPointY = lastPoint.y + (firstPoint.y - lastPoint.y) *0.5; points.unshift(midPointX, midPointY); points.push(midPointX, midPointY); } var verts = webGLData.points; var indices = webGLData.indices; var length = points.length / 2; var indexCount = points.length; var indexStart = verts.length/6; // DRAW the Line var width = graphicsData.lineWidth / 2; // sort color var color = PIXI.hex2rgb(graphicsData.lineColor); var alpha = graphicsData.lineAlpha; var r = color[0] * alpha; var g = color[1] * alpha; var b = color[2] * alpha; var px, py, p1x, p1y, p2x, p2y, p3x, p3y; var perpx, perpy, perp2x, perp2y, perp3x, perp3y; var a1, b1, c1, a2, b2, c2; var denom, pdist, dist; p1x = points[0]; p1y = points[1]; p2x = points[2]; p2y = points[3]; perpx = -(p1y - p2y); perpy = p1x - p2x; dist = Math.sqrt(perpx*perpx + perpy*perpy); perpx /= dist; perpy /= dist; perpx *= width; perpy *= width; // start verts.push(p1x - perpx , p1y - perpy, r, g, b, alpha); verts.push(p1x + perpx , p1y + perpy, r, g, b, alpha); for (i = 1; i < length-1; i++) { p1x = points[(i-1)*2]; p1y = points[(i-1)*2 + 1]; p2x = points[(i)*2]; p2y = points[(i)*2 + 1]; p3x = points[(i+1)*2]; p3y = points[(i+1)*2 + 1]; perpx = -(p1y - p2y); perpy = p1x - p2x; dist = Math.sqrt(perpx*perpx + perpy*perpy); perpx /= dist; perpy /= dist; perpx *= width; perpy *= width; perp2x = -(p2y - p3y); perp2y = p2x - p3x; dist = Math.sqrt(perp2x*perp2x + perp2y*perp2y); perp2x /= dist; perp2y /= dist; perp2x *= width; perp2y *= width; a1 = (-perpy + p1y) - (-perpy + p2y); b1 = (-perpx + p2x) - (-perpx + p1x); c1 = (-perpx + p1x) * (-perpy + p2y) - (-perpx + p2x) * (-perpy + p1y); a2 = (-perp2y + p3y) - (-perp2y + p2y); b2 = (-perp2x + p2x) - (-perp2x + p3x); c2 = (-perp2x + p3x) * (-perp2y + p2y) - (-perp2x + p2x) * (-perp2y + p3y); denom = a1*b2 - a2*b1; if(Math.abs(denom) < 0.1 ) { denom+=10.1; verts.push(p2x - perpx , p2y - perpy, r, g, b, alpha); verts.push(p2x + perpx , p2y + perpy, r, g, b, alpha); continue; } px = (b1*c2 - b2*c1)/denom; py = (a2*c1 - a1*c2)/denom; pdist = (px -p2x) * (px -p2x) + (py -p2y) + (py -p2y); if(pdist > 140 * 140) { perp3x = perpx - perp2x; perp3y = perpy - perp2y; dist = Math.sqrt(perp3x*perp3x + perp3y*perp3y); perp3x /= dist; perp3y /= dist; perp3x *= width; perp3y *= width; verts.push(p2x - perp3x, p2y -perp3y); verts.push(r, g, b, alpha); verts.push(p2x + perp3x, p2y +perp3y); verts.push(r, g, b, alpha); verts.push(p2x - perp3x, p2y -perp3y); verts.push(r, g, b, alpha); indexCount++; } else { verts.push(px , py); verts.push(r, g, b, alpha); verts.push(p2x - (px-p2x), p2y - (py - p2y)); verts.push(r, g, b, alpha); } } p1x = points[(length-2)*2]; p1y = points[(length-2)*2 + 1]; p2x = points[(length-1)*2]; p2y = points[(length-1)*2 + 1]; perpx = -(p1y - p2y); perpy = p1x - p2x; dist = Math.sqrt(perpx*perpx + perpy*perpy); perpx /= dist; perpy /= dist; perpx *= width; perpy *= width; verts.push(p2x - perpx , p2y - perpy); verts.push(r, g, b, alpha); verts.push(p2x + perpx , p2y + perpy); verts.push(r, g, b, alpha); indices.push(indexStart); for (i = 0; i < indexCount; i++) { indices.push(indexStart++); } indices.push(indexStart-1); }; /** * Builds a polygon to draw * * @static * @private * @method buildPoly * @param graphicsData {Graphics} The graphics object containing all the necessary properties * @param webGLData {Object} */ PIXI.WebGLGraphics.buildPoly = function(graphicsData, webGLData) { var points = graphicsData.points; if(points.length < 6)return; // get first and last point.. figure out the middle! var verts = webGLData.points; var indices = webGLData.indices; var length = points.length / 2; // sort color var color = PIXI.hex2rgb(graphicsData.fillColor); var alpha = graphicsData.fillAlpha; var r = color[0] * alpha; var g = color[1] * alpha; var b = color[2] * alpha; var triangles = PIXI.PolyK.Triangulate(points); var vertPos = verts.length / 6; var i = 0; for (i = 0; i < triangles.length; i+=3) { indices.push(triangles[i] + vertPos); indices.push(triangles[i] + vertPos); indices.push(triangles[i+1] + vertPos); indices.push(triangles[i+2] +vertPos); indices.push(triangles[i+2] + vertPos); } for (i = 0; i < length; i++) { verts.push(points[i * 2], points[i * 2 + 1], r, g, b, alpha); } }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ PIXI.glContexts = []; // this is where we store the webGL contexts for easy access. /** * 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 webGLBatch's. * So no need for Sprite Batch's or Sprite Cloud's * Dont forget to add the view to your DOM or you will not see anything :) * * @class WebGLRenderer * @constructor * @param width=0 {Number} the width of the canvas view * @param height=0 {Number} the height of the canvas view * @param view {HTMLCanvasElement} the canvas to use as a view, optional * @param transparent=false {Boolean} If the render view is transparent, default false * @param antialias=false {Boolean} sets antialias (only applicable in chrome at the moment) * */ PIXI.WebGLRenderer = function(width, height, view, transparent, antialias) { if(!PIXI.defaultRenderer)PIXI.defaultRenderer = this; this.type = PIXI.WEBGL_RENDERER; // do a catch.. only 1 webGL renderer.. /** * Whether the render view is transparent * * @property transparent * @type Boolean */ this.transparent = !!transparent; /** * The width of the canvas view * * @property width * @type Number * @default 800 */ this.width = width || 800; /** * The height of the canvas view * * @property height * @type Number * @default 600 */ this.height = height || 600; /** * The canvas element that everything is drawn to * * @property view * @type HTMLCanvasElement */ this.view = view || document.createElement( 'canvas' ); this.view.width = this.width; this.view.height = this.height; // deal with losing context.. this.contextLost = this.handleContextLost.bind(this); this.contextRestoredLost = this.handleContextRestored.bind(this); this.view.addEventListener('webglcontextlost', this.contextLost, false); this.view.addEventListener('webglcontextrestored', this.contextRestoredLost, false); this.options = { alpha: this.transparent, antialias:!!antialias, // SPEED UP?? premultipliedAlpha:!!transparent, stencil:true }; //try 'experimental-webgl' try { this.gl = this.view.getContext('experimental-webgl', this.options); } catch (e) { //try 'webgl' try { this.gl = this.view.getContext('webgl', this.options); } catch (e2) { // fail, not able to get a context throw new Error(' This browser does not support webGL. Try using the canvas renderer' + this); } } var gl = this.gl; this.glContextId = gl.id = PIXI.WebGLRenderer.glContextId ++; PIXI.glContexts[this.glContextId] = gl; if(!PIXI.blendModesWebGL) { PIXI.blendModesWebGL = []; PIXI.blendModesWebGL[PIXI.blendModes.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.ADD] = [gl.SRC_ALPHA, gl.DST_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.SCREEN] = [gl.SRC_ALPHA, gl.ONE]; PIXI.blendModesWebGL[PIXI.blendModes.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } this.projection = new PIXI.Point(); this.projection.x = this.width/2; this.projection.y = -this.height/2; this.offset = new PIXI.Point(0, 0); this.resize(this.width, this.height); this.contextLost = false; // time to create the render managers! each one focuses on managine a state in webGL this.shaderManager = new PIXI.WebGLShaderManager(gl); // deals with managing the shader programs and their attribs this.spriteBatch = new PIXI.WebGLSpriteBatch(gl); // manages the rendering of sprites this.maskManager = new PIXI.WebGLMaskManager(gl); // manages the masks using the stencil buffer this.filterManager = new PIXI.WebGLFilterManager(gl, this.transparent); // manages the filters this.renderSession = {}; 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.spriteBatch = this.spriteBatch; gl.useProgram(this.shaderManager.defaultShader.program); gl.disable(gl.DEPTH_TEST); gl.disable(gl.CULL_FACE); gl.enable(gl.BLEND); gl.colorMask(true, true, true, this.transparent); }; // constructor PIXI.WebGLRenderer.prototype.constructor = PIXI.WebGLRenderer; /** * Renders the stage to its webGL view * * @method render * @param stage {Stage} the Stage element to be rendered */ PIXI.WebGLRenderer.prototype.render = function(stage) { if(this.contextLost)return; // if rendering a new stage clear the batches.. if(this.__stage !== stage) { if(stage.interactive)stage.interactionManager.removeEvents(); // TODO make this work // dont think this is needed any more? this.__stage = stage; } // update any textures this includes uvs and uploading them to the gpu PIXI.WebGLRenderer.updateTextures(); // update the scene graph stage.updateTransform(); var gl = this.gl; // -- Does this need to be set every frame? -- // //gl.colorMask(true, true, true, this.transparent); 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.transparent) { gl.clearColor(0, 0, 0, 0); } else { gl.clearColor(stage.backgroundColorSplit[0],stage.backgroundColorSplit[1],stage.backgroundColorSplit[2], 1); } gl.clear(gl.COLOR_BUFFER_BIT); this.renderDisplayObject( stage, this.projection ); // interaction if(stage.interactive) { //need to add some events! if(!stage._interactiveEventsAdded) { stage._interactiveEventsAdded = true; stage.interactionManager.setTarget(this); } } else { if(stage._interactiveEventsAdded) { stage._interactiveEventsAdded = false; stage.interactionManager.setTarget(this); } } /* //can simulate context loss in Chrome like so: this.view.onmousedown = function(ev) { console.dir(this.gl.getSupportedExtensions()); var ext = ( gl.getExtension("WEBGL_scompressed_texture_s3tc") // gl.getExtension("WEBGL_compressed_texture_s3tc") || // gl.getExtension("MOZ_WEBGL_compressed_texture_s3tc") || // gl.getExtension("WEBKIT_WEBGL_compressed_texture_s3tc") ); console.dir(ext); var loseCtx = this.gl.getExtension("WEBGL_lose_context"); console.log("killing context"); loseCtx.loseContext(); setTimeout(function() { console.log("restoring context..."); loseCtx.restoreContext(); }.bind(this), 1000); }.bind(this); */ }; /** * 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) { // reset the render session data.. this.renderSession.drawCount = 0; this.renderSession.currentBlendMode = 9999; this.renderSession.projection = projection; 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); // finish the sprite batch this.spriteBatch.end(); }; /** * Updates the textures loaded into this webgl renderer * * @static * @method updateTextures * @private */ PIXI.WebGLRenderer.updateTextures = function() { var i = 0; //TODO break this out into a texture manager... //for (i = 0; i < PIXI.texturesToUpdate.length; i++) // PIXI.WebGLRenderer.updateTexture(PIXI.texturesToUpdate[i]); for (i=0; i < PIXI.Texture.frameUpdates.length; i++) PIXI.WebGLRenderer.updateTextureFrame(PIXI.Texture.frameUpdates[i]); for (i = 0; i < PIXI.texturesToDestroy.length; i++) PIXI.WebGLRenderer.destroyTexture(PIXI.texturesToDestroy[i]); PIXI.texturesToUpdate.length = 0; PIXI.texturesToDestroy.length = 0; PIXI.Texture.frameUpdates.length = 0; }; /** * Destroys a loaded webgl texture * * @method destroyTexture * @param texture {Texture} The texture to update * @private */ PIXI.WebGLRenderer.destroyTexture = function(texture) { //TODO break this out into a texture manager... for (var i = texture._glTextures.length - 1; i >= 0; i--) { var glTexture = texture._glTextures[i]; var gl = PIXI.glContexts[i]; if(gl && glTexture) { gl.deleteTexture(glTexture); } } texture._glTextures.length = 0; }; /** * * @method updateTextureFrame * @param texture {Texture} The texture to update the frame from * @private */ PIXI.WebGLRenderer.updateTextureFrame = function(texture) { texture.updateFrame = false; // now set the uvs. Figured that the uv data sits with a texture rather than a sprite. // so uv data is stored on the texture itself texture._updateWebGLuvs(); }; /** * 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.height = height; this.view.width = width; this.view.height = height; this.gl.viewport(0, 0, this.width, this.height); this.projection.x = this.width/2; this.projection.y = -this.height/2; }; /** * Creates a WebGL texture * * @method createWebGLTexture * @param texture {Texture} the texture to render * @param gl {webglContext} the WebGL context * @static */ PIXI.createWebGLTexture = function(texture, gl) { if(texture.hasLoaded) { texture._glTextures[gl.id] = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true); 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); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); // reguler... 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); } gl.bindTexture(gl.TEXTURE_2D, null); } return texture._glTextures[gl.id]; }; /** * Updates a WebGL texture * * @method updateWebGLTexture * @param texture {Texture} the texture to update * @param gl {webglContext} the WebGL context * @private */ PIXI.updateWebGLTexture = function(texture, gl) { if( texture._glTextures[gl.id] ) { gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true); 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); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); // reguler... 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); } gl.bindTexture(gl.TEXTURE_2D, null); } }; /** * Handles a lost webgl context * * @method handleContextLost * @param event {Event} * @private */ PIXI.WebGLRenderer.prototype.handleContextLost = function(event) { event.preventDefault(); this.contextLost = true; }; /** * Handles a restored webgl context * * @method handleContextRestored * @param event {Event} * @private */ PIXI.WebGLRenderer.prototype.handleContextRestored = function() { //try 'experimental-webgl' try { this.gl = this.view.getContext('experimental-webgl', this.options); } catch (e) { //try 'webgl' try { this.gl = this.view.getContext('webgl', this.options); } catch (e2) { // fail, not able to get a context throw new Error(' This browser does not support webGL. Try using the canvas renderer' + this); } } var gl = this.gl; gl.id = PIXI.WebGLRenderer.glContextId ++; // need to set the context... this.shaderManager.setContext(gl); this.spriteBatch.setContext(gl); this.maskManager.setContext(gl); this.filterManager.setContext(gl); this.renderSession.gl = this.gl; gl.disable(gl.DEPTH_TEST); gl.disable(gl.CULL_FACE); gl.enable(gl.BLEND); gl.colorMask(true, true, true, this.transparent); this.gl.viewport(0, 0, this.width, this.height); for(var key in PIXI.TextureCache) { var texture = PIXI.TextureCache[key].baseTexture; texture._glTextures = []; } /** * Whether the context was lost * @property contextLost * @type Boolean */ this.contextLost = false; }; /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @method destroy */ PIXI.WebGLRenderer.prototype.destroy = function() { // deal with losing context.. // remove listeners this.view.removeEventListener('webglcontextlost', this.contextLost); this.view.removeEventListener('webglcontextrestored', this.contextRestoredLost); PIXI.glContexts[this.glContextId] = null; this.projection = null; this.offset = null; // time to create the render managers! each one focuses on managine a state in webGL 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.WebGLRenderer.glContextId = 0; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * @class WebGLMaskManager * @constructor * @param gl {WebGLContext} the current WebGL drawing context * @private */ PIXI.WebGLMaskManager = function(gl) { this.maskStack = []; this.maskPosition = 0; this.setContext(gl); }; /** * 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 {RenderSession} */ PIXI.WebGLMaskManager.prototype.pushMask = function(maskData, renderSession) { var gl = this.gl; if(this.maskStack.length === 0) { gl.enable(gl.STENCIL_TEST); gl.stencilFunc(gl.ALWAYS,1,1); } // maskData.visible = false; this.maskStack.push(maskData); gl.colorMask(false, false, false, true); gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); PIXI.WebGLGraphics.renderGraphics(maskData, renderSession); gl.colorMask(true, true, true, true); gl.stencilFunc(gl.NOTEQUAL,0, this.maskStack.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); }; /** * Removes the last filter from the filter stack and doesn't return it * @method popMask * * @param renderSession {RenderSession} an object containing all the useful parameters */ PIXI.WebGLMaskManager.prototype.popMask = function(renderSession) { var gl = this.gl; var maskData = this.maskStack.pop(); if(maskData) { gl.colorMask(false, false, false, false); //gl.stencilFunc(gl.ALWAYS,1,1); gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); PIXI.WebGLGraphics.renderGraphics(maskData, renderSession); gl.colorMask(true, true, true, true); gl.stencilFunc(gl.NOTEQUAL,0,this.maskStack.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } if(this.maskStack.length === 0)gl.disable(gl.STENCIL_TEST); }; /** * Destroys the mask stack * @method destroy */ PIXI.WebGLMaskManager.prototype.destroy = function() { this.maskStack = null; this.gl = null; }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * @class WebGLShaderManager * @constructor * @param gl {WebGLContext} the current WebGL drawing context * @private */ PIXI.WebGLShaderManager = function(gl) { this.maxAttibs = 10; this.attribState = []; this.tempAttribState = []; for (var i = 0; i < this.maxAttibs; i++) { this.attribState[i] = false; } this.setContext(gl); // the final one is used for the rendering strips //this.stripShader = new PIXI.StripShader(gl); }; /** * Initialises the context and the properties * @method setContext * @param gl {WebGLContext} the current WebGL drawing context * @param transparent {Boolean} Whether or not the drawing context should be transparent */ PIXI.WebGLShaderManager.prototype.setContext = function(gl) { this.gl = gl; // the next one is used for rendering primatives this.primitiveShader = new PIXI.PrimitiveShader(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); this.activateShader(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-up the given shader * * @method activateShader * @param shader {Object} the shader that is going to be activated */ PIXI.WebGLShaderManager.prototype.activateShader = function(shader) { //if(this.currentShader == shader)return; this.currentShader = shader; this.gl.useProgram(shader.program); this.setAttribs(shader.attributes); }; /** * Triggers the primitive shader * @method activatePrimitiveShader */ PIXI.WebGLShaderManager.prototype.activatePrimitiveShader = function() { var gl = this.gl; gl.useProgram(this.primitiveShader.program); this.setAttribs(this.primitiveShader.attributes); }; /** * Disable the primitive shader * @method deactivatePrimitiveShader */ PIXI.WebGLShaderManager.prototype.deactivatePrimitiveShader = function() { var gl = this.gl; gl.useProgram(this.defaultShader.program); this.setAttribs(this.defaultShader.attributes); }; /** * Destroys * @method destroy */ PIXI.WebGLShaderManager.prototype.destroy = function() { this.attribState = null; this.tempAttribState = null; this.primitiveShader.destroy(); this.defaultShader.destroy(); this.fastShader.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! * * 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 * @param gl {WebGLContext} the current WebGL drawing context * */ PIXI.WebGLSpriteBatch = function(gl) { /** * * * @property vertSize * @type Number */ this.vertSize = 6; /** * The number of images in the SpriteBatch before it flushes * @property size * @type Number */ this.size = 10000;//Math.pow(2, 16) / this.vertSize; //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.size * 6; //vertex data /** * Holds the vertices * * @property vertices * @type Float32Array */ this.vertices = new Float32Array(numVerts); //index data /** * Holds the indices * * @property indices * @type Uint16Array */ this.indices = new Uint16Array(numIndices); 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; } this.drawing = false; this.currentBatchSize = 0; this.currentBaseTexture = null; this.setContext(gl); }; /** * * @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; }; /** * * @method begin * * @param renderSession {RenderSession} the RenderSession */ 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 */ PIXI.WebGLSpriteBatch.prototype.render = function(sprite) { // check texture.. if(sprite.texture.baseTexture !== this.currentBaseTexture || this.currentBatchSize >= this.size) { this.flush(); this.currentBaseTexture = sprite.texture.baseTexture; } // check blend mode if(sprite.blendMode !== this.currentBlendMode) { this.setBlendMode(sprite.blendMode); } // get the uvs for the texture var uvs = sprite._uvs || sprite.texture._uvs; // if the uvs have not updated then no point rendering just yet! if(!uvs)return; // get the sprites current alpha var alpha = sprite.worldAlpha; var tint = sprite.tint; var verticies = this.vertices; var width = sprite.texture.frame.width; var height = sprite.texture.frame.height; // TODO trim?? var aX = sprite.anchor.x; var aY = sprite.anchor.y; var w0, w1, h0, h1; if (sprite.texture.trim) { // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. var trim = sprite.texture.trim; w1 = trim.x - aX * trim.width; w0 = w1 + width; h1 = trim.y - aY * trim.height; h0 = h1 + height; } else { w0 = (width ) * (1-aX); w1 = (width ) * -aX; h0 = height * (1-aY); h1 = height * -aY; } var index = this.currentBatchSize * 4 * this.vertSize; var worldTransform = sprite.worldTransform;//.toArray(); var a = worldTransform.a;//[0]; var b = worldTransform.c;//[3]; var c = worldTransform.b;//[1]; var d = worldTransform.d;//[4]; var tx = worldTransform.tx;//[2]; var ty = worldTransform.ty;///[5]; // xy verticies[index++] = a * w1 + c * h1 + tx; verticies[index++] = d * h1 + b * w1 + ty; // uv verticies[index++] = uvs.x0; verticies[index++] = uvs.y0; // color verticies[index++] = alpha; verticies[index++] = tint; // xy verticies[index++] = a * w0 + c * h1 + tx; verticies[index++] = d * h1 + b * w0 + ty; // uv verticies[index++] = uvs.x1; verticies[index++] = uvs.y1; // color verticies[index++] = alpha; verticies[index++] = tint; // xy verticies[index++] = a * w0 + c * h0 + tx; verticies[index++] = d * h0 + b * w0 + ty; // uv verticies[index++] = uvs.x2; verticies[index++] = uvs.y2; // color verticies[index++] = alpha; verticies[index++] = tint; // xy verticies[index++] = a * w1 + c * h0 + tx; verticies[index++] = d * h0 + b * w1 + ty; // uv verticies[index++] = uvs.x3; verticies[index++] = uvs.y3; // color verticies[index++] = alpha; verticies[index++] = tint; // increment the batchsize this.currentBatchSize++; }; /** * Renders a tilingSprite using the spriteBatch * @method renderTilingSprite * * @param sprite {TilingSprite} the tilingSprite to render */ PIXI.WebGLSpriteBatch.prototype.renderTilingSprite = function(tilingSprite) { var texture = tilingSprite.tilingTexture; if(texture.baseTexture !== this.currentBaseTexture || this.currentBatchSize >= this.size) { this.flush(); this.currentBaseTexture = texture.baseTexture; } // check blend mode if(tilingSprite.blendMode !== this.currentBlendMode) { this.setBlendMode(tilingSprite.blendMode); } // set the textures uvs temporarily // TODO create a separate texture so that we can tile part of a texture if(!tilingSprite._uvs)tilingSprite._uvs = new PIXI.TextureUvs(); var uvs = tilingSprite._uvs; tilingSprite.tilePosition.x %= texture.baseTexture.width; tilingSprite.tilePosition.y %= texture.baseTexture.height; var offsetX = tilingSprite.tilePosition.x/texture.baseTexture.width; var offsetY = tilingSprite.tilePosition.y/texture.baseTexture.height; var scaleX = (tilingSprite.width / texture.baseTexture.width) / (tilingSprite.tileScale.x * tilingSprite.tileScaleOffset.x); var scaleY = (tilingSprite.height / texture.baseTexture.height) / (tilingSprite.tileScale.y * tilingSprite.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 tilingSprites current alpha var alpha = tilingSprite.worldAlpha; var tint = tilingSprite.tint; var verticies = this.vertices; var width = tilingSprite.width; var height = tilingSprite.height; // TODO trim?? var aX = tilingSprite.anchor.x; // - tilingSprite.texture.trim.x var aY = tilingSprite.anchor.y; //- tilingSprite.texture.trim.y var w0 = width * (1-aX); var w1 = width * -aX; var h0 = height * (1-aY); var h1 = height * -aY; var index = this.currentBatchSize * 4 * this.vertSize; var worldTransform = tilingSprite.worldTransform; var a = worldTransform.a;//[0]; var b = worldTransform.c;//[3]; var c = worldTransform.b;//[1]; var d = worldTransform.d;//[4]; var tx = worldTransform.tx;//[2]; var ty = worldTransform.ty;///[5]; // xy verticies[index++] = a * w1 + c * h1 + tx; verticies[index++] = d * h1 + b * w1 + ty; // uv verticies[index++] = uvs.x0; verticies[index++] = uvs.y0; // color verticies[index++] = alpha; verticies[index++] = tint; // xy verticies[index++] = a * w0 + c * h1 + tx; verticies[index++] = d * h1 + b * w0 + ty; // uv verticies[index++] = uvs.x1; verticies[index++] = uvs.y1; // color verticies[index++] = alpha; verticies[index++] = tint; // xy verticies[index++] = a * w0 + c * h0 + tx; verticies[index++] = d * h0 + b * w0 + ty; // uv verticies[index++] = uvs.x2; verticies[index++] = uvs.y2; // color verticies[index++] = alpha; verticies[index++] = tint; // xy verticies[index++] = a * w1 + c * h0 + tx; verticies[index++] = d * h0 + b * w1 + ty; // uv verticies[index++] = uvs.x3; verticies[index++] = uvs.y3; // color verticies[index++] = alpha; verticies[index++] = tint; // increment the batchs this.currentBatchSize++; }; /** * 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; // bind the current texture gl.bindTexture(gl.TEXTURE_2D, this.currentBaseTexture._glTextures[gl.id] || PIXI.createWebGLTexture(this.currentBaseTexture, gl)); // 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); } // 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.WebGLSpriteBatch.prototype.stop = function() { this.flush(); }; /** * * @method start * */ PIXI.WebGLSpriteBatch.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 pointers var stride = this.vertSize * 4; gl.vertexAttribPointer(this.shader.aVertexPosition, 2, gl.FLOAT, false, stride, 0); gl.vertexAttribPointer(this.shader.aTextureCoord, 2, gl.FLOAT, false, stride, 2 * 4); gl.vertexAttribPointer(this.shader.colorAttribute, 2, gl.FLOAT, false, stride, 4 * 4); // set the blend mode.. if(this.currentBlendMode !== PIXI.blendModes.NORMAL) { this.setBlendMode(PIXI.blendModes.NORMAL); } }; /** * 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.WebGLSpriteBatch.prototype.setBlendMode = function(blendMode) { this.flush(); this.currentBlendMode = blendMode; var blendModeWebGL = PIXI.blendModesWebGL[this.currentBlendMode]; this.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); }; /** * 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 */ PIXI.WebGLFastSpriteBatch = function(gl) { this.vertSize = 10; this.maxSize = 6000;//Math.pow(2, 16) / this.vertSize; 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 this.vertices = new Float32Array(numVerts); //index data this.indices = new Uint16Array(numIndices); this.vertexBuffer = null; this.indexBuffer = null; 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; } this.drawing = false; this.currentBatchSize = 0; this.currentBaseTexture = null; this.currentBlendMode = 0; this.renderSession = null; this.shader = null; this.matrix = null; this.setContext(gl); }; 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); this.currentBlendMode = 99999; }; PIXI.WebGLFastSpriteBatch.prototype.begin = function(spriteBatch, renderSession) { this.renderSession = renderSession; this.shader = this.renderSession.shaderManager.fastShader; this.matrix = spriteBatch.worldTransform.toArray(true); this.start(); }; PIXI.WebGLFastSpriteBatch.prototype.end = function() { this.flush(); }; 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.currentBlendMode) { this.setBlendMode(sprite.blendMode); } for(var i=0,j= children.length; i= this.size) { this.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])PIXI.createWebGLTexture(this.currentBaseTexture, gl); gl.bindTexture(gl.TEXTURE_2D, this.currentBaseTexture._glTextures[gl.id]);// || PIXI.createWebGLTexture(this.currentBaseTexture, gl)); // 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++; }; PIXI.WebGLFastSpriteBatch.prototype.stop = function() { this.flush(); }; 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); // set the blend mode.. if(this.currentBlendMode !== PIXI.blendModes.NORMAL) { this.setBlendMode(PIXI.blendModes.NORMAL); } }; PIXI.WebGLFastSpriteBatch.prototype.setBlendMode = function(blendMode) { this.flush(); this.currentBlendMode = blendMode; var blendModeWebGL = PIXI.blendModesWebGL[this.currentBlendMode]; this.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * @class WebGLFilterManager * @constructor * @param gl {WebGLContext} the current WebGL drawing context * @param transparent {Boolean} Whether or not the drawing context should be transparent * @private */ PIXI.WebGLFilterManager = function(gl, transparent) { this.transparent = transparent; this.filterStack = []; this.offsetX = 0; this.offsetY = 0; this.setContext(gl); }; // API /** * 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; // 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.target.filterArea.x; this.offsetY += filterBlock.target.filterArea.y; var texture = this.texturePool.pop(); if(!texture) { texture = new PIXI.FilterTexture(this.gl, this.width, this.height); } else { texture.resize(this.width, this.height); } gl.bindTexture(gl.TEXTURE_2D, texture.texture); filterBlock.target.filterArea = filterBlock.target.getBounds(); var filterArea = filterBlock.target.filterArea; var padidng = filter.padding; filterArea.x -= padidng; filterArea.y -= padidng; filterArea.width += padidng * 2; filterArea.height += padidng * 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, filterArea.height); projection.x = filterArea.width/2; projection.y = -filterArea.height/2; offset.x = -filterArea.x; offset.y = -filterArea.y; // update projection 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.target.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, filterArea.height); 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.height); // 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, this.transparent); } else { var currentFilter = this.filterStack[this.filterStack.length-1]; filterArea = currentFilter.target.filterArea; sizeX = filterArea.width; sizeY = filterArea.height; offsetX = filterArea.x; offsetY = filterArea.y; buffer = currentFilter._glFilterTexture.frameBuffer; } // TODO need toremove thease global elements.. projection.x = sizeX/2; projection.y = -sizeY/2; offset.x = offsetX; offset.y = offsetY; filterArea = filterBlock.target.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, sizeY); // 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); // apply! this.applyFilterPass(filter, filterArea, sizeX, sizeY); // now restore the regular shader.. gl.useProgram(this.defaultShader.program); 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 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 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 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 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.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 * @private */ PIXI.FilterTexture = function(gl, width, height) { /** * @property gl * @type WebGLContext */ this.gl = gl; // next time to create a frame buffer and texture this.frameBuffer = gl.createFramebuffer(); this.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 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); this.resize(width, height); }; /** * 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); }; /** * 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 * * */ /** * A set of functions used to handle masking * * @class CanvasMaskManager */ PIXI.CanvasMaskManager = function() { }; /** * This method adds it to the current stack of masks * * @method pushMask * @param maskData the maskData that will be pushed * @param context {Context2D} the 2d drawing method of the canvas */ PIXI.CanvasMaskManager.prototype.pushMask = function(maskData, context) { context.save(); var cacheAlpha = maskData.alpha; var transform = maskData.worldTransform; context.setTransform(transform.a, transform.c, transform.b, transform.d, transform.tx, transform.ty); 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 context {Context2D} the 2d drawing method of the canvas */ PIXI.CanvasMaskManager.prototype.popMask = function(context) { context.restore(); }; /** * @author Mat Groves * * */ /** * @class CanvasTinter * @constructor * @static */ PIXI.CanvasTinter = function() { /// this.textureCach }; //PIXI.CanvasTinter.cachTint = true; /** * Basically this method just needs a sprite and a color and tints the sprite * with the given color * * @method getTintedTexture * @param sprite {Sprite} the sprite to tint * @param color {Number} the color to use to tint the sprite with */ PIXI.CanvasTinter.getTintedTexture = function(sprite, color) { var texture = sprite.texture; color = PIXI.CanvasTinter.roundColor(color); var stringColor = "#" + ("00000" + ( color | 0).toString(16)).substr(-6); texture.tintCache = texture.tintCache || {}; if(texture.tintCache[stringColor]) return texture.tintCache[stringColor]; // clone texture.. var canvas = PIXI.CanvasTinter.canvas || document.createElement("canvas"); //PIXI.CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); PIXI.CanvasTinter.tintMethod(texture, color, canvas); if(PIXI.CanvasTinter.convertTintToImage) { // is this better? var tintImage = new Image(); tintImage.src = canvas.toDataURL(); texture.tintCache[stringColor] = tintImage; } else { texture.tintCache[stringColor] = canvas; // if we are not converting the texture to an image then we need to lose the reference to the canvas PIXI.CanvasTinter.canvas = null; } return canvas; }; /** * Tint a texture using the "multiply" operation * @method tintWithMultiply * @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 frame = texture.frame; canvas.width = frame.width; canvas.height = frame.height; context.fillStyle = "#" + ("00000" + ( color | 0).toString(16)).substr(-6); context.fillRect(0, 0, frame.width, frame.height); context.globalCompositeOperation = "multiply"; context.drawImage(texture.baseTexture.source, frame.x, frame.y, frame.width, frame.height, 0, 0, frame.width, frame.height); context.globalCompositeOperation = "destination-atop"; context.drawImage(texture.baseTexture.source, frame.x, frame.y, frame.width, frame.height, 0, 0, frame.width, frame.height); }; /** * Tint a texture using the "overlay" operation * @method tintWithOverlay * @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.tintWithOverlay = function(texture, color, canvas) { var context = canvas.getContext( "2d" ); var frame = texture.frame; canvas.width = frame.width; canvas.height = frame.height; context.globalCompositeOperation = "copy"; context.fillStyle = "#" + ("00000" + ( color | 0).toString(16)).substr(-6); context.fillRect(0, 0, frame.width, frame.height); context.globalCompositeOperation = "destination-atop"; context.drawImage(texture.baseTexture.source, frame.x, frame.y, frame.width, frame.height, 0, 0, frame.width, frame.height); //context.globalCompositeOperation = "copy"; }; /** * Tint a texture pixel per pixel * @method tintPerPixel * @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 frame = texture.frame; canvas.width = frame.width; canvas.height = frame.height; context.globalCompositeOperation = "copy"; context.drawImage(texture.baseTexture.source, frame.x, frame.y, frame.width, frame.height, 0, 0, frame.width, frame.height); var rgbValues = PIXI.hex2rgb(color); var r = rgbValues[0], g = rgbValues[1], b = rgbValues[2]; var pixelData = context.getImageData(0, 0, frame.width, frame.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; } context.putImageData(pixelData, 0, 0); }; /** * Rounds the specified color according to the PIXI.CanvasTinter.cacheStepsPerColorChannel * @method roundColor * @param color {number} the color to round, should be a hex color */ PIXI.CanvasTinter.roundColor = function(color) { var step = PIXI.CanvasTinter.cacheStepsPerColorChannel; var rgbValues = PIXI.hex2rgb(color); rgbValues[0] = Math.min(255, (rgbValues[0] / step) * step); rgbValues[1] = Math.min(255, (rgbValues[1] / step) * step); rgbValues[2] = Math.min(255, (rgbValues[2] / step) * step); return PIXI.rgb2hex(rgbValues); }; /** * * Number of steps which will be used as a cap when rounding colors * * @property cacheStepsPerColorChannel * @type Number */ PIXI.CanvasTinter.cacheStepsPerColorChannel = 8; /** * * Number of steps which will be used as a cap when rounding colors * * @property convertTintToImage * @type Boolean */ PIXI.CanvasTinter.convertTintToImage = false; /** * Whether or not the Canvas BlendModes are supported, consequently the ability to tint using the multiply method * * @property canUseMultiply * @type Boolean */ PIXI.CanvasTinter.canUseMultiply = PIXI.canUseNewCanvasBlendModes(); 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. * Dont forget to add the view to your DOM or you will not see anything :) * * @class CanvasRenderer * @constructor * @param width=800 {Number} the width of the canvas view * @param height=600 {Number} the height of the canvas view * @param [view] {HTMLCanvasElement} the canvas to use as a view, optional * @param [transparent=false] {Boolean} the transparency of the render view, default false */ PIXI.CanvasRenderer = function(width, height, view, transparent) { PIXI.defaultRenderer = PIXI.defaultRenderer || this; this.type = PIXI.CANVAS_RENDERER; /** * 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 = true; /** * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. * Handy for crisp pixel art and speed on legacy devices. * * @property roundPixels * @type Boolean * @default */ this.roundPixels = false; /** * Whether the render view is transparent * * @property transparent * @type Boolean */ this.transparent = !!transparent; if(!PIXI.blendModesCanvas) { PIXI.blendModesCanvas = []; if(PIXI.canUseNewCanvasBlendModes()) { PIXI.blendModesCanvas[PIXI.blendModes.NORMAL] = "source-over"; PIXI.blendModesCanvas[PIXI.blendModes.ADD] = "lighter"; //IS THIS OK??? PIXI.blendModesCanvas[PIXI.blendModes.MULTIPLY] = "multiply"; PIXI.blendModesCanvas[PIXI.blendModes.SCREEN] = "screen"; PIXI.blendModesCanvas[PIXI.blendModes.OVERLAY] = "overlay"; PIXI.blendModesCanvas[PIXI.blendModes.DARKEN] = "darken"; PIXI.blendModesCanvas[PIXI.blendModes.LIGHTEN] = "lighten"; PIXI.blendModesCanvas[PIXI.blendModes.COLOR_DODGE] = "color-dodge"; PIXI.blendModesCanvas[PIXI.blendModes.COLOR_BURN] = "color-burn"; PIXI.blendModesCanvas[PIXI.blendModes.HARD_LIGHT] = "hard-light"; PIXI.blendModesCanvas[PIXI.blendModes.SOFT_LIGHT] = "soft-light"; PIXI.blendModesCanvas[PIXI.blendModes.DIFFERENCE] = "difference"; PIXI.blendModesCanvas[PIXI.blendModes.EXCLUSION] = "exclusion"; PIXI.blendModesCanvas[PIXI.blendModes.HUE] = "hue"; PIXI.blendModesCanvas[PIXI.blendModes.SATURATION] = "saturation"; PIXI.blendModesCanvas[PIXI.blendModes.COLOR] = "color"; PIXI.blendModesCanvas[PIXI.blendModes.LUMINOSITY] = "luminosity"; } else { // this means that the browser does not support the cool new blend modes in canvas "cough" ie "cough" PIXI.blendModesCanvas[PIXI.blendModes.NORMAL] = "source-over"; PIXI.blendModesCanvas[PIXI.blendModes.ADD] = "lighter"; //IS THIS OK??? PIXI.blendModesCanvas[PIXI.blendModes.MULTIPLY] = "source-over"; PIXI.blendModesCanvas[PIXI.blendModes.SCREEN] = "source-over"; PIXI.blendModesCanvas[PIXI.blendModes.OVERLAY] = "source-over"; PIXI.blendModesCanvas[PIXI.blendModes.DARKEN] = "source-over"; PIXI.blendModesCanvas[PIXI.blendModes.LIGHTEN] = "source-over"; PIXI.blendModesCanvas[PIXI.blendModes.COLOR_DODGE] = "source-over"; PIXI.blendModesCanvas[PIXI.blendModes.COLOR_BURN] = "source-over"; PIXI.blendModesCanvas[PIXI.blendModes.HARD_LIGHT] = "source-over"; PIXI.blendModesCanvas[PIXI.blendModes.SOFT_LIGHT] = "source-over"; PIXI.blendModesCanvas[PIXI.blendModes.DIFFERENCE] = "source-over"; PIXI.blendModesCanvas[PIXI.blendModes.EXCLUSION] = "source-over"; PIXI.blendModesCanvas[PIXI.blendModes.HUE] = "source-over"; PIXI.blendModesCanvas[PIXI.blendModes.SATURATION] = "source-over"; PIXI.blendModesCanvas[PIXI.blendModes.COLOR] = "source-over"; PIXI.blendModesCanvas[PIXI.blendModes.LUMINOSITY] = "source-over"; } } /** * The width of the canvas view * * @property width * @type Number * @default 800 */ this.width = width || 800; /** * The height of the canvas view * * @property height * @type Number * @default 600 */ this.height = height || 600; /** * The canvas element that everything is drawn to * * @property view * @type HTMLCanvasElement */ this.view = view || document.createElement( "canvas" ); /** * The canvas 2d context that everything is drawn with * @property context * @type HTMLCanvasElement 2d Context */ this.context = this.view.getContext( "2d", { alpha: this.transparent } ); this.refresh = true; // hack to enable some hardware acceleration! //this.view.style["transform"] = "translatez(0)"; this.view.width = this.width; this.view.height = this.height; 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: null }; if("imageSmoothingEnabled" in this.context) this.renderSession.smoothProperty = "imageSmoothingEnabled"; else if("webkitImageSmoothingEnabled" in this.context) this.renderSession.smoothProperty = "webkitImageSmoothingEnabled"; else if("mozImageSmoothingEnabled" in this.context) this.renderSession.smoothProperty = "mozImageSmoothingEnabled"; else if("oImageSmoothingEnabled" in this.context) this.renderSession.smoothProperty = "oImageSmoothingEnabled"; }; // constructor PIXI.CanvasRenderer.prototype.constructor = PIXI.CanvasRenderer; /** * Renders the stage to its canvas view * * @method render * @param stage {Stage} the Stage element to be rendered */ PIXI.CanvasRenderer.prototype.render = function(stage) { // update textures if need be PIXI.texturesToUpdate.length = 0; PIXI.texturesToDestroy.length = 0; stage.updateTransform(); this.context.setTransform(1,0,0,1,0,0); this.context.globalAlpha = 1; if (!this.transparent && this.clearBeforeRender) { this.context.fillStyle = stage.backgroundColorString; this.context.fillRect(0, 0, this.width, this.height); } else if (this.transparent && this.clearBeforeRender) { this.context.clearRect(0, 0, this.width, this.height); } this.renderDisplayObject(stage); // run interaction! if(stage.interactive) { //need to add some events! if(!stage._interactiveEventsAdded) { stage._interactiveEventsAdded = true; stage.interactionManager.setTarget(this); } } // remove frame updates.. if(PIXI.Texture.frameUpdates.length > 0) { PIXI.Texture.frameUpdates.length = 0; } }; /** * 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.height = height; this.view.width = width; this.view.height = height; }; /** * Renders a display object * * @method renderDisplayObject * @param displayObject {DisplayObject} The displayObject to render * @param context {Context2D} the context 2d method of the canvas * @private */ PIXI.CanvasRenderer.prototype.renderDisplayObject = function(displayObject, context) { // no longer recursive! //var transform; //var context = this.context; this.renderSession.context = context || this.context; displayObject._renderCanvas(this.renderSession); }; /** * Renders a flat strip * * @method renderStripFlat * @param strip {Strip} The Strip to render * @private */ PIXI.CanvasRenderer.prototype.renderStripFlat = function(strip) { var context = this.context; var verticies = strip.verticies; var length = verticies.length/2; this.count++; context.beginPath(); for (var i=1; i < length-2; i++) { // draw some triangles! var index = i*2; var x0 = verticies[index], x1 = verticies[index+2], x2 = verticies[index+4]; var y0 = verticies[index+1], y1 = verticies[index+3], y2 = verticies[index+5]; context.moveTo(x0, y0); context.lineTo(x1, y1); context.lineTo(x2, y2); } context.fillStyle = "#FF0000"; context.fill(); context.closePath(); }; /** * Renders a strip * * @method renderStrip * @param strip {Strip} The Strip to render * @private */ PIXI.CanvasRenderer.prototype.renderStrip = function(strip) { var context = this.context; // draw triangles!! var verticies = strip.verticies; var uvs = strip.uvs; var length = verticies.length/2; this.count++; for (var i = 1; i < length-2; i++) { // draw some triangles! var index = i*2; var x0 = verticies[index], x1 = verticies[index+2], x2 = verticies[index+4]; var y0 = verticies[index+1], y1 = verticies[index+3], y2 = verticies[index+5]; var u0 = uvs[index] * strip.texture.width, u1 = uvs[index+2] * strip.texture.width, u2 = uvs[index+4]* strip.texture.width; var v0 = uvs[index+1]* strip.texture.height, v1 = uvs[index+3] * strip.texture.height, v2 = uvs[index+5]* strip.texture.height; 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(strip.texture.baseTexture.source, 0, 0); context.restore(); } }; /** * Creates a Canvas element of the given size * * @method CanvasBuffer * @param width {Number} the width for the newly created canvas * @param height {Number} the height for the newly created canvas * @static * @private */ PIXI.CanvasBuffer = function(width, height) { this.width = width; this.height = height; this.canvas = document.createElement( "canvas" ); this.context = this.canvas.getContext( "2d" ); this.canvas.width = width; this.canvas.height = height; }; /** * Clears the canvas that was created by the CanvasBuffer class * * @method clear * @private */ PIXI.CanvasBuffer.prototype.clear = function() { this.context.clearRect(0,0, this.width, this.height); }; /** * Resizes the canvas that was created by the CanvasBuffer class 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 * @private */ PIXI.CanvasBuffer.prototype.resize = function(width, height) { this.width = this.canvas.width = width; this.height = this.canvas.height = height; }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * A set of functions used by the canvas renderer to draw the primitive graphics data * * @class CanvasGraphics */ PIXI.CanvasGraphics = function() { }; /* * Renders the graphics object * * @static * @private * @method renderGraphics * @param graphics {Graphics} the actual graphics object to render * @param context {Context2D} the 2d drawing method of the canvas */ PIXI.CanvasGraphics.renderGraphics = function(graphics, context) { var worldAlpha = graphics.worldAlpha; var color = ''; for (var i = 0; i < graphics.graphicsData.length; i++) { var data = graphics.graphicsData[i]; var points = data.points; context.strokeStyle = color = '#' + ('00000' + ( data.lineColor | 0).toString(16)).substr(-6); context.lineWidth = data.lineWidth; if(data.type === PIXI.Graphics.POLY) { context.beginPath(); context.moveTo(points[0], points[1]); for (var j=1; j < points.length/2; j++) { context.lineTo(points[j * 2], points[j * 2 + 1]); } // if the first and last point are the same close the path - much neater :) if(points[0] === points[points.length-2] && points[1] === points[points.length-1]) { context.closePath(); } if(data.fill) { context.globalAlpha = data.fillAlpha * worldAlpha; context.fillStyle = color = '#' + ('00000' + ( data.fillColor | 0).toString(16)).substr(-6); context.fill(); } if(data.lineWidth) { context.globalAlpha = data.lineAlpha * worldAlpha; context.stroke(); } } else if(data.type === PIXI.Graphics.RECT) { if(data.fillColor || data.fillColor === 0) { context.globalAlpha = data.fillAlpha * worldAlpha; context.fillStyle = color = '#' + ('00000' + ( data.fillColor | 0).toString(16)).substr(-6); context.fillRect(points[0], points[1], points[2], points[3]); } if(data.lineWidth) { context.globalAlpha = data.lineAlpha * worldAlpha; context.strokeRect(points[0], points[1], points[2], points[3]); } } else if(data.type === PIXI.Graphics.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(points[0], points[1], points[2],0,2*Math.PI); context.closePath(); if(data.fill) { context.globalAlpha = data.fillAlpha * worldAlpha; context.fillStyle = color = '#' + ('00000' + ( data.fillColor | 0).toString(16)).substr(-6); context.fill(); } if(data.lineWidth) { context.globalAlpha = data.lineAlpha * worldAlpha; context.stroke(); } } else if(data.type === PIXI.Graphics.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas var ellipseData = data.points; var w = ellipseData[2] * 2; var h = ellipseData[3] * 2; var x = ellipseData[0] - w/2; var y = ellipseData[1] - h/2; context.beginPath(); var kappa = 0.5522848, ox = (w / 2) * kappa, // control point offset horizontal oy = (h / 2) * kappa, // control point offset vertical xe = x + w, // x-end ye = y + h, // y-end xm = x + w / 2, // x-middle ym = y + h / 2; // y-middle context.moveTo(x, ym); context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); context.closePath(); if(data.fill) { context.globalAlpha = data.fillAlpha * worldAlpha; context.fillStyle = color = '#' + ('00000' + ( data.fillColor | 0).toString(16)).substr(-6); context.fill(); } if(data.lineWidth) { context.globalAlpha = data.lineAlpha * worldAlpha; context.stroke(); } } } }; /* * Renders a graphics mask * * @static * @private * @method renderGraphicsMask * @param graphics {Graphics} the graphics which will be used as a mask * @param context {Context2D} the context 2d method of the canvas */ PIXI.CanvasGraphics.renderGraphicsMask = function(graphics, context) { var len = graphics.graphicsData.length; if(len === 0) return; if(len > 1) { len = 1; window.console.log('Pixi.js warning: masks in canvas can only mask using the first path in the graphics object'); } for (var i = 0; i < 1; i++) { var data = graphics.graphicsData[i]; var points = data.points; if(data.type === PIXI.Graphics.POLY) { context.beginPath(); context.moveTo(points[0], points[1]); for (var j=1; j < points.length/2; j++) { context.lineTo(points[j * 2], points[j * 2 + 1]); } // if the first and last point are the same close the path - much neater :) if(points[0] === points[points.length-2] && points[1] === points[points.length-1]) { context.closePath(); } } else if(data.type === PIXI.Graphics.RECT) { context.beginPath(); context.rect(points[0], points[1], points[2], points[3]); context.closePath(); } else if(data.type === PIXI.Graphics.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(points[0], points[1], points[2],0,2*Math.PI); context.closePath(); } else if(data.type === PIXI.Graphics.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas var ellipseData = data.points; var w = ellipseData[2] * 2; var h = ellipseData[3] * 2; var x = ellipseData[0] - w/2; var y = ellipseData[1] - h/2; context.beginPath(); var kappa = 0.5522848, ox = (w / 2) * kappa, // control point offset horizontal oy = (h / 2) * kappa, // control point offset vertical xe = x + w, // x-end ye = y + h, // y-end xm = x + w / 2, // x-middle ym = y + h / 2; // y-middle context.moveTo(x, ym); context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); context.closePath(); } } }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * The Graphics class contains a set of methods that you can use to create primitive shapes and lines. * It is important to know that with the webGL renderer only simple polygons can be filled at this stage * Complex polygons will not be filled. Heres an example of a complex polygon: http://www.goodboydigital.com/wp-content/uploads/2013/06/complexPolygon.png * * @class Graphics * @extends DisplayObjectContainer * @constructor */ PIXI.Graphics = function() { PIXI.DisplayObjectContainer.call( this ); this.renderable = true; /** * The alpha of the fill of this graphics object * * @property fillAlpha * @type Number */ this.fillAlpha = 1; /** * The width of any lines drawn * * @property lineWidth * @type Number */ this.lineWidth = 0; /** * The color of any lines drawn * * @property lineColor * @type String */ this.lineColor = "black"; /** * Graphics data * * @property graphicsData * @type Array * @private */ this.graphicsData = []; /** * The tint applied to the graphic shape. This is a hex value * * @property tint * @type Number * @default 0xFFFFFF */ this.tint = 0xFFFFFF;// * Math.random(); /** * The blend mode to be applied to the graphic shape * * @property blendMode * @type Number * @default PIXI.blendModes.NORMAL; */ this.blendMode = PIXI.blendModes.NORMAL; /** * Current path * * @property currentPath * @type Object * @private */ this.currentPath = {points:[]}; /** * Array containing some WebGL-related properties used by the WebGL renderer * * @property _webGL * @type Array * @private */ this._webGL = []; /** * Whether this shape is being used as a mask * * @property isMask * @type isMask */ this.isMask = false; /** * The bounds of the graphic shape as rectangle object * * @property bounds * @type Rectangle */ this.bounds = null; /** * the bounds' padding used for bounds calculation * * @property bounds * @type Number */ this.boundsPadding = 10; }; // constructor PIXI.Graphics.prototype = Object.create( PIXI.DisplayObjectContainer.prototype ); PIXI.Graphics.prototype.constructor = PIXI.Graphics; /** * If cacheAsBitmap is true the graphics object will then be rendered as if it was a sprite. * This is useful if your graphics element does not change often as it will speed up the rendering of the object * It is also usful as the graphics object will always be antialiased because it will be rendered using canvas * Not recommended if you are constanly redrawing the graphics element. * * @property cacheAsBitmap * @default false * @type Boolean * @private */ Object.defineProperty(PIXI.Graphics.prototype, "cacheAsBitmap", { get: function() { return this._cacheAsBitmap; }, set: function(value) { this._cacheAsBitmap = value; if(this._cacheAsBitmap) { this._generateCachedSprite(); } else { this.destroyCachedSprite(); this.dirty = true; } } }); /** * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * * @method lineStyle * @param lineWidth {Number} width of the line to draw, will update the object's stored style * @param color {Number} color of the line to draw, will update the object's stored style * @param alpha {Number} alpha of the line to draw, will update the object's stored style */ PIXI.Graphics.prototype.lineStyle = function(lineWidth, color, alpha) { if (!this.currentPath.points.length) this.graphicsData.pop(); this.lineWidth = lineWidth || 0; this.lineColor = color || 0; this.lineAlpha = (arguments.length < 3) ? 1 : alpha; this.currentPath = {lineWidth:this.lineWidth, lineColor:this.lineColor, lineAlpha:this.lineAlpha, fillColor:this.fillColor, fillAlpha:this.fillAlpha, fill:this.filling, points:[], type:PIXI.Graphics.POLY}; this.graphicsData.push(this.currentPath); return this; }; /** * Moves the current drawing position to (x, y). * * @method moveTo * @param x {Number} the X coordinate to move to * @param y {Number} the Y coordinate to move to */ PIXI.Graphics.prototype.moveTo = function(x, y) { if (!this.currentPath.points.length) this.graphicsData.pop(); this.currentPath = this.currentPath = {lineWidth:this.lineWidth, lineColor:this.lineColor, lineAlpha:this.lineAlpha, fillColor:this.fillColor, fillAlpha:this.fillAlpha, fill:this.filling, points:[], type:PIXI.Graphics.POLY}; this.currentPath.points.push(x, y); this.graphicsData.push(this.currentPath); return this; }; /** * Draws a line using the current line style from the current drawing position to (x, y); * the current drawing position is then set to (x, y). * * @method lineTo * @param x {Number} the X coordinate to draw to * @param y {Number} the Y coordinate to draw to */ PIXI.Graphics.prototype.lineTo = function(x, y) { this.currentPath.points.push(x, y); this.dirty = true; return this; }; /** * Specifies a simple one-color fill that subsequent calls to other Graphics methods * (such as lineTo() or drawCircle()) use when drawing. * * @method beginFill * @param color {Number} the color of the fill * @param alpha {Number} the alpha of the fill */ PIXI.Graphics.prototype.beginFill = function(color, alpha) { this.filling = true; this.fillColor = color || 0; this.fillAlpha = (arguments.length < 2) ? 1 : alpha; return this; }; /** * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. * * @method endFill */ PIXI.Graphics.prototype.endFill = function() { this.filling = false; this.fillColor = null; this.fillAlpha = 1; return this; }; /** * @method drawRect * * @param x {Number} The X coord of the top-left of the rectangle * @param y {Number} The Y coord of the top-left of the rectangle * @param width {Number} The width of the rectangle * @param height {Number} The height of the rectangle */ PIXI.Graphics.prototype.drawRect = function( x, y, width, height ) { if (!this.currentPath.points.length) this.graphicsData.pop(); this.currentPath = {lineWidth:this.lineWidth, lineColor:this.lineColor, lineAlpha:this.lineAlpha, fillColor:this.fillColor, fillAlpha:this.fillAlpha, fill:this.filling, points:[x, y, width, height], type:PIXI.Graphics.RECT}; this.graphicsData.push(this.currentPath); this.dirty = true; return this; }; /** * Draws a circle. * * @method drawCircle * @param x {Number} The X coordinate of the center of the circle * @param y {Number} The Y coordinate of the center of the circle * @param radius {Number} The radius of the circle */ PIXI.Graphics.prototype.drawCircle = function( x, y, radius) { if (!this.currentPath.points.length) this.graphicsData.pop(); this.currentPath = {lineWidth:this.lineWidth, lineColor:this.lineColor, lineAlpha:this.lineAlpha, fillColor:this.fillColor, fillAlpha:this.fillAlpha, fill:this.filling, points:[x, y, radius, radius], type:PIXI.Graphics.CIRC}; this.graphicsData.push(this.currentPath); this.dirty = true; return this; }; /** * Draws an ellipse. * * @method drawEllipse * @param x {Number} The X coordinate of the upper-left corner of the framing rectangle of this ellipse * @param y {Number} The Y coordinate of the upper-left corner of the framing rectangle of this ellipse * @param width {Number} The width of the ellipse * @param height {Number} The height of the ellipse */ PIXI.Graphics.prototype.drawEllipse = function( x, y, width, height) { if (!this.currentPath.points.length) this.graphicsData.pop(); this.currentPath = {lineWidth:this.lineWidth, lineColor:this.lineColor, lineAlpha:this.lineAlpha, fillColor:this.fillColor, fillAlpha:this.fillAlpha, fill:this.filling, points:[x, y, width, height], type:PIXI.Graphics.ELIP}; this.graphicsData.push(this.currentPath); this.dirty = true; return this; }; /** * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. * * @method clear */ PIXI.Graphics.prototype.clear = function() { this.lineWidth = 0; this.filling = false; this.dirty = true; this.clearDirty = true; this.graphicsData = []; this.bounds = null; //new PIXI.Rectangle(); return this; }; /** * Useful function that returns a texture of the graphics object that can then be used to create sprites * This can be quite useful if your geometry is complicated and needs to be reused multiple times. * * @method generateTexture * @return {Texture} a texture of the graphics object */ PIXI.Graphics.prototype.generateTexture = function() { var bounds = this.getBounds(); var canvasBuffer = new PIXI.CanvasBuffer(bounds.width, bounds.height); var texture = PIXI.Texture.fromCanvas(canvasBuffer.canvas); canvasBuffer.context.translate(-bounds.x,-bounds.y); PIXI.CanvasGraphics.renderGraphics(this, canvasBuffer.context); return texture; }; /** * Renders the object using the WebGL renderer * * @method _renderWebGL * @param renderSession {RenderSession} * @private */ PIXI.Graphics.prototype._renderWebGL = function(renderSession) { // if the sprite is not visible or the alpha is 0 then no need to render this element if(this.visible === false || this.alpha === 0 || this.isMask === true)return; if(this._cacheAsBitmap) { if(this.dirty) { this._generateCachedSprite(); // we will also need to update the texture on the gpu too! PIXI.updateWebGLTexture(this._cachedSprite.texture.baseTexture, renderSession.gl); this.dirty = false; } PIXI.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); return; } else { renderSession.spriteBatch.stop(); if(this._mask)renderSession.maskManager.pushMask(this.mask, renderSession); if(this._filters)renderSession.filterManager.pushFilter(this._filterBlock); // check blend mode if(this.blendMode !== renderSession.spriteBatch.currentBlendMode) { renderSession.spriteBatch.currentBlendMode = this.blendMode; var blendModeWebGL = PIXI.blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); } PIXI.WebGLGraphics.renderGraphics(this, renderSession); // only render if it has children! if(this.children.length) { renderSession.spriteBatch.start(); // simple render children! for(var i=0, j=this.children.length; i 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; return bounds; }; /** * Update the bounds of the object * * @method updateBounds */ PIXI.Graphics.prototype.updateBounds = function() { var minX = Infinity; var maxX = -Infinity; var minY = Infinity; var maxY = -Infinity; var points, x, y, w, h; for (var i = 0; i < this.graphicsData.length; i++) { var data = this.graphicsData[i]; var type = data.type; var lineWidth = data.lineWidth; points = data.points; if(type === PIXI.Graphics.RECT) { x = points[0] - lineWidth/2; y = points[1] - lineWidth/2; w = points[2] + lineWidth; h = points[3] + lineWidth; minX = x < minX ? x : minX; maxX = x + w > maxX ? x + w : maxX; minY = y < minY ? x : minY; maxY = y + h > maxY ? y + h : maxY; } else if(type === PIXI.Graphics.CIRC || type === PIXI.Graphics.ELIP) { x = points[0]; y = points[1]; w = points[2] + lineWidth/2; h = points[3] + lineWidth/2; minX = x - w < minX ? x - w : minX; maxX = x + w > maxX ? x + w : maxX; minY = y - h < minY ? y - h : minY; maxY = y + h > maxY ? y + h : maxY; } else { // POLY for (var j = 0; j < points.length; j+=2) { x = points[j]; y = points[j+1]; minX = x-lineWidth < minX ? x-lineWidth : minX; maxX = x+lineWidth > maxX ? x+lineWidth : maxX; minY = y-lineWidth < minY ? y-lineWidth : minY; maxY = y+lineWidth > maxY ? y+lineWidth : maxY; } } } var padding = this.boundsPadding; this.bounds = new PIXI.Rectangle(minX - padding, minY - padding, (maxX - minX) + padding * 2, (maxY - minY) + padding * 2); }; /** * Generates the cached sprite when the sprite has cacheAsBitmap = true * * @method _generateCachedSprite * @private */ PIXI.Graphics.prototype._generateCachedSprite = function() { var bounds = this.getLocalBounds(); if(!this._cachedSprite) { var canvasBuffer = new PIXI.CanvasBuffer(bounds.width, bounds.height); var texture = PIXI.Texture.fromCanvas(canvasBuffer.canvas); this._cachedSprite = new PIXI.Sprite(texture); this._cachedSprite.buffer = canvasBuffer; this._cachedSprite.worldTransform = this.worldTransform; } else { this._cachedSprite.buffer.resize(bounds.width, bounds.height); } // leverage the anchor to account for the offset of the element this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); // this._cachedSprite.buffer.context.save(); this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); PIXI.CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); // this._cachedSprite.buffer.context.restore(); }; PIXI.Graphics.prototype.destroyCachedSprite = function() { this._cachedSprite.texture.destroy(true); // let the gc collect the unused sprite // TODO could be object pooled! this._cachedSprite = null; }; // SOME TYPES: PIXI.Graphics.POLY = 0; PIXI.Graphics.RECT = 1; PIXI.Graphics.CIRC = 2; PIXI.Graphics.ELIP = 3; /** * @author Mat Groves http://matgroves.com/ */ /** * * @class Strip * @extends DisplayObjectContainer * @constructor * @param texture {Texture} The texture to use * @param width {Number} the width * @param height {Number} the height * */ PIXI.Strip = function(texture, width, height) { PIXI.DisplayObjectContainer.call( this ); this.texture = texture; this.blendMode = PIXI.blendModes.NORMAL; try { this.uvs = new Float32Array([0, 1, 1, 1, 1, 0, 0,1]); this.verticies = new Float32Array([0, 0, 0,0, 0,0, 0, 0, 0]); this.colors = new Float32Array([1, 1, 1, 1]); this.indices = new Uint16Array([0, 1, 2, 3]); } catch(error) { this.uvs = [0, 1, 1, 1, 1, 0, 0,1]; this.verticies = [0, 0, 0,0, 0,0, 0, 0, 0]; this.colors = [1, 1, 1, 1]; this.indices = [0, 1, 2, 3]; } /* this.uvs = new Float32Array() this.verticies = new Float32Array() this.colors = new Float32Array() this.indices = new Uint16Array() */ this.width = width; this.height = height; // load the texture! if(texture.baseTexture.hasLoaded) { this.width = this.texture.frame.width; this.height = this.texture.frame.height; this.updateFrame = true; } else { this.onTextureUpdateBind = this.onTextureUpdate.bind(this); this.texture.addEventListener( 'update', this.onTextureUpdateBind ); } this.renderable = true; }; // constructor PIXI.Strip.prototype = Object.create(PIXI.DisplayObjectContainer.prototype); PIXI.Strip.prototype.constructor = PIXI.Strip; /* * Sets the texture that the Strip will use * * @method setTexture * @param texture {Texture} the texture that will be used * @private */ 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; }; /* @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * * @class Rope * @constructor * @param texture {Texture} The texture to use * @param points {Array} * */ PIXI.Rope = function(texture, points) { PIXI.Strip.call( this, texture ); this.points = points; try { this.verticies = new Float32Array(points.length * 4); this.uvs = new Float32Array(points.length * 4); this.colors = new Float32Array(points.length * 2); this.indices = new Uint16Array(points.length * 2); } catch(error) { this.verticies = new Array(points.length * 4); this.uvs = new Array(points.length * 4); this.colors = new Array(points.length * 2); this.indices = new Array(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] = 1; 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 verticies = this.verticies; verticies[0] = lastPoint.x + perp.x; verticies[1] = lastPoint.y + perp.y; //+ 200 verticies[2] = lastPoint.x - perp.x; verticies[3] = lastPoint.y - perp.y;//+200 // time to do some smart drawing! var total = points.length, point, index, ratio, perpLength, num; for (var i = 1; 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; verticies[index] = point.x + perp.x; verticies[index+1] = point.y + perp.y; verticies[index+2] = point.x - perp.x; verticies[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 DisplayObjectContainer * @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 with of the tiling sprite * * @property width * @type Number */ this.width = width || 100; /** * The height of the tiling sprite * * @property height * @type Number */ this.height = height || 100; /** * 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(0,0); /** * 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; /** * The blend mode to be applied to the sprite * * @property blendMode * @type Number * @default PIXI.blendModes.NORMAL; */ this.blendMode = PIXI.blendModes.NORMAL; }; // constructor PIXI.TilingSprite.prototype = Object.create(PIXI.Sprite.prototype); PIXI.TilingSprite.prototype.constructor = PIXI.TilingSprite; /** * 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; } }); /** * When the texture is updated, this event will be fired to update the scale and frame * * @method onTextureUpdate * @param event * @private */ PIXI.TilingSprite.prototype.onTextureUpdate = function() { this.updateFrame = true; }; /** * Renders the object using the WebGL renderer * * @method _renderWebGL * @param renderSession {RenderSession} * @private */ PIXI.TilingSprite.prototype._renderWebGL = function(renderSession) { if(this.visible === false || this.alpha === 0)return; var i,j; if(this.mask || this.filters) { 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.tilingTexture)this.generateTilingTexture(true); else renderSession.spriteBatch.renderTilingSprite(this); // simple render children! for(i=0,j=this.children.length; i 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; }; /** * * @method generateTilingTexture * * @param forcePowerOfTwo {Boolean} Whether we want to force the texture to be a power of two */ PIXI.TilingSprite.prototype.generateTilingTexture = function(forcePowerOfTwo) { var texture = this.texture; if(!texture.baseTexture.hasLoaded)return; var baseTexture = texture.baseTexture; var frame = texture.frame; var targetWidth, targetHeight; // check that the frame is the same size as the base texture. var isFrame = frame.width !== baseTexture.width || frame.height !== baseTexture.height; this.tilingTexture = texture; var newTextureRequired = false; if(!forcePowerOfTwo) { if(isFrame) { targetWidth = frame.width; targetHeight = frame.height; newTextureRequired = true; } } else { targetWidth = PIXI.getNextPowerOfTwo(texture.frame.width); targetHeight = PIXI.getNextPowerOfTwo(texture.frame.height); if(frame.width !== targetWidth && frame.height !== targetHeight)newTextureRequired = true; } if(newTextureRequired) { var canvasBuffer = new PIXI.CanvasBuffer(targetWidth, targetHeight); canvasBuffer.context.drawImage(texture.baseTexture.source, frame.x, frame.y, frame.width, frame.height, 0, 0, targetWidth, targetHeight); this.tilingTexture = PIXI.Texture.fromCanvas(canvasBuffer.canvas); this.tileScaleOffset.x = frame.width / targetWidth; this.tileScaleOffset.y = frame.height / targetHeight; } this.tilingTexture.baseTexture._powerOf2 = true; }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 * based on pixi impact spine implementation made by Eemeli Kelokorpi (@ekelokorpi) https://github.com/ekelokorpi * * Awesome JS run time provided by EsotericSoftware * https://github.com/EsotericSoftware/spine-runtimes * */ /* * Awesome JS run time provided by EsotericSoftware * * https://github.com/EsotericSoftware/spine-runtimes * */ var spine = {}; spine.BoneData = function (name, parent) { this.name = name; this.parent = parent; }; spine.BoneData.prototype = { length: 0, x: 0, y: 0, rotation: 0, scaleX: 1, scaleY: 1 }; spine.SlotData = function (name, boneData) { this.name = name; this.boneData = boneData; }; spine.SlotData.prototype = { r: 1, g: 1, b: 1, a: 1, attachmentName: null }; spine.Bone = function (boneData, parent) { this.data = boneData; this.parent = parent; this.setToSetupPose(); }; spine.Bone.yDown = false; spine.Bone.prototype = { x: 0, y: 0, rotation: 0, scaleX: 1, scaleY: 1, m00: 0, m01: 0, worldX: 0, // a b x m10: 0, m11: 0, worldY: 0, // c d y worldRotation: 0, worldScaleX: 1, worldScaleY: 1, updateWorldTransform: function (flipX, flipY) { var parent = this.parent; if (parent != null) { this.worldX = this.x * parent.m00 + this.y * parent.m01 + parent.worldX; this.worldY = this.x * parent.m10 + this.y * parent.m11 + parent.worldY; this.worldScaleX = parent.worldScaleX * this.scaleX; this.worldScaleY = parent.worldScaleY * this.scaleY; this.worldRotation = parent.worldRotation + this.rotation; } else { this.worldX = this.x; this.worldY = this.y; this.worldScaleX = this.scaleX; this.worldScaleY = this.scaleY; this.worldRotation = this.rotation; } var radians = this.worldRotation * Math.PI / 180; var cos = Math.cos(radians); var sin = Math.sin(radians); this.m00 = cos * this.worldScaleX; this.m10 = sin * this.worldScaleX; this.m01 = -sin * this.worldScaleY; this.m11 = cos * this.worldScaleY; if (flipX) { this.m00 = -this.m00; this.m01 = -this.m01; } if (flipY) { this.m10 = -this.m10; this.m11 = -this.m11; } if (spine.Bone.yDown) { this.m10 = -this.m10; this.m11 = -this.m11; } }, setToSetupPose: function () { var data = this.data; this.x = data.x; this.y = data.y; this.rotation = data.rotation; this.scaleX = data.scaleX; this.scaleY = data.scaleY; } }; spine.Slot = function (slotData, skeleton, bone) { this.data = slotData; this.skeleton = skeleton; this.bone = bone; this.setToSetupPose(); }; spine.Slot.prototype = { r: 1, g: 1, b: 1, a: 1, _attachmentTime: 0, attachment: null, setAttachment: function (attachment) { this.attachment = attachment; this._attachmentTime = this.skeleton.time; }, setAttachmentTime: function (time) { this._attachmentTime = this.skeleton.time - time; }, getAttachmentTime: function () { return this.skeleton.time - this._attachmentTime; }, setToSetupPose: function () { var data = this.data; this.r = data.r; this.g = data.g; this.b = data.b; this.a = data.a; var slotDatas = this.skeleton.data.slots; for (var i = 0, n = slotDatas.length; i < n; i++) { if (slotDatas[i] == data) { this.setAttachment(!data.attachmentName ? null : this.skeleton.getAttachmentBySlotIndex(i, data.attachmentName)); break; } } } }; spine.Skin = function (name) { this.name = name; this.attachments = {}; }; spine.Skin.prototype = { addAttachment: function (slotIndex, name, attachment) { this.attachments[slotIndex + ":" + name] = attachment; }, getAttachment: function (slotIndex, name) { return this.attachments[slotIndex + ":" + name]; }, _attachAll: function (skeleton, oldSkin) { for (var key in oldSkin.attachments) { var colon = key.indexOf(":"); var slotIndex = parseInt(key.substring(0, colon), 10); var name = key.substring(colon + 1); var slot = skeleton.slots[slotIndex]; if (slot.attachment && slot.attachment.name == name) { var attachment = this.getAttachment(slotIndex, name); if (attachment) slot.setAttachment(attachment); } } } }; spine.Animation = function (name, timelines, duration) { this.name = name; this.timelines = timelines; this.duration = duration; }; spine.Animation.prototype = { apply: function (skeleton, time, loop) { if (loop && this.duration) time %= this.duration; var timelines = this.timelines; for (var i = 0, n = timelines.length; i < n; i++) timelines[i].apply(skeleton, time, 1); }, mix: function (skeleton, time, loop, alpha) { if (loop && this.duration) time %= this.duration; var timelines = this.timelines; for (var i = 0, n = timelines.length; i < n; i++) timelines[i].apply(skeleton, time, alpha); } }; spine.binarySearch = function (values, target, step) { var low = 0; var high = Math.floor(values.length / step) - 2; if (!high) return step; var current = high >>> 1; while (true) { if (values[(current + 1) * step] <= target) low = current + 1; else high = current; if (low == high) return (low + 1) * step; current = (low + high) >>> 1; } }; spine.linearSearch = function (values, target, step) { for (var i = 0, last = values.length - step; i <= last; i += step) if (values[i] > target) return i; return -1; }; spine.Curves = function (frameCount) { this.curves = []; // dfx, dfy, ddfx, ddfy, dddfx, dddfy, ... this.curves.length = (frameCount - 1) * 6; }; spine.Curves.prototype = { setLinear: function (frameIndex) { this.curves[frameIndex * 6] = 0/*LINEAR*/; }, setStepped: function (frameIndex) { this.curves[frameIndex * 6] = -1/*STEPPED*/; }, /** Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. * cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of * the difference between the keyframe's values. */ setCurve: function (frameIndex, cx1, cy1, cx2, cy2) { var subdiv_step = 1 / 10/*BEZIER_SEGMENTS*/; var subdiv_step2 = subdiv_step * subdiv_step; var subdiv_step3 = subdiv_step2 * subdiv_step; var pre1 = 3 * subdiv_step; var pre2 = 3 * subdiv_step2; var pre4 = 6 * subdiv_step2; var pre5 = 6 * subdiv_step3; var tmp1x = -cx1 * 2 + cx2; var tmp1y = -cy1 * 2 + cy2; var tmp2x = (cx1 - cx2) * 3 + 1; var tmp2y = (cy1 - cy2) * 3 + 1; var i = frameIndex * 6; var curves = this.curves; curves[i] = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv_step3; curves[i + 1] = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv_step3; curves[i + 2] = tmp1x * pre4 + tmp2x * pre5; curves[i + 3] = tmp1y * pre4 + tmp2y * pre5; curves[i + 4] = tmp2x * pre5; curves[i + 5] = tmp2y * pre5; }, getCurvePercent: function (frameIndex, percent) { percent = percent < 0 ? 0 : (percent > 1 ? 1 : percent); var curveIndex = frameIndex * 6; var curves = this.curves; var dfx = curves[curveIndex]; if (!dfx/*LINEAR*/) return percent; if (dfx == -1/*STEPPED*/) return 0; var dfy = curves[curveIndex + 1]; var ddfx = curves[curveIndex + 2]; var ddfy = curves[curveIndex + 3]; var dddfx = curves[curveIndex + 4]; var dddfy = curves[curveIndex + 5]; var x = dfx, y = dfy; var i = 10/*BEZIER_SEGMENTS*/ - 2; while (true) { if (x >= percent) { var lastX = x - dfx; var lastY = y - dfy; return lastY + (y - lastY) * (percent - lastX) / (x - lastX); } if (!i) break; i--; dfx += ddfx; dfy += ddfy; ddfx += dddfx; ddfy += dddfy; x += dfx; y += dfy; } return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. } }; spine.RotateTimeline = function (frameCount) { this.curves = new spine.Curves(frameCount); this.frames = []; // time, angle, ... this.frames.length = frameCount * 2; }; spine.RotateTimeline.prototype = { boneIndex: 0, getFrameCount: function () { return this.frames.length / 2; }, setFrame: function (frameIndex, time, angle) { frameIndex *= 2; this.frames[frameIndex] = time; this.frames[frameIndex + 1] = angle; }, apply: function (skeleton, time, alpha) { var frames = this.frames, amount; if (time < frames[0]) return; // Time is before first frame. var bone = skeleton.bones[this.boneIndex]; if (time >= frames[frames.length - 2]) { // Time is after last frame. amount = bone.data.rotation + frames[frames.length - 1] - bone.rotation; while (amount > 180) amount -= 360; while (amount < -180) amount += 360; bone.rotation += amount * alpha; return; } // Interpolate between the last frame and the current frame. var frameIndex = spine.binarySearch(frames, time, 2); var lastFrameValue = frames[frameIndex - 1]; var frameTime = frames[frameIndex]; var percent = 1 - (time - frameTime) / (frames[frameIndex - 2/*LAST_FRAME_TIME*/] - frameTime); percent = this.curves.getCurvePercent(frameIndex / 2 - 1, percent); amount = frames[frameIndex + 1/*FRAME_VALUE*/] - lastFrameValue; while (amount > 180) amount -= 360; while (amount < -180) amount += 360; amount = bone.data.rotation + (lastFrameValue + amount * percent) - bone.rotation; while (amount > 180) amount -= 360; while (amount < -180) amount += 360; bone.rotation += amount * alpha; } }; spine.TranslateTimeline = function (frameCount) { this.curves = new spine.Curves(frameCount); this.frames = []; // time, x, y, ... this.frames.length = frameCount * 3; }; spine.TranslateTimeline.prototype = { boneIndex: 0, getFrameCount: function () { return this.frames.length / 3; }, setFrame: function (frameIndex, time, x, y) { frameIndex *= 3; this.frames[frameIndex] = time; this.frames[frameIndex + 1] = x; this.frames[frameIndex + 2] = y; }, apply: function (skeleton, time, alpha) { var frames = this.frames; if (time < frames[0]) return; // Time is before first frame. var bone = skeleton.bones[this.boneIndex]; if (time >= frames[frames.length - 3]) { // Time is after last frame. bone.x += (bone.data.x + frames[frames.length - 2] - bone.x) * alpha; bone.y += (bone.data.y + frames[frames.length - 1] - bone.y) * alpha; return; } // Interpolate between the last frame and the current frame. var frameIndex = spine.binarySearch(frames, time, 3); var lastFrameX = frames[frameIndex - 2]; var lastFrameY = frames[frameIndex - 1]; var frameTime = frames[frameIndex]; var percent = 1 - (time - frameTime) / (frames[frameIndex + -3/*LAST_FRAME_TIME*/] - frameTime); percent = this.curves.getCurvePercent(frameIndex / 3 - 1, percent); bone.x += (bone.data.x + lastFrameX + (frames[frameIndex + 1/*FRAME_X*/] - lastFrameX) * percent - bone.x) * alpha; bone.y += (bone.data.y + lastFrameY + (frames[frameIndex + 2/*FRAME_Y*/] - lastFrameY) * percent - bone.y) * alpha; } }; spine.ScaleTimeline = function (frameCount) { this.curves = new spine.Curves(frameCount); this.frames = []; // time, x, y, ... this.frames.length = frameCount * 3; }; spine.ScaleTimeline.prototype = { boneIndex: 0, getFrameCount: function () { return this.frames.length / 3; }, setFrame: function (frameIndex, time, x, y) { frameIndex *= 3; this.frames[frameIndex] = time; this.frames[frameIndex + 1] = x; this.frames[frameIndex + 2] = y; }, apply: function (skeleton, time, alpha) { var frames = this.frames; if (time < frames[0]) return; // Time is before first frame. var bone = skeleton.bones[this.boneIndex]; if (time >= frames[frames.length - 3]) { // Time is after last frame. bone.scaleX += (bone.data.scaleX - 1 + frames[frames.length - 2] - bone.scaleX) * alpha; bone.scaleY += (bone.data.scaleY - 1 + frames[frames.length - 1] - bone.scaleY) * alpha; return; } // Interpolate between the last frame and the current frame. var frameIndex = spine.binarySearch(frames, time, 3); var lastFrameX = frames[frameIndex - 2]; var lastFrameY = frames[frameIndex - 1]; var frameTime = frames[frameIndex]; var percent = 1 - (time - frameTime) / (frames[frameIndex + -3/*LAST_FRAME_TIME*/] - frameTime); percent = this.curves.getCurvePercent(frameIndex / 3 - 1, percent); bone.scaleX += (bone.data.scaleX - 1 + lastFrameX + (frames[frameIndex + 1/*FRAME_X*/] - lastFrameX) * percent - bone.scaleX) * alpha; bone.scaleY += (bone.data.scaleY - 1 + lastFrameY + (frames[frameIndex + 2/*FRAME_Y*/] - lastFrameY) * percent - bone.scaleY) * alpha; } }; spine.ColorTimeline = function (frameCount) { this.curves = new spine.Curves(frameCount); this.frames = []; // time, r, g, b, a, ... this.frames.length = frameCount * 5; }; spine.ColorTimeline.prototype = { slotIndex: 0, getFrameCount: function () { return this.frames.length / 2; }, setFrame: function (frameIndex, time, x, y) { frameIndex *= 5; this.frames[frameIndex] = time; this.frames[frameIndex + 1] = r; this.frames[frameIndex + 2] = g; this.frames[frameIndex + 3] = b; this.frames[frameIndex + 4] = a; }, apply: function (skeleton, time, alpha) { var frames = this.frames; if (time < frames[0]) return; // Time is before first frame. var slot = skeleton.slots[this.slotIndex]; if (time >= frames[frames.length - 5]) { // Time is after last frame. var i = frames.length - 1; slot.r = frames[i - 3]; slot.g = frames[i - 2]; slot.b = frames[i - 1]; slot.a = frames[i]; return; } // Interpolate between the last frame and the current frame. var frameIndex = spine.binarySearch(frames, time, 5); var lastFrameR = frames[frameIndex - 4]; var lastFrameG = frames[frameIndex - 3]; var lastFrameB = frames[frameIndex - 2]; var lastFrameA = frames[frameIndex - 1]; var frameTime = frames[frameIndex]; var percent = 1 - (time - frameTime) / (frames[frameIndex - 5/*LAST_FRAME_TIME*/] - frameTime); percent = this.curves.getCurvePercent(frameIndex / 5 - 1, percent); var r = lastFrameR + (frames[frameIndex + 1/*FRAME_R*/] - lastFrameR) * percent; var g = lastFrameG + (frames[frameIndex + 2/*FRAME_G*/] - lastFrameG) * percent; var b = lastFrameB + (frames[frameIndex + 3/*FRAME_B*/] - lastFrameB) * percent; var a = lastFrameA + (frames[frameIndex + 4/*FRAME_A*/] - lastFrameA) * percent; if (alpha < 1) { slot.r += (r - slot.r) * alpha; slot.g += (g - slot.g) * alpha; slot.b += (b - slot.b) * alpha; slot.a += (a - slot.a) * alpha; } else { slot.r = r; slot.g = g; slot.b = b; slot.a = a; } } }; spine.AttachmentTimeline = function (frameCount) { this.curves = new spine.Curves(frameCount); this.frames = []; // time, ... this.frames.length = frameCount; this.attachmentNames = []; // time, ... this.attachmentNames.length = frameCount; }; spine.AttachmentTimeline.prototype = { slotIndex: 0, getFrameCount: function () { return this.frames.length; }, setFrame: function (frameIndex, time, attachmentName) { this.frames[frameIndex] = time; this.attachmentNames[frameIndex] = attachmentName; }, apply: function (skeleton, time, alpha) { var frames = this.frames; if (time < frames[0]) return; // Time is before first frame. var frameIndex; if (time >= frames[frames.length - 1]) // Time is after last frame. frameIndex = frames.length - 1; else frameIndex = spine.binarySearch(frames, time, 1) - 1; var attachmentName = this.attachmentNames[frameIndex]; skeleton.slots[this.slotIndex].setAttachment(!attachmentName ? null : skeleton.getAttachmentBySlotIndex(this.slotIndex, attachmentName)); } }; spine.SkeletonData = function () { this.bones = []; this.slots = []; this.skins = []; this.animations = []; }; spine.SkeletonData.prototype = { defaultSkin: null, /** @return May be null. */ findBone: function (boneName) { var bones = this.bones; for (var i = 0, n = bones.length; i < n; i++) if (bones[i].name == boneName) return bones[i]; return null; }, /** @return -1 if the bone was not found. */ findBoneIndex: function (boneName) { var bones = this.bones; for (var i = 0, n = bones.length; i < n; i++) if (bones[i].name == boneName) return i; return -1; }, /** @return May be null. */ findSlot: function (slotName) { var slots = this.slots; for (var i = 0, n = slots.length; i < n; i++) { if (slots[i].name == slotName) return slot[i]; } return null; }, /** @return -1 if the bone was not found. */ findSlotIndex: function (slotName) { var slots = this.slots; for (var i = 0, n = slots.length; i < n; i++) if (slots[i].name == slotName) return i; return -1; }, /** @return May be null. */ findSkin: function (skinName) { var skins = this.skins; for (var i = 0, n = skins.length; i < n; i++) if (skins[i].name == skinName) return skins[i]; return null; }, /** @return May be null. */ findAnimation: function (animationName) { var animations = this.animations; for (var i = 0, n = animations.length; i < n; i++) if (animations[i].name == animationName) return animations[i]; return null; } }; spine.Skeleton = function (skeletonData) { this.data = skeletonData; this.bones = []; for (var i = 0, n = skeletonData.bones.length; i < n; i++) { var boneData = skeletonData.bones[i]; var parent = !boneData.parent ? null : this.bones[skeletonData.bones.indexOf(boneData.parent)]; this.bones.push(new spine.Bone(boneData, parent)); } this.slots = []; this.drawOrder = []; for (i = 0, n = skeletonData.slots.length; i < n; i++) { var slotData = skeletonData.slots[i]; var bone = this.bones[skeletonData.bones.indexOf(slotData.boneData)]; var slot = new spine.Slot(slotData, this, bone); this.slots.push(slot); this.drawOrder.push(slot); } }; spine.Skeleton.prototype = { x: 0, y: 0, skin: null, r: 1, g: 1, b: 1, a: 1, time: 0, flipX: false, flipY: false, /** Updates the world transform for each bone. */ updateWorldTransform: function () { var flipX = this.flipX; var flipY = this.flipY; var bones = this.bones; for (var i = 0, n = bones.length; i < n; i++) bones[i].updateWorldTransform(flipX, flipY); }, /** Sets the bones and slots to their setup pose values. */ setToSetupPose: function () { this.setBonesToSetupPose(); this.setSlotsToSetupPose(); }, setBonesToSetupPose: function () { var bones = this.bones; for (var i = 0, n = bones.length; i < n; i++) bones[i].setToSetupPose(); }, setSlotsToSetupPose: function () { var slots = this.slots; for (var i = 0, n = slots.length; i < n; i++) slots[i].setToSetupPose(i); }, /** @return May return null. */ getRootBone: function () { return this.bones.length ? this.bones[0] : null; }, /** @return May be null. */ findBone: function (boneName) { var bones = this.bones; for (var i = 0, n = bones.length; i < n; i++) if (bones[i].data.name == boneName) return bones[i]; return null; }, /** @return -1 if the bone was not found. */ findBoneIndex: function (boneName) { var bones = this.bones; for (var i = 0, n = bones.length; i < n; i++) if (bones[i].data.name == boneName) return i; return -1; }, /** @return May be null. */ findSlot: function (slotName) { var slots = this.slots; for (var i = 0, n = slots.length; i < n; i++) if (slots[i].data.name == slotName) return slots[i]; return null; }, /** @return -1 if the bone was not found. */ findSlotIndex: function (slotName) { var slots = this.slots; for (var i = 0, n = slots.length; i < n; i++) if (slots[i].data.name == slotName) return i; return -1; }, setSkinByName: function (skinName) { var skin = this.data.findSkin(skinName); if (!skin) throw "Skin not found: " + skinName; this.setSkin(skin); }, /** Sets the skin used to look up attachments not found in the {@link SkeletonData#getDefaultSkin() default skin}. Attachments * from the new skin are attached if the corresponding attachment from the old skin was attached. * @param newSkin May be null. */ setSkin: function (newSkin) { if (this.skin && newSkin) newSkin._attachAll(this, this.skin); this.skin = newSkin; }, /** @return May be null. */ getAttachmentBySlotName: function (slotName, attachmentName) { return this.getAttachmentBySlotIndex(this.data.findSlotIndex(slotName), attachmentName); }, /** @return May be null. */ getAttachmentBySlotIndex: function (slotIndex, attachmentName) { if (this.skin) { var attachment = this.skin.getAttachment(slotIndex, attachmentName); if (attachment) return attachment; } if (this.data.defaultSkin) return this.data.defaultSkin.getAttachment(slotIndex, attachmentName); return null; }, /** @param attachmentName May be null. */ setAttachment: function (slotName, attachmentName) { var slots = this.slots; for (var i = 0, n = slots.size; i < n; i++) { var slot = slots[i]; if (slot.data.name == slotName) { var attachment = null; if (attachmentName) { attachment = this.getAttachment(i, attachmentName); if (attachment == null) throw "Attachment not found: " + attachmentName + ", for slot: " + slotName; } slot.setAttachment(attachment); return; } } throw "Slot not found: " + slotName; }, update: function (delta) { time += delta; } }; spine.AttachmentType = { region: 0 }; spine.RegionAttachment = function () { this.offset = []; this.offset.length = 8; this.uvs = []; this.uvs.length = 8; }; spine.RegionAttachment.prototype = { x: 0, y: 0, rotation: 0, scaleX: 1, scaleY: 1, width: 0, height: 0, rendererObject: null, regionOffsetX: 0, regionOffsetY: 0, regionWidth: 0, regionHeight: 0, regionOriginalWidth: 0, regionOriginalHeight: 0, setUVs: function (u, v, u2, v2, rotate) { var uvs = this.uvs; if (rotate) { uvs[2/*X2*/] = u; uvs[3/*Y2*/] = v2; uvs[4/*X3*/] = u; uvs[5/*Y3*/] = v; uvs[6/*X4*/] = u2; uvs[7/*Y4*/] = v; uvs[0/*X1*/] = u2; uvs[1/*Y1*/] = v2; } else { uvs[0/*X1*/] = u; uvs[1/*Y1*/] = v2; uvs[2/*X2*/] = u; uvs[3/*Y2*/] = v; uvs[4/*X3*/] = u2; uvs[5/*Y3*/] = v; uvs[6/*X4*/] = u2; uvs[7/*Y4*/] = v2; } }, updateOffset: function () { var regionScaleX = this.width / this.regionOriginalWidth * this.scaleX; var regionScaleY = this.height / this.regionOriginalHeight * this.scaleY; var localX = -this.width / 2 * this.scaleX + this.regionOffsetX * regionScaleX; var localY = -this.height / 2 * this.scaleY + this.regionOffsetY * regionScaleY; var localX2 = localX + this.regionWidth * regionScaleX; var localY2 = localY + this.regionHeight * regionScaleY; var radians = this.rotation * Math.PI / 180; var cos = Math.cos(radians); var sin = Math.sin(radians); var localXCos = localX * cos + this.x; var localXSin = localX * sin; var localYCos = localY * cos + this.y; var localYSin = localY * sin; var localX2Cos = localX2 * cos + this.x; var localX2Sin = localX2 * sin; var localY2Cos = localY2 * cos + this.y; var localY2Sin = localY2 * sin; var offset = this.offset; offset[0/*X1*/] = localXCos - localYSin; offset[1/*Y1*/] = localYCos + localXSin; offset[2/*X2*/] = localXCos - localY2Sin; offset[3/*Y2*/] = localY2Cos + localXSin; offset[4/*X3*/] = localX2Cos - localY2Sin; offset[5/*Y3*/] = localY2Cos + localX2Sin; offset[6/*X4*/] = localX2Cos - localYSin; offset[7/*Y4*/] = localYCos + localX2Sin; }, computeVertices: function (x, y, bone, vertices) { x += bone.worldX; y += bone.worldY; var m00 = bone.m00; var m01 = bone.m01; var m10 = bone.m10; var m11 = bone.m11; var offset = this.offset; vertices[0/*X1*/] = offset[0/*X1*/] * m00 + offset[1/*Y1*/] * m01 + x; vertices[1/*Y1*/] = offset[0/*X1*/] * m10 + offset[1/*Y1*/] * m11 + y; vertices[2/*X2*/] = offset[2/*X2*/] * m00 + offset[3/*Y2*/] * m01 + x; vertices[3/*Y2*/] = offset[2/*X2*/] * m10 + offset[3/*Y2*/] * m11 + y; vertices[4/*X3*/] = offset[4/*X3*/] * m00 + offset[5/*X3*/] * m01 + x; vertices[5/*X3*/] = offset[4/*X3*/] * m10 + offset[5/*X3*/] * m11 + y; vertices[6/*X4*/] = offset[6/*X4*/] * m00 + offset[7/*Y4*/] * m01 + x; vertices[7/*Y4*/] = offset[6/*X4*/] * m10 + offset[7/*Y4*/] * m11 + y; } } spine.AnimationStateData = function (skeletonData) { this.skeletonData = skeletonData; this.animationToMixTime = {}; }; spine.AnimationStateData.prototype = { defaultMix: 0, setMixByName: function (fromName, toName, duration) { var from = this.skeletonData.findAnimation(fromName); if (!from) throw "Animation not found: " + fromName; var to = this.skeletonData.findAnimation(toName); if (!to) throw "Animation not found: " + toName; this.setMix(from, to, duration); }, setMix: function (from, to, duration) { this.animationToMixTime[from.name + ":" + to.name] = duration; }, getMix: function (from, to) { var time = this.animationToMixTime[from.name + ":" + to.name]; return time ? time : this.defaultMix; } }; spine.AnimationState = function (stateData) { this.data = stateData; this.queue = []; }; spine.AnimationState.prototype = { current: null, previous: null, currentTime: 0, previousTime: 0, currentLoop: false, previousLoop: false, mixTime: 0, mixDuration: 0, update: function (delta) { this.currentTime += delta; this.previousTime += delta; this.mixTime += delta; if (this.queue.length > 0) { var entry = this.queue[0]; if (this.currentTime >= entry.delay) { this._setAnimation(entry.animation, entry.loop); this.queue.shift(); } } }, apply: function (skeleton) { if (!this.current) return; if (this.previous) { this.previous.apply(skeleton, this.previousTime, this.previousLoop); var alpha = this.mixTime / this.mixDuration; if (alpha >= 1) { alpha = 1; this.previous = null; } this.current.mix(skeleton, this.currentTime, this.currentLoop, alpha); } else this.current.apply(skeleton, this.currentTime, this.currentLoop); }, clearAnimation: function () { this.previous = null; this.current = null; this.queue.length = 0; }, _setAnimation: function (animation, loop) { this.previous = null; if (animation && this.current) { this.mixDuration = this.data.getMix(this.current, animation); if (this.mixDuration > 0) { this.mixTime = 0; this.previous = this.current; this.previousTime = this.currentTime; this.previousLoop = this.currentLoop; } } this.current = animation; this.currentLoop = loop; this.currentTime = 0; }, /** @see #setAnimation(Animation, Boolean) */ setAnimationByName: function (animationName, loop) { var animation = this.data.skeletonData.findAnimation(animationName); if (!animation) throw "Animation not found: " + animationName; this.setAnimation(animation, loop); }, /** Set the current animation. Any queued animations are cleared and the current animation time is set to 0. * @param animation May be null. */ setAnimation: function (animation, loop) { this.queue.length = 0; this._setAnimation(animation, loop); }, /** @see #addAnimation(Animation, Boolean, Number) */ addAnimationByName: function (animationName, loop, delay) { var animation = this.data.skeletonData.findAnimation(animationName); if (!animation) throw "Animation not found: " + animationName; this.addAnimation(animation, loop, delay); }, /** Adds an animation to be played delay seconds after the current or last queued animation. * @param delay May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. */ addAnimation: function (animation, loop, delay) { var entry = {}; entry.animation = animation; entry.loop = loop; if (!delay || delay <= 0) { var previousAnimation = this.queue.length ? this.queue[this.queue.length - 1].animation : this.current; if (previousAnimation != null) delay = previousAnimation.duration - this.data.getMix(previousAnimation, animation) + (delay || 0); else delay = 0; } entry.delay = delay; this.queue.push(entry); }, /** Returns true if no animation is set or if the current time is greater than the animation duration, regardless of looping. */ isComplete: function () { return !this.current || this.currentTime >= this.current.duration; } }; spine.SkeletonJson = function (attachmentLoader) { this.attachmentLoader = attachmentLoader; }; spine.SkeletonJson.prototype = { scale: 1, readSkeletonData: function (root) { /*jshint -W069*/ var skeletonData = new spine.SkeletonData(), boneData; // Bones. var bones = root["bones"]; for (var i = 0, n = bones.length; i < n; i++) { var boneMap = bones[i]; var parent = null; if (boneMap["parent"]) { parent = skeletonData.findBone(boneMap["parent"]); if (!parent) throw "Parent bone not found: " + boneMap["parent"]; } boneData = new spine.BoneData(boneMap["name"], parent); boneData.length = (boneMap["length"] || 0) * this.scale; boneData.x = (boneMap["x"] || 0) * this.scale; boneData.y = (boneMap["y"] || 0) * this.scale; boneData.rotation = (boneMap["rotation"] || 0); boneData.scaleX = boneMap["scaleX"] || 1; boneData.scaleY = boneMap["scaleY"] || 1; skeletonData.bones.push(boneData); } // Slots. var slots = root["slots"]; for (i = 0, n = slots.length; i < n; i++) { var slotMap = slots[i]; boneData = skeletonData.findBone(slotMap["bone"]); if (!boneData) throw "Slot bone not found: " + slotMap["bone"]; var slotData = new spine.SlotData(slotMap["name"], boneData); var color = slotMap["color"]; if (color) { slotData.r = spine.SkeletonJson.toColor(color, 0); slotData.g = spine.SkeletonJson.toColor(color, 1); slotData.b = spine.SkeletonJson.toColor(color, 2); slotData.a = spine.SkeletonJson.toColor(color, 3); } slotData.attachmentName = slotMap["attachment"]; skeletonData.slots.push(slotData); } // Skins. var skins = root["skins"]; for (var skinName in skins) { if (!skins.hasOwnProperty(skinName)) continue; var skinMap = skins[skinName]; var skin = new spine.Skin(skinName); for (var slotName in skinMap) { if (!skinMap.hasOwnProperty(slotName)) continue; var slotIndex = skeletonData.findSlotIndex(slotName); var slotEntry = skinMap[slotName]; for (var attachmentName in slotEntry) { if (!slotEntry.hasOwnProperty(attachmentName)) continue; var attachment = this.readAttachment(skin, attachmentName, slotEntry[attachmentName]); if (attachment != null) skin.addAttachment(slotIndex, attachmentName, attachment); } } skeletonData.skins.push(skin); if (skin.name == "default") skeletonData.defaultSkin = skin; } // Animations. var animations = root["animations"]; for (var animationName in animations) { if (!animations.hasOwnProperty(animationName)) continue; this.readAnimation(animationName, animations[animationName], skeletonData); } return skeletonData; }, readAttachment: function (skin, name, map) { /*jshint -W069*/ name = map["name"] || name; var type = spine.AttachmentType[map["type"] || "region"]; if (type == spine.AttachmentType.region) { var attachment = new spine.RegionAttachment(); attachment.x = (map["x"] || 0) * this.scale; attachment.y = (map["y"] || 0) * this.scale; attachment.scaleX = map["scaleX"] || 1; attachment.scaleY = map["scaleY"] || 1; attachment.rotation = map["rotation"] || 0; attachment.width = (map["width"] || 32) * this.scale; attachment.height = (map["height"] || 32) * this.scale; attachment.updateOffset(); attachment.rendererObject = {}; attachment.rendererObject.name = name; attachment.rendererObject.scale = {}; attachment.rendererObject.scale.x = attachment.scaleX; attachment.rendererObject.scale.y = attachment.scaleY; attachment.rendererObject.rotation = -attachment.rotation * Math.PI / 180; return attachment; } throw "Unknown attachment type: " + type; }, readAnimation: function (name, map, skeletonData) { /*jshint -W069*/ var timelines = []; var duration = 0; var frameIndex, timeline, timelineName, valueMap, values, i, n; var bones = map["bones"]; for (var boneName in bones) { if (!bones.hasOwnProperty(boneName)) continue; var boneIndex = skeletonData.findBoneIndex(boneName); if (boneIndex == -1) throw "Bone not found: " + boneName; var boneMap = bones[boneName]; for (timelineName in boneMap) { if (!boneMap.hasOwnProperty(timelineName)) continue; values = boneMap[timelineName]; if (timelineName == "rotate") { timeline = new spine.RotateTimeline(values.length); timeline.boneIndex = boneIndex; frameIndex = 0; for (i = 0, n = values.length; i < n; i++) { valueMap = values[i]; timeline.setFrame(frameIndex, valueMap["time"], valueMap["angle"]); spine.SkeletonJson.readCurve(timeline, frameIndex, valueMap); frameIndex++; } timelines.push(timeline); duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 2 - 2]); } else if (timelineName == "translate" || timelineName == "scale") { var timelineScale = 1; if (timelineName == "scale") timeline = new spine.ScaleTimeline(values.length); else { timeline = new spine.TranslateTimeline(values.length); timelineScale = this.scale; } timeline.boneIndex = boneIndex; frameIndex = 0; for (i = 0, n = values.length; i < n; i++) { valueMap = values[i]; var x = (valueMap["x"] || 0) * timelineScale; var y = (valueMap["y"] || 0) * timelineScale; timeline.setFrame(frameIndex, valueMap["time"], x, y); spine.SkeletonJson.readCurve(timeline, frameIndex, valueMap); frameIndex++; } timelines.push(timeline); duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 3 - 3]); } else throw "Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"; } } var slots = map["slots"]; for (var slotName in slots) { if (!slots.hasOwnProperty(slotName)) continue; var slotMap = slots[slotName]; var slotIndex = skeletonData.findSlotIndex(slotName); for (timelineName in slotMap) { if (!slotMap.hasOwnProperty(timelineName)) continue; values = slotMap[timelineName]; if (timelineName == "color") { timeline = new spine.ColorTimeline(values.length); timeline.slotIndex = slotIndex; frameIndex = 0; for (i = 0, n = values.length; i < n; i++) { valueMap = values[i]; var color = valueMap["color"]; var r = spine.SkeletonJson.toColor(color, 0); var g = spine.SkeletonJson.toColor(color, 1); var b = spine.SkeletonJson.toColor(color, 2); var a = spine.SkeletonJson.toColor(color, 3); timeline.setFrame(frameIndex, valueMap["time"], r, g, b, a); spine.SkeletonJson.readCurve(timeline, frameIndex, valueMap); frameIndex++; } timelines.push(timeline); duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 5 - 5]); } else if (timelineName == "attachment") { timeline = new spine.AttachmentTimeline(values.length); timeline.slotIndex = slotIndex; frameIndex = 0; for (i = 0, n = values.length; i < n; i++) { valueMap = values[i]; timeline.setFrame(frameIndex++, valueMap["time"], valueMap["name"]); } timelines.push(timeline); duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]); } else throw "Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"; } } skeletonData.animations.push(new spine.Animation(name, timelines, duration)); } }; spine.SkeletonJson.readCurve = function (timeline, frameIndex, valueMap) { /*jshint -W069*/ var curve = valueMap["curve"]; if (!curve) return; if (curve == "stepped") timeline.curves.setStepped(frameIndex); else if (curve instanceof Array) timeline.curves.setCurve(frameIndex, curve[0], curve[1], curve[2], curve[3]); }; spine.SkeletonJson.toColor = function (hexString, colorIndex) { if (hexString.length != 8) throw "Color hexidecimal length must be 8, recieved: " + hexString; return parseInt(hexString.substring(colorIndex * 2, 2), 16) / 255; }; spine.Atlas = function (atlasText, textureLoader) { this.textureLoader = textureLoader; this.pages = []; this.regions = []; var reader = new spine.AtlasReader(atlasText); var tuple = []; tuple.length = 4; var page = null; while (true) { var line = reader.readLine(); if (line == null) break; line = reader.trim(line); if (!line.length) page = null; else if (!page) { page = new spine.AtlasPage(); page.name = line; page.format = spine.Atlas.Format[reader.readValue()]; reader.readTuple(tuple); page.minFilter = spine.Atlas.TextureFilter[tuple[0]]; page.magFilter = spine.Atlas.TextureFilter[tuple[1]]; var direction = reader.readValue(); page.uWrap = spine.Atlas.TextureWrap.clampToEdge; page.vWrap = spine.Atlas.TextureWrap.clampToEdge; if (direction == "x") page.uWrap = spine.Atlas.TextureWrap.repeat; else if (direction == "y") page.vWrap = spine.Atlas.TextureWrap.repeat; else if (direction == "xy") page.uWrap = page.vWrap = spine.Atlas.TextureWrap.repeat; textureLoader.load(page, line); this.pages.push(page); } else { var region = new spine.AtlasRegion(); region.name = line; region.page = page; region.rotate = reader.readValue() == "true"; reader.readTuple(tuple); var x = parseInt(tuple[0], 10); var y = parseInt(tuple[1], 10); reader.readTuple(tuple); var width = parseInt(tuple[0], 10); var height = parseInt(tuple[1], 10); region.u = x / page.width; region.v = y / page.height; if (region.rotate) { region.u2 = (x + height) / page.width; region.v2 = (y + width) / page.height; } else { region.u2 = (x + width) / page.width; region.v2 = (y + height) / page.height; } region.x = x; region.y = y; region.width = Math.abs(width); region.height = Math.abs(height); if (reader.readTuple(tuple) == 4) { // split is optional region.splits = [parseInt(tuple[0], 10), parseInt(tuple[1], 10), parseInt(tuple[2], 10), parseInt(tuple[3], 10)]; if (reader.readTuple(tuple) == 4) { // pad is optional, but only present with splits region.pads = [parseInt(tuple[0], 10), parseInt(tuple[1], 10), parseInt(tuple[2], 10), parseInt(tuple[3], 10)]; reader.readTuple(tuple); } } region.originalWidth = parseInt(tuple[0], 10); region.originalHeight = parseInt(tuple[1], 10); reader.readTuple(tuple); region.offsetX = parseInt(tuple[0], 10); region.offsetY = parseInt(tuple[1], 10); region.index = parseInt(reader.readValue(), 10); this.regions.push(region); } } }; spine.Atlas.prototype = { findRegion: function (name) { var regions = this.regions; for (var i = 0, n = regions.length; i < n; i++) if (regions[i].name == name) return regions[i]; return null; }, dispose: function () { var pages = this.pages; for (var i = 0, n = pages.length; i < n; i++) this.textureLoader.unload(pages[i].rendererObject); }, updateUVs: function (page) { var regions = this.regions; for (var i = 0, n = regions.length; i < n; i++) { var region = regions[i]; if (region.page != page) continue; region.u = region.x / page.width; region.v = region.y / page.height; if (region.rotate) { region.u2 = (region.x + region.height) / page.width; region.v2 = (region.y + region.width) / page.height; } else { region.u2 = (region.x + region.width) / page.width; region.v2 = (region.y + region.height) / page.height; } } } }; spine.Atlas.Format = { alpha: 0, intensity: 1, luminanceAlpha: 2, rgb565: 3, rgba4444: 4, rgb888: 5, rgba8888: 6 }; spine.Atlas.TextureFilter = { nearest: 0, linear: 1, mipMap: 2, mipMapNearestNearest: 3, mipMapLinearNearest: 4, mipMapNearestLinear: 5, mipMapLinearLinear: 6 }; spine.Atlas.TextureWrap = { mirroredRepeat: 0, clampToEdge: 1, repeat: 2 }; spine.AtlasPage = function () {}; spine.AtlasPage.prototype = { name: null, format: null, minFilter: null, magFilter: null, uWrap: null, vWrap: null, rendererObject: null, width: 0, height: 0 }; spine.AtlasRegion = function () {}; spine.AtlasRegion.prototype = { page: null, name: null, x: 0, y: 0, width: 0, height: 0, u: 0, v: 0, u2: 0, v2: 0, offsetX: 0, offsetY: 0, originalWidth: 0, originalHeight: 0, index: 0, rotate: false, splits: null, pads: null, }; spine.AtlasReader = function (text) { this.lines = text.split(/\r\n|\r|\n/); }; spine.AtlasReader.prototype = { index: 0, trim: function (value) { return value.replace(/^\s+|\s+$/g, ""); }, readLine: function () { if (this.index >= this.lines.length) return null; return this.lines[this.index++]; }, readValue: function () { var line = this.readLine(); var colon = line.indexOf(":"); if (colon == -1) throw "Invalid line: " + line; return this.trim(line.substring(colon + 1)); }, /** Returns the number of tuple values read (2 or 4). */ readTuple: function (tuple) { var line = this.readLine(); var colon = line.indexOf(":"); if (colon == -1) throw "Invalid line: " + line; var i = 0, lastMatch= colon + 1; for (; i < 3; i++) { var comma = line.indexOf(",", lastMatch); if (comma == -1) { if (!i) throw "Invalid line: " + line; break; } tuple[i] = this.trim(line.substr(lastMatch, comma - lastMatch)); lastMatch = comma + 1; } tuple[i] = this.trim(line.substring(lastMatch)); return i + 1; } } spine.AtlasAttachmentLoader = function (atlas) { this.atlas = atlas; } spine.AtlasAttachmentLoader.prototype = { newAttachment: function (skin, type, name) { switch (type) { case spine.AttachmentType.region: var region = this.atlas.findRegion(name); if (!region) throw "Region not found in atlas: " + name + " (" + type + ")"; var attachment = new spine.RegionAttachment(name); attachment.rendererObject = region; attachment.setUVs(region.u, region.v, region.u2, region.v2, region.rotate); attachment.regionOffsetX = region.offsetX; attachment.regionOffsetY = region.offsetY; attachment.regionWidth = region.width; attachment.regionHeight = region.height; attachment.regionOriginalWidth = region.originalWidth; attachment.regionOriginalHeight = region.originalHeight; return attachment; } throw "Unknown attachment type: " + type; } } spine.Bone.yDown = true; PIXI.AnimCache = {}; /** * A class that enables the you to import and run your spine animations in pixi. * Spine animation data needs to be loaded using the PIXI.AssetLoader or PIXI.SpineLoader before it can be used by this class * See example 12 (http://www.goodboydigital.com/pixijs/examples/12/) to see a working example and check out the source * * @class Spine * @extends DisplayObjectContainer * @constructor * @param url {String} The url of the spine anim file to be used */ PIXI.Spine = function (url) { PIXI.DisplayObjectContainer.call(this); this.spineData = PIXI.AnimCache[url]; if (!this.spineData) { throw new Error("Spine data must be preloaded using PIXI.SpineLoader or PIXI.AssetLoader: " + url); } this.skeleton = new spine.Skeleton(this.spineData); this.skeleton.updateWorldTransform(); this.stateData = new spine.AnimationStateData(this.spineData); this.state = new spine.AnimationState(this.stateData); this.slotContainers = []; for (var i = 0, n = this.skeleton.drawOrder.length; i < n; i++) { var slot = this.skeleton.drawOrder[i]; var attachment = slot.attachment; var slotContainer = new PIXI.DisplayObjectContainer(); this.slotContainers.push(slotContainer); this.addChild(slotContainer); if (!(attachment instanceof spine.RegionAttachment)) { continue; } var spriteName = attachment.rendererObject.name; var sprite = this.createSprite(slot, attachment.rendererObject); slot.currentSprite = sprite; slot.currentSpriteName = spriteName; slotContainer.addChild(sprite); } }; PIXI.Spine.prototype = Object.create(PIXI.DisplayObjectContainer.prototype); PIXI.Spine.prototype.constructor = PIXI.Spine; /* * Updates the object transform for rendering * * @method updateTransform * @private */ PIXI.Spine.prototype.updateTransform = function () { this.lastTime = this.lastTime || Date.now(); var timeDelta = (Date.now() - this.lastTime) * 0.001; this.lastTime = Date.now(); this.state.update(timeDelta); this.state.apply(this.skeleton); this.skeleton.updateWorldTransform(); var drawOrder = this.skeleton.drawOrder; for (var i = 0, n = drawOrder.length; i < n; i++) { var slot = drawOrder[i]; var attachment = slot.attachment; var slotContainer = this.slotContainers[i]; if (!(attachment instanceof spine.RegionAttachment)) { slotContainer.visible = false; continue; } if (attachment.rendererObject) { if (!slot.currentSpriteName || slot.currentSpriteName != attachment.name) { var spriteName = attachment.rendererObject.name; if (slot.currentSprite !== undefined) { slot.currentSprite.visible = false; } slot.sprites = slot.sprites || {}; if (slot.sprites[spriteName] !== undefined) { slot.sprites[spriteName].visible = true; } else { var sprite = this.createSprite(slot, attachment.rendererObject); slotContainer.addChild(sprite); } slot.currentSprite = slot.sprites[spriteName]; slot.currentSpriteName = spriteName; } } slotContainer.visible = true; var bone = slot.bone; slotContainer.position.x = bone.worldX + attachment.x * bone.m00 + attachment.y * bone.m01; slotContainer.position.y = bone.worldY + attachment.x * bone.m10 + attachment.y * bone.m11; slotContainer.scale.x = bone.worldScaleX; slotContainer.scale.y = bone.worldScaleY; slotContainer.rotation = -(slot.bone.worldRotation * Math.PI / 180); } PIXI.DisplayObjectContainer.prototype.updateTransform.call(this); }; PIXI.Spine.prototype.createSprite = function (slot, descriptor) { var name = PIXI.TextureCache[descriptor.name] ? descriptor.name : descriptor.name + ".png"; var sprite = new PIXI.Sprite(PIXI.Texture.fromFrame(name)); sprite.scale = descriptor.scale; sprite.rotation = descriptor.rotation; sprite.anchor.x = sprite.anchor.y = 0.5; slot.sprites = slot.sprites || {}; slot.sprites[descriptor.name] = sprite; return sprite; }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ PIXI.BaseTextureCache = {}; PIXI.texturesToUpdate = []; PIXI.texturesToDestroy = []; 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} Should be one of the PIXI.scaleMode consts */ PIXI.BaseTexture = function(source, scaleMode) { PIXI.EventTarget.call( this ); /** * [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 PIXI.scaleModes * @default PIXI.scaleModes.LINEAR */ this.scaleMode = scaleMode || PIXI.scaleModes.DEFAULT; /** * [read-only] Describes if the base texture has loaded or not * * @property hasLoaded * @type Boolean * @readOnly */ this.hasLoaded = false; /** * The source that is loaded to create the texture * * @property source * @type Image */ this.source = source; if(!source)return; if(this.source.complete || this.source.getContext) { this.hasLoaded = true; this.width = this.source.width; this.height = this.source.height; PIXI.texturesToUpdate.push(this); } else { var scope = this; this.source.onload = function() { scope.hasLoaded = true; scope.width = scope.source.width; scope.height = scope.source.height; // add it to somewhere... PIXI.texturesToUpdate.push(scope); scope.dispatchEvent( { type: 'loaded', content: scope } ); }; } this.imageUrl = null; this._powerOf2 = false; //TODO will be used for futer pixi 1.5... this.id = PIXI.BaseTextureCacheIdGenerator++; // used for webGL this._glTextures = []; }; PIXI.BaseTexture.prototype.constructor = PIXI.BaseTexture; /** * Destroys this base texture * * @method destroy */ PIXI.BaseTexture.prototype.destroy = function() { if(this.imageUrl) { delete PIXI.BaseTextureCache[this.imageUrl]; this.imageUrl = null; this.source.src = null; } this.source = null; PIXI.texturesToDestroy.push(this); }; /** * 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; }; /** * Helper function that returns a base texture based on an 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} Should be one of the PIXI.scaleMode consts * @return BaseTexture */ PIXI.BaseTexture.fromImage = function(imageUrl, crossorigin, scaleMode) { var baseTexture = PIXI.BaseTextureCache[imageUrl]; crossorigin = !crossorigin; 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();//document.createElement('img'); if (crossorigin) { image.crossOrigin = ''; } image.src = imageUrl; baseTexture = new PIXI.BaseTexture(image, scaleMode); baseTexture.imageUrl = imageUrl; PIXI.BaseTextureCache[imageUrl] = baseTexture; } return baseTexture; }; PIXI.BaseTexture.fromCanvas = function(canvas, scaleMode) { if(!canvas._pixiId) { canvas._pixiId = 'canvas_' + PIXI.TextureCacheIdGenerator++; } 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 = {}; 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. To do this use 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 */ PIXI.Texture = function(baseTexture, frame) { PIXI.EventTarget.call( this ); if(!frame) { this.noFrame = true; frame = new PIXI.Rectangle(0,0,1,1); } if(baseTexture instanceof PIXI.Texture) baseTexture = baseTexture.baseTexture; /** * The base texture of 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 trim point * * @property trim * @type Rectangle */ this.trim = null; this.scope = this; if(baseTexture.hasLoaded) { if(this.noFrame)frame = new PIXI.Rectangle(0,0, baseTexture.width, baseTexture.height); this.setFrame(frame); } else { var scope = this; baseTexture.addEventListener('loaded', function(){ scope.onBaseTextureLoaded(); }); } }; PIXI.Texture.prototype.constructor = PIXI.Texture; /** * Called when the base texture is loaded * * @method onBaseTextureLoaded * @param event * @private */ PIXI.Texture.prototype.onBaseTextureLoaded = function() { var baseTexture = this.baseTexture; baseTexture.removeEventListener( 'loaded', this.onLoaded ); if(this.noFrame)this.frame = new PIXI.Rectangle(0,0, baseTexture.width, baseTexture.height); this.setFrame(this.frame); this.scope.dispatchEvent( { type: 'update', content: this } ); }; /** * 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(); }; /** * Specifies the rectangle region of the baseTexture * * @method setFrame * @param frame {Rectangle} The frame of the texture to set it to */ PIXI.Texture.prototype.setFrame = function(frame) { this.frame = frame; this.width = frame.width; this.height = frame.height; if(frame.x + frame.width > this.baseTexture.width || frame.y + frame.height > this.baseTexture.height) { throw new Error('Texture Error: frame does not fit inside the base Texture dimensions ' + this); } this.updateFrame = true; PIXI.Texture.frameUpdates.push(this); //this.dispatchEvent( { type: 'update', content: this } ); }; PIXI.Texture.prototype._updateWebGLuvs = function() { if(!this._uvs)this._uvs = new PIXI.TextureUvs(); var frame = this.frame; 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 returns a texture based on an 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 * @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 based on a 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 returns a texture based on a canvas element * If the canvas is not in the texture cache it will be created and loaded * * @static * @method fromCanvas * @param canvas {Canvas} The canvas element source of the texture * @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 textureCache. * * @static * @method addTextureToCache * @param texture {Texture} * @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 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]; PIXI.TextureCache[id] = null; return texture; }; // this is more for webGL.. it contains updated frames.. PIXI.Texture.frameUpdates = []; 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.y4 = 0; }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** A RenderTexture is a special texture that allows any pixi displayObject to be rendered to it. __Hint__: All DisplayObjects (exmpl. Sprites) that render on RenderTexture should be preloaded. Otherwise black rectangles will be drawn instead. RenderTexture takes snapshot of DisplayObject passed to render method. If DisplayObject is passed to render method, position and rotation of it will be 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); Sprite in this case will be rendered to 0,0 position. To render this sprite at center 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 */ PIXI.RenderTexture = function(width, height, renderer) { PIXI.EventTarget.call( this ); /** * 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 framing rectangle of the render texture * * @property frame * @type Rectangle */ this.frame = new PIXI.Rectangle(0, 0, this.width, this.height); /** * The base texture object that this texture uses * * @property baseTexture * @type BaseTexture */ this.baseTexture = new PIXI.BaseTexture(); this.baseTexture.width = this.width; this.baseTexture.height = this.height; this.baseTexture._glTextures = []; this.baseTexture.hasLoaded = true; // each render texture can only belong to one renderer at the moment if its webGL this.renderer = renderer || PIXI.defaultRenderer; if(this.renderer.type === PIXI.WEBGL_RENDERER) { var gl = this.renderer.gl; this.textureBuffer = new PIXI.FilterTexture(gl, this.width, this.height); this.baseTexture._glTextures[gl.id] = this.textureBuffer.texture; this.render = this.renderWebGL; this.projection = new PIXI.Point(this.width/2 , -this.height/2); } else { this.render = this.renderCanvas; this.textureBuffer = new PIXI.CanvasBuffer(this.width, this.height); this.baseTexture.source = this.textureBuffer.canvas; } PIXI.Texture.frameUpdates.push(this); }; PIXI.RenderTexture.prototype = Object.create(PIXI.Texture.prototype); PIXI.RenderTexture.prototype.constructor = PIXI.RenderTexture; PIXI.RenderTexture.prototype.resize = function(width, height) { this.width = width; this.height = height; this.frame.width = this.width; this.frame.height = this.height; if(this.renderer.type === PIXI.WEBGL_RENDERER) { this.projection.x = this.width / 2; this.projection.y = -this.height / 2; var gl = this.renderer.gl; gl.bindTexture(gl.TEXTURE_2D, this.baseTexture._glTextures[gl.id]); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.width, this.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); } else { this.textureBuffer.resize(this.width, this.height); } PIXI.Texture.frameUpdates.push(this); }; /** * This function will draw the display object to the texture. * * @method renderWebGL * @param displayObject {DisplayObject} The display object to render this texture on * @param clear {Boolean} If true the texture will be cleared before the displayObject is drawn * @private */ PIXI.RenderTexture.prototype.renderWebGL = function(displayObject, position, clear) { //TOOD replace position with matrix.. var gl = this.renderer.gl; gl.colorMask(true, true, true, true); gl.viewport(0, 0, this.width, this.height); gl.bindFramebuffer(gl.FRAMEBUFFER, this.textureBuffer.frameBuffer ); if(clear)this.textureBuffer.clear(); // THIS WILL MESS WITH HIT TESTING! var children = displayObject.children; //TODO -? create a new one??? dont think so! var originalWorldTransform = displayObject.worldTransform; displayObject.worldTransform = PIXI.RenderTexture.tempMatrix; // modify to flip... displayObject.worldTransform.d = -1; displayObject.worldTransform.ty = this.projection.y * -2; if(position) { displayObject.worldTransform.tx = position.x; displayObject.worldTransform.ty -= position.y; } for(var i=0,j=children.length; i} assetURLs an array of image/sprite sheet urls that you would like loaded * supported. Supported image formats include 'jpeg', 'jpg', 'png', 'gif'. Supported * sprite sheet data formats only include 'JSON' at this time. Supported bitmap font * data formats include 'xml' and 'fnt'. * @param crossorigin {Boolean} Whether requests should be treated as crossorigin */ PIXI.AssetLoader = function(assetURLs, crossorigin) { PIXI.EventTarget.call(this); /** * The array of asset URLs that are going to be loaded * * @property assetURLs * @type Array */ this.assetURLs = assetURLs; /** * Whether the requests should be treated as cross origin * * @property crossorigin * @type Boolean */ this.crossorigin = crossorigin; /** * Maps file extension to loader types * * @property loadersByType * @type Object */ this.loadersByType = { 'jpg': PIXI.ImageLoader, 'jpeg': PIXI.ImageLoader, 'png': PIXI.ImageLoader, 'gif': PIXI.ImageLoader, 'json': PIXI.JsonLoader, 'atlas': PIXI.AtlasLoader, 'anim': PIXI.SpineLoader, 'xml': PIXI.BitmapFontLoader, 'fnt': PIXI.BitmapFontLoader }; }; /** * Fired when an item has loaded * @event onProgress */ /** * Fired when all the assets have loaded * @event onComplete */ // constructor PIXI.AssetLoader.prototype.constructor = PIXI.AssetLoader; /** * Given a filename, returns its extension, wil * * @method _getDataType * @param str {String} the name of the asset */ PIXI.AssetLoader.prototype._getDataType = function(str) { var test = 'data:'; //starts with 'data:' var start = str.slice(0, test.length).toLowerCase(); if (start === test) { var data = str.slice(test.length); var sepIdx = data.indexOf(','); if (sepIdx === -1) //malformed data URI scheme return null; //e.g. 'image/gif;base64' => 'image/gif' var info = data.slice(0, sepIdx).split(';')[0]; //We might need to handle some special cases here... //standardize text/plain to 'txt' file extension if (!info || info.toLowerCase() === 'text/plain') return 'txt'; //User specified mime type, try splitting it by '/' return info.split('/').pop().toLowerCase(); } return null; }; /** * Starts loading the assets sequentially * * @method load */ PIXI.AssetLoader.prototype.load = function() { var scope = this; function onLoad(evt) { scope.onAssetLoaded(evt.loader); } this.loadCount = this.assetURLs.length; for (var i=0; i < this.assetURLs.length; i++) { var fileName = this.assetURLs[i]; //first see if we have a data URI scheme.. var fileType = this._getDataType(fileName); //if not, assume it's a file URI if (!fileType) fileType = fileName.split('?').shift().split('.').pop().toLowerCase(); var Constructor = this.loadersByType[fileType]; if(!Constructor) throw new Error(fileType + ' is an unsupported file type'); var loader = new Constructor(fileName, this.crossorigin); loader.addEventListener('loaded', onLoad); loader.load(); } }; /** * Invoked after each file is loaded * * @method onAssetLoaded * @private */ PIXI.AssetLoader.prototype.onAssetLoaded = function(loader) { this.loadCount--; this.dispatchEvent({ type: 'onProgress', content: this, loader: loader }); if (this.onProgress) this.onProgress(loader); if (!this.loadCount) { this.dispatchEvent({type: 'onComplete', content: this}); if(this.onComplete) this.onComplete(); } }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * The json file loader is used to load in JSON data and parse it * When loaded this class will dispatch a 'loaded' event * If loading fails this class will dispatch an 'error' event * * @class JsonLoader * @uses EventTarget * @constructor * @param url {String} The url of the JSON file * @param crossorigin {Boolean} Whether requests should be treated as crossorigin */ PIXI.JsonLoader = function (url, crossorigin) { PIXI.EventTarget.call(this); /** * The url of the bitmap font data * * @property url * @type String */ this.url = url; /** * Whether the requests should be treated as cross origin * * @property crossorigin * @type Boolean */ this.crossorigin = crossorigin; /** * [read-only] The base url of the bitmap font data * * @property baseUrl * @type String * @readOnly */ this.baseUrl = url.replace(/[^\/]*$/, ''); /** * [read-only] Whether the data has loaded yet * * @property loaded * @type Boolean * @readOnly */ this.loaded = false; }; // constructor PIXI.JsonLoader.prototype.constructor = PIXI.JsonLoader; /** * Loads the JSON data * * @method load */ PIXI.JsonLoader.prototype.load = function () { this.ajaxRequest = new PIXI.AjaxRequest(this.crossorigin); var scope = this; this.ajaxRequest.onreadystatechange = function () { scope.onJSONLoaded(); }; this.ajaxRequest.open('GET', this.url, true); if (this.ajaxRequest.overrideMimeType) this.ajaxRequest.overrideMimeType('application/json'); this.ajaxRequest.send(null); }; /** * Invoke when JSON file is loaded * * @method onJSONLoaded * @private */ PIXI.JsonLoader.prototype.onJSONLoaded = function () { if (this.ajaxRequest.readyState === 4) { if (this.ajaxRequest.status === 200 || window.location.protocol.indexOf('http') === -1) { this.json = JSON.parse(this.ajaxRequest.responseText); if(this.json.frames) { // sprite sheet var scope = this; var textureUrl = this.baseUrl + this.json.meta.image; var image = new PIXI.ImageLoader(textureUrl, this.crossorigin); var frameData = this.json.frames; this.texture = image.texture.baseTexture; image.addEventListener('loaded', function() { scope.onLoaded(); }); for (var i in frameData) { var rect = frameData[i].frame; if (rect) { PIXI.TextureCache[i] = new PIXI.Texture(this.texture, { x: rect.x, y: rect.y, width: rect.w, height: rect.h }); // check to see ifthe sprite ha been trimmed.. if (frameData[i].trimmed) { var texture = PIXI.TextureCache[i]; var actualSize = frameData[i].sourceSize; var realSize = frameData[i].spriteSourceSize; texture.trim = new PIXI.Rectangle(realSize.x, realSize.y, actualSize.w, actualSize.h); } } } image.load(); } else if(this.json.bones) { // spine animation var spineJsonParser = new spine.SkeletonJson(); var skeletonData = spineJsonParser.readSkeletonData(this.json); PIXI.AnimCache[this.url] = skeletonData; this.onLoaded(); } else { this.onLoaded(); } } else { this.onError(); } } }; /** * Invoke when json file loaded * * @method onLoaded * @private */ PIXI.JsonLoader.prototype.onLoaded = function () { this.loaded = true; this.dispatchEvent({ type: 'loaded', content: this }); }; /** * Invoke when error occured * * @method onError * @private */ PIXI.JsonLoader.prototype.onError = function () { this.dispatchEvent({ type: 'error', content: this }); }; /** * @author Martin Kelm http://mkelm.github.com */ /** * The atlas file loader is used to load in Atlas data and parse it * When loaded this class will dispatch a 'loaded' event * If loading fails this class will dispatch an 'error' event * @class AtlasLoader * @extends EventTarget * @constructor * @param {String} url the url of the JSON file * @param {Boolean} crossorigin */ PIXI.AtlasLoader = function (url, crossorigin) { PIXI.EventTarget.call(this); this.url = url; this.baseUrl = url.replace(/[^\/]*$/, ''); this.crossorigin = crossorigin; this.loaded = false; }; // constructor PIXI.AtlasLoader.constructor = PIXI.AtlasLoader; /** * Starts loading the JSON file * * @method load */ PIXI.AtlasLoader.prototype.load = function () { this.ajaxRequest = new PIXI.AjaxRequest(); this.ajaxRequest.onreadystatechange = this.onAtlasLoaded.bind(this); this.ajaxRequest.open('GET', this.url, true); if (this.ajaxRequest.overrideMimeType) this.ajaxRequest.overrideMimeType('application/json'); this.ajaxRequest.send(null); }; /** * Invoke when JSON file is loaded * @method onAtlasLoaded * @private */ PIXI.AtlasLoader.prototype.onAtlasLoaded = function () { if (this.ajaxRequest.readyState === 4) { if (this.ajaxRequest.status === 200 || window.location.href.indexOf('http') === -1) { this.atlas = { meta : { image : [] }, frames : [] }; var result = this.ajaxRequest.responseText.split(/\r?\n/); var lineCount = -3; var currentImageId = 0; var currentFrame = null; var nameInNextLine = false; var i = 0, j = 0, selfOnLoaded = this.onLoaded.bind(this); // parser without rotation support yet! for (i = 0; i < result.length; i++) { result[i] = result[i].replace(/^\s+|\s+$/g, ''); if (result[i] === '') { nameInNextLine = i+1; } if (result[i].length > 0) { if (nameInNextLine === i) { this.atlas.meta.image.push(result[i]); currentImageId = this.atlas.meta.image.length - 1; this.atlas.frames.push({}); lineCount = -3; } else if (lineCount > 0) { if (lineCount % 7 === 1) { // frame name if (currentFrame != null) { //jshint ignore:line this.atlas.frames[currentImageId][currentFrame.name] = currentFrame; } currentFrame = { name: result[i], frame : {} }; } else { var text = result[i].split(' '); if (lineCount % 7 === 3) { // position currentFrame.frame.x = Number(text[1].replace(',', '')); currentFrame.frame.y = Number(text[2]); } else if (lineCount % 7 === 4) { // size currentFrame.frame.w = Number(text[1].replace(',', '')); currentFrame.frame.h = Number(text[2]); } else if (lineCount % 7 === 5) { // real size var realSize = { x : 0, y : 0, w : Number(text[1].replace(',', '')), h : Number(text[2]) }; if (realSize.w > currentFrame.frame.w || realSize.h > currentFrame.frame.h) { currentFrame.trimmed = true; currentFrame.realSize = realSize; } else { currentFrame.trimmed = false; } } } } lineCount++; } } if (currentFrame != null) { //jshint ignore:line this.atlas.frames[currentImageId][currentFrame.name] = currentFrame; } if (this.atlas.meta.image.length > 0) { this.images = []; for (j = 0; j < this.atlas.meta.image.length; j++) { // sprite sheet var textureUrl = this.baseUrl + this.atlas.meta.image[j]; var frameData = this.atlas.frames[j]; this.images.push(new PIXI.ImageLoader(textureUrl, this.crossorigin)); for (i in frameData) { var rect = frameData[i].frame; if (rect) { PIXI.TextureCache[i] = new PIXI.Texture(this.images[j].texture.baseTexture, { x: rect.x, y: rect.y, width: rect.w, height: rect.h }); if (frameData[i].trimmed) { PIXI.TextureCache[i].realSize = frameData[i].realSize; // trim in pixi not supported yet, todo update trim properties if it is done ... PIXI.TextureCache[i].trim.x = 0; PIXI.TextureCache[i].trim.y = 0; } } } } this.currentImageId = 0; for (j = 0; j < this.images.length; j++) { this.images[j].addEventListener('loaded', selfOnLoaded); } this.images[this.currentImageId].load(); } else { this.onLoaded(); } } else { this.onError(); } } }; /** * Invoke when json file has loaded * @method onLoaded * @private */ PIXI.AtlasLoader.prototype.onLoaded = function () { if (this.images.length - 1 > this.currentImageId) { this.currentImageId++; this.images[this.currentImageId].load(); } else { this.loaded = true; this.dispatchEvent({ type: 'loaded', content: this }); } }; /** * Invoke when error occured * @method onError * @private */ PIXI.AtlasLoader.prototype.onError = function () { this.dispatchEvent({ type: 'error', content: this }); }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * The sprite sheet loader is used to load in JSON sprite sheet data * To generate the data you can use http://www.codeandweb.com/texturepacker and publish in the 'JSON' format * There is a free version so thats nice, although the paid version is great value for money. * It is highly recommended to use Sprite sheets (also know as a 'texture atlas') as it means sprites can be batched and drawn together for highly increased rendering speed. * Once the data has been loaded the frames are stored in the PIXI texture cache and can be accessed though PIXI.Texture.fromFrameId() and PIXI.Sprite.fromFrameId() * This loader will load the image file that the Spritesheet points to as well as the data. * When loaded this class will dispatch a 'loaded' event * * @class SpriteSheetLoader * @uses EventTarget * @constructor * @param url {String} The url of the sprite sheet JSON file * @param crossorigin {Boolean} Whether requests should be treated as crossorigin */ PIXI.SpriteSheetLoader = function (url, crossorigin) { /* * i use texture packer to load the assets.. * http://www.codeandweb.com/texturepacker * make sure to set the format as 'JSON' */ PIXI.EventTarget.call(this); /** * The url of the bitmap font data * * @property url * @type String */ this.url = url; /** * Whether the requests should be treated as cross origin * * @property crossorigin * @type Boolean */ this.crossorigin = crossorigin; /** * [read-only] The base url of the bitmap font data * * @property baseUrl * @type String * @readOnly */ this.baseUrl = url.replace(/[^\/]*$/, ''); /** * The texture being loaded * * @property texture * @type Texture */ this.texture = null; /** * The frames of the sprite sheet * * @property frames * @type Object */ this.frames = {}; }; // constructor PIXI.SpriteSheetLoader.prototype.constructor = PIXI.SpriteSheetLoader; /** * This will begin loading the JSON file * * @method load */ PIXI.SpriteSheetLoader.prototype.load = function () { var scope = this; var jsonLoader = new PIXI.JsonLoader(this.url, this.crossorigin); jsonLoader.addEventListener('loaded', function (event) { scope.json = event.content.json; scope.onLoaded(); }); jsonLoader.load(); }; /** * Invoke when all files are loaded (json and texture) * * @method onLoaded * @private */ PIXI.SpriteSheetLoader.prototype.onLoaded = function () { this.dispatchEvent({ type: 'loaded', content: this }); }; /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ /** * The image loader class is responsible for loading images file formats ('jpeg', 'jpg', 'png' and 'gif') * Once the image has been loaded it is stored in the PIXI texture cache and can be accessed though PIXI.Texture.fromFrameId() and PIXI.Sprite.fromFrameId() * When loaded this class will dispatch a 'loaded' event * * @class ImageLoader * @uses EventTarget * @constructor * @param url {String} The url of the image * @param crossorigin {Boolean} Whether requests should be treated as crossorigin */ PIXI.ImageLoader = function(url, crossorigin) { PIXI.EventTarget.call(this); /** * The texture being loaded * * @property texture * @type Texture */ this.texture = PIXI.Texture.fromImage(url, crossorigin); /** * if the image is loaded with loadFramedSpriteSheet * frames will contain the sprite sheet frames * */ this.frames = []; }; // constructor PIXI.ImageLoader.prototype.constructor = PIXI.ImageLoader; /** * Loads image or takes it from cache * * @method load */ PIXI.ImageLoader.prototype.load = function() { if(!this.texture.baseTexture.hasLoaded) { var scope = this; this.texture.baseTexture.addEventListener('loaded', function() { scope.onLoaded(); }); } else { this.onLoaded(); } }; /** * Invoked when image file is loaded or it is already cached and ready to use * * @method onLoaded * @private */ PIXI.ImageLoader.prototype.onLoaded = function() { this.dispatchEvent({type: 'loaded', content: this}); }; /** * Loads image and split it to uniform sized frames * * * @method loadFramedSpriteSheet * @param frameWidth {Number} width of each frame * @param frameHeight {Number} height of each frame * @param textureName {String} if given, the frames will be cached in - format */ PIXI.ImageLoader.prototype.loadFramedSpriteSheet = function(frameWidth, frameHeight, textureName) { this.frames = []; var cols = Math.floor(this.texture.width / frameWidth); var rows = Math.floor(this.texture.height / frameHeight); var i=0; for (var y=0; y