From 53dd29ce4491c0ad8ebb38ac9611922604a3e815 Mon Sep 17 00:00:00 2001 From: Dan Delany Date: Sun, 8 Jun 2014 15:20:02 -0400 Subject: [PATCH 001/110] change init root to window --- src/intro.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/intro.js b/src/intro.js index dd7a77b9..3b9ac00e 100644 --- a/src/intro.js +++ b/src/intro.js @@ -9,7 +9,7 @@ // Browser globals (root is window) root.Physics = factory.call(root); } -}(this, function () { +}(window, function () { 'use strict'; From d96ecd57dcf75f5f75cc69d653df924560efdc5d Mon Sep 17 00:00:00 2001 From: Dan Delany Date: Sun, 8 Jun 2014 15:25:32 -0400 Subject: [PATCH 002/110] safer window context fix --- src/intro.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/intro.js b/src/intro.js index 3b9ac00e..8d993a37 100644 --- a/src/intro.js +++ b/src/intro.js @@ -9,7 +9,7 @@ // Browser globals (root is window) root.Physics = factory.call(root); } -}(window, function () { +}(typeof window !== 'undefined' ? window : this, function () { 'use strict'; From 6d2d92f89e2341506bf6b06a87208df18e2da772 Mon Sep 17 00:00:00 2001 From: Eric Dobbertin Date: Wed, 10 Sep 2014 13:51:44 -0500 Subject: [PATCH 003/110] support multi-touch --- src/behaviors/interactive.js | 235 +++++++++++++++++++++++------------ 1 file changed, 158 insertions(+), 77 deletions(-) diff --git a/src/behaviors/interactive.js b/src/behaviors/interactive.js index 312bd356..8932a7df 100644 --- a/src/behaviors/interactive.js +++ b/src/behaviors/interactive.js @@ -5,7 +5,7 @@ * * User interaction helper. * - * Used to get mouse/touch events and add a mouse grab interaction. + * Used to get mouse/touch events and add grab interactions. * * Additional options include: * - el: The element of the renderer. What you input as the `el` for the renderer. @@ -26,17 +26,21 @@ * data.x; // the x coord * data.y; // the y coord * }); + * // when a mouse or pointer moves * world.on('interact:move', function( data ){ * data.x; // the x coord * data.y; // the y coord - * data.body; // the body that was grabbed (if applicable) + * data.body; // the grabbed body that was moved (if applicable) * }); * // when the viewport is released (mouseup, touchend) * world.on('interact:release', function( data ){ * data.x; // the x coord * data.y; // the y coord + * data.body; // the body that was grabbed (if applicable) * }); * ``` + * + * The behavior also sets body.isGrabbed = true for any grabbed bodies while they are grabbed. **/ Physics.behavior('interactive', function( parent ){ @@ -69,18 +73,6 @@ Physics.behavior('interactive', function( parent ){ return { left: curleft, top: curtop }; } - ,getCoords = function( e ){ - var offset = getElementOffset( e.target ) - ,obj = ( e.changedTouches && e.changedTouches[0] ) || e - ,x = obj.pageX - offset.left - ,y = obj.pageY - offset.top - ; - - return { - x: x - ,y: y - }; - } ; return { @@ -98,9 +90,10 @@ Physics.behavior('interactive', function( parent ){ this.options( options ); // vars - this.mousePos = new Physics.vector(); - this.mousePosOld = new Physics.vector(); - this.offset = new Physics.vector(); + this.bodies = {}; + this.contactPoints = {}; + this.contactPointsOld = {}; + this.offsets = {}; this.el = typeof this.options.el === 'string' ? document.getElementById(this.options.el) : this.options.el; @@ -109,93 +102,180 @@ Physics.behavior('interactive', function( parent ){ } // init events + // when there are multiple touchdowns, grab is usually called separately for each, + // but we loop through e.changedTouches just in case var grab = function grab( e ){ - var pos = getCoords( e ) + var pos ,body + ,touchId + ,touch + ,offset ; - time = Physics.util.ticker.now(); - if ( self._world ){ - body = self._world.findOne({ $at: new Physics.vector( pos.x, pos.y ) }); - if ( body ){ - // we're trying to grab a body - - // fix the body in place - prevTreatment = body.treatment; - body.treatment = 'kinematic'; - body.state.vel.zero(); - body.state.angular.vel = 0; - // remember the currently grabbed body - self.body = body; - // remember the mouse offset - self.mousePos.clone( pos ); - self.mousePosOld.clone( pos ); - self.offset.clone( pos ).vsub( body.state.pos ); - - pos.body = body; - self._world.emit('interact:grab', pos); - - } else { + // Adjust for PointerEvent and older browsers + if (!e.changedTouches) { + e.changedTouches = []; + e.changedTouches.push(e); + } - self._world.emit('interact:poke', pos); + for (var touchIndex = 0; touchIndex < e.changedTouches.length; touchIndex++) { + offset = getElementOffset( e.target ); + touch = e.changedTouches[touchIndex]; + pos = {x: touch.pageX - offset.left, y: touch.pageY - offset.top}; + touchId = touch.identifier || touch.pointerId || "mouse"; + body = self._world.findOne({ $at: new Physics.vector( pos.x, pos.y ) }); + + if ( body ){ + // we're trying to grab a body + + // fix the body in place + prevTreatment = body.treatment; + body.treatment = 'kinematic'; + body.state.vel.zero(); + body.state.angular.vel = 0; + body.isGrabbed = true; + // remember the currently grabbed bodies + self.bodies[touchId] = body; + // remember the click/touch offset + self.contactPoints[touchId] = self.contactPoints[touchId] || new Physics.vector(); + self.contactPoints[touchId].clone( pos ); + + self.offsets[touchId] = self.offsets[touchId] || new Physics.vector(); + self.offsets[touchId].clone( pos ).vsub( body.state.pos ); + // init contactPointsOld here, too, so we don't have to do it in "move" + self.contactPointsOld[touchId] = self.contactPointsOld[touchId] || new Physics.vector(); + + pos.body = body; + self._world.emit('interact:grab', pos); + + } else { + + self._world.emit('interact:poke', pos); + } } } }; + // when there are multiple touchdowns, move is called once + // and e.changedTouches will have one or more touches in it var move = Physics.util.throttle(function move( e ){ - var pos = getCoords( e ) + var pos ,state + ,body + ,touchId + ,touch + ,offset ; - if ( self.body ){ - time = Physics.util.ticker.now(); + if ( self._world ){ - self.mousePosOld.clone( self.mousePos ); - // get new mouse position - self.mousePos.set(pos.x, pos.y); + // Adjust for PointerEvent and older browsers + if (!e.changedTouches) { + e.changedTouches = []; + e.changedTouches.push(e); + } - pos.body = self.body; + for (var touchIndex = 0; touchIndex < e.changedTouches.length; touchIndex++) { + offset = getElementOffset( e.target ); + touch = e.changedTouches[touchIndex]; + pos = {x: touch.pageX - offset.left, y: touch.pageY - offset.top}; + touchId = touch.identifier || touch.pointerId || "mouse"; + body = self.bodies[touchId]; + + if ( body ){ + time = Physics.util.ticker.now(); + + // set old mouse position + self.contactPointsOld[touchId].clone( self.contactPoints[touchId] ); + // get new mouse position + self.contactPoints[touchId].set(pos.x, pos.y); + + pos.body = body; + } + + self._world.emit('interact:move', pos); + } } - self._world.emit('interact:move', pos); - }, self.options.moveThrottle); + // when there are multiple touchups, release is called once + // and e.changedTouches will have one or more touches in it var release = function release( e ){ - var pos = getCoords( e ) + var pos ,body + ,touchId + ,touch + ,offset ,dt = Math.max(Physics.util.ticker.now() - time, self.options.moveThrottle) ; - // get new mouse position - self.mousePos.set(pos.x, pos.y); - - // release the body - if (self.body){ - self.body.treatment = prevTreatment; - // calculate the release velocity - self.body.state.vel.clone( self.mousePos ).vsub( self.mousePosOld ).mult( 1 / dt ); - // make sure it's not too big - self.body.state.vel.clamp( self.options.minVel, self.options.maxVel ); - self.body = false; - } - if ( self._world ){ - self._world.emit('interact:release', pos); + // Adjust for PointerEvent and older browsers + if (!e.changedTouches) { + e.changedTouches = []; + e.changedTouches.push(e); + } + + for (var touchIndex = 0; touchIndex < e.changedTouches.length; touchIndex++) { + offset = getElementOffset( e.target ); + touch = e.changedTouches[touchIndex]; + pos = {x: touch.pageX - offset.left, y: touch.pageY - offset.top}; + touchId = touch.identifier || touch.pointerId || "mouse"; + body = self.bodies[touchId]; + + // release the body + if (body){ + // get new mouse position + self.contactPoints[touchId].set(pos.x, pos.y); + + body.treatment = prevTreatment; + // calculate the release velocity + body.state.vel.clone( self.contactPoints[touchId] ).vsub( self.contactPointsOld[touchId] ).mult( 1 / dt ); + // make sure it's not too big + body.state.vel.clamp( self.options.minVel, self.options.maxVel ); + + body.isGrabbed = false; + pos.body = body; + } + + // emit before we delete the vars in case + // the listeners need the body + self._world.emit('interact:release', pos); + + // remove vars + delete self.contactPoints[touchId]; + delete self.contactPointsOld[touchId]; + delete self.offsets[touchId]; + delete self.bodies[touchId]; + delete body.isGrabbed; + } + } }; - this.el.addEventListener('mousedown', grab); - this.el.addEventListener('touchstart', grab); + if (window.PointerEvent) { - this.el.addEventListener('mousemove', move); - this.el.addEventListener('touchmove', move); + this.el.addEventListener('pointerdown', grab); + this.el.addEventListener('pointermove', move); + this.el.addEventListener('pointerup', release); + + } else { + + this.el.addEventListener('mousedown', grab); + this.el.addEventListener('touchstart', grab); + + this.el.addEventListener('mousemove', move); + this.el.addEventListener('touchmove', move); + + this.el.addEventListener('mouseup', release); + this.el.addEventListener('touchend', release); + + } - this.el.addEventListener('mouseup', release); - this.el.addEventListener('touchend', release); }, // extended @@ -218,14 +298,15 @@ Physics.behavior('interactive', function( parent ){ var self = this ,state ,dt = Math.max(data.dt, self.options.moveThrottle) + ,body ; - if ( self.body ){ - - // if we have a body, we need to move it the the new mouse position. - // we'll do this by adjusting the velocity so it gets there at the next step - state = self.body.state; - state.vel.clone( self.mousePos ).vsub( self.offset ).vsub( state.pos ).mult( 1 / dt ); + // if we have one or more bodies grabbed, we need to move them to the new mouse/finger positions. + // we'll do this by adjusting the velocity so they get there at the next step + for (var touchId in self.bodies) { + body = self.bodies[touchId]; + state = body.state; + state.vel.clone( self.contactPoints[touchId] ).vsub( self.offsets[touchId] ).vsub( state.pos ).mult( 1 / dt ); } } }; From 6075152cfe9fe97e6808f3a9f5de8f92b333e85c Mon Sep 17 00:00:00 2001 From: Eric Dobbertin Date: Wed, 10 Sep 2014 14:31:47 -0500 Subject: [PATCH 004/110] check for body first --- src/behaviors/interactive.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/behaviors/interactive.js b/src/behaviors/interactive.js index 8932a7df..4312a564 100644 --- a/src/behaviors/interactive.js +++ b/src/behaviors/interactive.js @@ -251,7 +251,9 @@ Physics.behavior('interactive', function( parent ){ delete self.contactPointsOld[touchId]; delete self.offsets[touchId]; delete self.bodies[touchId]; - delete body.isGrabbed; + if (body) { + delete body.isGrabbed; + } } } From 9103f4f9829701a209e2880313e4218d45048bd7 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Mon, 13 Oct 2014 15:15:04 -0500 Subject: [PATCH 005/110] added integrator testing sandbox --- lib/pixi.js | 16181 +++++++++++++++++++++++++++++++++ test/integrator-sandbox.html | 171 + 2 files changed, 16352 insertions(+) create mode 100644 lib/pixi.js create mode 100644 test/integrator-sandbox.html diff --git a/lib/pixi.js b/lib/pixi.js new file mode 100644 index 00000000..ed543db6 --- /dev/null +++ b/lib/pixi.js @@ -0,0 +1,16181 @@ +/** + * @license + * pixi.js - v1.6.0 + * Copyright (c) 2012-2014, Mat Groves + * http://goodboydigital.com/ + * + * Compiled: 2014-07-18 + * + * 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.6.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 +}; + +// used to create uids for various pixi objects.. +PIXI._UID = 0; + +if(typeof(Float32Array) != 'undefined') +{ + PIXI.Float32Array = Float32Array; + PIXI.Uint16Array = Uint16Array; +} +else +{ + PIXI.Float32Array = Array; + PIXI.Uint16Array = Array; +} + +// 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; + + +PIXI.dontSayHello = false; + +PIXI.sayHello = function (type) +{ + if(PIXI.dontSayHello)return; + + if ( navigator.userAgent.toLowerCase().indexOf('chrome') > -1 ) + { + var args = [ + '%c %c %c Pixi.js ' + PIXI.VERSION + ' - ' + type + ' %c ' + ' %c ' + ' http://www.pixijs.com/ %c %c ♥%c♥%c♥ ', + 'background: #ff66a5', + 'background: #ff66a5', + 'color: #ff66a5; background: #030307;', + 'background: #ff66a5', + 'background: #ffc3dc', + 'background: #ff66a5', + 'color: #ff2424; background: #fff', + 'color: #ff2424; background: #fff', + 'color: #ff2424; background: #fff' + ]; + + + + console.log.apply(console, args); + } + else if (window['console']) + { + console.log('Pixi.js ' + PIXI.VERSION + ' - http://www.pixijs.com/'); + } + + PIXI.dontSayHello = true; +}; + +/** + * @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); +}; + +/** + * Sets the point to a new x and y position. + * If y is ommited, both x and y will be set to x. + * + * @method set + * @param [x=0] {Number} position of the point on the x axis + * @param [y=0] {Number} position of the point on the y axis + */ +PIXI.Point.prototype.set = function(x, y) +{ + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; +}; + +// constructor +PIXI.Point.prototype.constructor = PIXI.Point; +/** + * @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 center of this circle + * @param y {Number} The Y coordinate of the center 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); +}; + +/** +* Returns the framing rectangle of the circle as a PIXI.Rectangle object +* +* @method getBounds +* @return {Rectangle} the framing rectangle +*/ +PIXI.Circle.prototype.getBounds = function() +{ + return new PIXI.Rectangle(this.x - this.radius, this.y - this.radius, this.width, this.height); +}; + +// 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 center of the ellipse + * @param y {Number} The Y coordinate of the center of the ellipse + * @param width {Number} The half width of this ellipse + * @param height {Number} The half 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.width, this.y - this.height, this.width, this.height); +}; + +// constructor +PIXI.Ellipse.prototype.constructor = PIXI.Ellipse; + +/** + * @author Mat Groves http://matgroves.com/ @Doormat23 + */ + +/** + * The Matrix class is now an object, which makes it a lot faster, + * here is a representation of it : + * | a | b | tx| + * | c | d | ty| + * | 0 | 0 | 1 | + * + * @class Matrix + * @constructor + */ +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} 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) + { + array[0] = this.a; + array[1] = this.c; + array[2] = 0; + array[3] = this.b; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.b; + array[2] = this.tx; + array[3] = this.c; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; +}; + +PIXI.identityMatrix = new PIXI.Matrix(); + +PIXI.determineMatrixArrayType = function() { + return (typeof Float32Array !== 'undefined') ? Float32Array : Array; +}; + +/** + * 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 + * + * @class Matrix2 + * @constructor + */ +PIXI.Matrix2 = PIXI.determineMatrixArrayType(); + +/** + * @author Mat Groves http://matgroves.com/ @Doormat23 + */ + +/** + * The base class for all objects that are rendered on the screen. + * This is an abstract class and should not be used on its own rather it should be extended. + * + * @class DisplayObject + * @constructor + */ +PIXI.DisplayObject = function() +{ + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @property position + * @type Point + */ + this.position = new PIXI.Point(); + + /** + * 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 like the hitArea this is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * @property filterArea + * @type Rectangle + */ + this.filterArea = null;//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; + + this._cacheAsBitmap = false; + this._cacheIsDirty = false; + + + /* + * 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; + } +}); + +/** + * Set weather or not a the display objects is cached as a bitmap. + * This basically takes a snap shot of the display object as it is at that moment. It can provide a performance benefit for complex static displayObjects + * To remove filters simply set this property to 'null' + * @property cacheAsBitmap + * @type Boolean + */ +Object.defineProperty(PIXI.DisplayObject.prototype, 'cacheAsBitmap', { + get: function() { + return this._cacheAsBitmap; + }, + set: function(value) { + + if(this._cacheAsBitmap === value)return; + + if(value) + { + //this._cacheIsDirty = true; + this._generateCachedSprite(); + } + else + { + this._destroyCachedSprite(); + } + + this._cacheAsBitmap = 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() +{ + 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; +}; + +PIXI.DisplayObject.prototype.generateTexture = function(renderer) +{ + var bounds = this.getLocalBounds(); + + var renderTexture = new PIXI.RenderTexture(bounds.width | 0, bounds.height | 0, renderer); + renderTexture.render(this, new PIXI.Point(-bounds.x, -bounds.y) ); + + return renderTexture; +}; + +PIXI.DisplayObject.prototype.updateCache = function() +{ + this._generateCachedSprite(); +}; + +PIXI.DisplayObject.prototype._renderCachedSprite = function(renderSession) +{ + this._cachedSprite.worldAlpha = this.worldAlpha; + + if(renderSession.gl) + { + PIXI.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + } + else + { + PIXI.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); + } +}; + +PIXI.DisplayObject.prototype._generateCachedSprite = function()//renderSession) +{ + this._cacheAsBitmap = false; + var bounds = this.getLocalBounds(); + + if(!this._cachedSprite) + { + var renderTexture = new PIXI.RenderTexture(bounds.width | 0, bounds.height | 0);//, renderSession.renderer); + + this._cachedSprite = new PIXI.Sprite(renderTexture); + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.texture.resize(bounds.width | 0, bounds.height | 0); + } + + //REMOVE filter! + var tempFilters = this._filters; + this._filters = null; + + this._cachedSprite.filters = tempFilters; + this._cachedSprite.texture.render(this, new PIXI.Point(-bounds.x, -bounds.y) ); + + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + this._filters = tempFilters; + + this._cacheAsBitmap = true; +}; + +/** +* Renders the object using the WebGL renderer +* +* @method _renderWebGL +* @param renderSession {RenderSession} +* @private +*/ +PIXI.DisplayObject.prototype._destroyCachedSprite = function() +{ + if(!this._cachedSprite)return; + + this._cachedSprite.texture.destroy(true); + // console.log("DESTROY") + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + + +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) { + + var width = this.getLocalBounds().width; + + if(width !== 0) + { + this.scale.x = value / ( width/this.scale.x ); + } + else + { + this.scale.x = 1; + } + + + this._width = value; + } +}); + + +/** + * The height of the displayObjectContainer, setting this will actually modify the scale to achieve the value set + * + * @property height + * @type Number + */ + +Object.defineProperty(PIXI.DisplayObjectContainer.prototype, 'height', { + get: function() { + return this.scale.y * this.getLocalBounds().height; + }, + set: function(value) { + + var height = this.getLocalBounds().height; + + if(height !== 0) + { + this.scale.y = value / ( height/this.scale.y ); + } + else + { + this.scale.y = 1; + } + + this._height = value; + } +}); + + +/** + * Adds a child to the container. + * + * @method addChild + * @param child {DisplayObject} The DisplayObject to add to the container + */ +PIXI.DisplayObjectContainer.prototype.addChild = function(child) +{ + return this.addChildAt(child, this.children.length); +}; + +/** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @method addChildAt + * @param child {DisplayObject} The child to add + * @param index {Number} The index to place the child in + */ +PIXI.DisplayObjectContainer.prototype.addChildAt = function(child, index) +{ + if(index >= 0 && index <= this.children.length) + { + if(child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + if(this.stage)child.setStageReference(this.stage); + + return child; + } + else + { + throw new Error(child + ' 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('Supplied index does not exist in the child list, or the supplied DisplayObject must be a child of the caller'); + } +}; + +/** + * Removes a child from the container. + * + * @method removeChild + * @param child {DisplayObject} The DisplayObject to remove + */ +PIXI.DisplayObjectContainer.prototype.removeChild = function(child) +{ + return this.removeChildAt( this.children.indexOf( child ) ); +}; + +/** + * Removes a child from the specified index position in the child list of the container. + * + * @method removeChildAt + * @param index {Number} The index to get the child from + */ +PIXI.DisplayObjectContainer.prototype.removeChildAt = function(index) +{ + var child = this.getChildAt( index ); + if(this.stage) + child.removeStageReference(); + + child.parent = undefined; + this.children.splice( index, 1 ); + return child; +}; + +/** +* Removes all child instances from the child list of the container. +* +* @method removeChildren +* @param beginIndex {Number} The beginning position. Predefined value is 0. +* @param endIndex {Number} The ending position. Predefined value is children's array length. +*/ +PIXI.DisplayObjectContainer.prototype.removeChildren = function(beginIndex, endIndex) +{ + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + + if (range > 0 && range <= end) + { + var removed = this.children.splice(begin, range); + for (var i = 0; i < removed.length; i++) { + var child = removed[i]; + if(this.stage) + child.removeStageReference(); + child.parent = undefined; + } + return removed; + } + else + { + throw new Error( 'Range Error, numeric values are outside the acceptable range' ); + } +}; + +/* + * 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 ); + + if(this._cacheAsBitmap)return; + + 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; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if(this._filters) + { + spriteBatch.flush(); + renderSession.filterManager.pushFilter(this._filterBlock); + } + + if(this._mask) + { + spriteBatch.stop(); + renderSession.maskManager.pushMask(this.mask, renderSession); + spriteBatch.start(); + } + + // add this sprite to the batch + spriteBatch.render(this); + + // now loop through the children and make sure they get rendered + for(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; + + this.currentFrame = this.currentFrame % this.textures.length; + + 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(); + } + } +}; + +/** + * A short hand way of creating a movieclip from an array of frame ids + * + * @static + * @method fromFrames + * @param frames {Array} the array of frames ids the movieclip will use as its texture frames + */ +PIXI.MovieClip.fromFrames = function(frames) +{ + var textures = []; + + for (var i = 0; i < frames.length; i++) + { + textures.push(new PIXI.Texture.fromFrame(frames[i])); + } + + return new PIXI.MovieClip(textures); +}; + +/** + * A short hand way of creating a movieclip from an array of image ids + * + * @static + * @method fromFrames + * @param frames {Array} the array of image ids the movieclip will use as its texture frames + */ +PIXI.MovieClip.fromImages = function(images) +{ + var textures = []; + + for (var i = 0; i < images.length; i++) + { + textures.push(new PIXI.Texture.fromImage(images[i])); + } + + return new PIXI.MovieClip(textures); +}; +/** + * @author Mat Groves http://matgroves.com/ @Doormat23 + */ + + +PIXI.FilterBlock = function() +{ + this.visible = true; + this.renderable = true; +}; + +/** + * @author Mat Groves http://matgroves.com/ @Doormat23 + * - Modified by Tom Slezakowski http://www.tomslezakowski.com @TomSlezakowski (24/03/2014) - Added dropShadowColor. + */ + +/** + * 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 + * @param [style.dropShadow=false] {Boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {String} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/4] {Number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {Number} Set a distance of the drop shadow + */ +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); +}; + +// constructor +PIXI.Text.prototype = Object.create(PIXI.Sprite.prototype); +PIXI.Text.prototype.constructor = PIXI.Text; + + +/** + * The width of the sprite, setting this will actually modify the scale to achieve the value set + * + * @property width + * @type Number + */ +Object.defineProperty(PIXI.Text.prototype, 'width', { + get: function() { + + if(this.dirty) + { + this.updateText(); + this.dirty = false; + } + + + return this.scale.x * this.texture.frame.width; + }, + set: function(value) { + this.scale.x = value / this.texture.frame.width; + this._width = value; + } +}); + +/** + * The height of the Text, setting this will actually modify the scale to achieve the value set + * + * @property height + * @type Number + */ +Object.defineProperty(PIXI.Text.prototype, 'height', { + get: function() { + + if(this.dirty) + { + this.updateText(); + this.dirty = false; + } + + + return this.scale.y * this.texture.frame.height; + }, + set: function(value) { + this.scale.y = value / this.texture.frame.height; + this._height = value; + } +}); + + +/** + * 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 + * @param [style.dropShadow=false] {Boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {String} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/4] {Number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {Number} Set a distance of the drop shadow + */ +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; + style.wordWrapWidth = style.wordWrapWidth || 100; + + style.dropShadow = style.dropShadow || false; + style.dropShadowAngle = style.dropShadowAngle || Math.PI / 6; + style.dropShadowDistance = style.dropShadowDistance || 4; + style.dropShadowColor = style.dropShadowColor || 'black'; + + 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); + } + + var width = maxLineWidth + this.style.strokeThickness; + if(this.style.dropShadow)width += this.style.dropShadowDistance; + + this.canvas.width = width + this.context.lineWidth; + //calculate text height + var lineHeight = this.determineFontHeight('font: ' + this.style.font + ';') + this.style.strokeThickness; + + var height = lineHeight * lines.length; + if(this.style.dropShadow)height += this.style.dropShadowDistance; + + this.canvas.height = height; + + if(navigator.isCocoonJS) this.context.clearRect(0,0,this.canvas.width,this.canvas.height); + + this.context.font = this.style.font; + this.context.strokeStyle = this.style.stroke; + this.context.lineWidth = this.style.strokeThickness; + this.context.textBaseline = 'top'; + + var linePositionX; + var linePositionY; + + if(this.style.dropShadow) + { + this.context.fillStyle = this.style.dropShadowColor; + + var xShadowOffset = Math.sin(this.style.dropShadowAngle) * this.style.dropShadowDistance; + var yShadowOffset = Math.cos(this.style.dropShadowAngle) * this.style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = this.style.strokeThickness / 2; + linePositionY = this.style.strokeThickness / 2 + i * lineHeight; + + if(this.style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if(this.style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if(this.style.fill) + { + this.context.fillText(lines[i], linePositionX + xShadowOffset, linePositionY + yShadowOffset); + } + + // if(dropShadow) + } + } + + //set canvas text styles + this.context.fillStyle = this.style.fill; + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = this.style.strokeThickness / 2; + linePositionY = this.style.strokeThickness / 2 + i * lineHeight; + + if(this.style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if(this.style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if(this.style.stroke && this.style.strokeThickness) + { + this.context.strokeText(lines[i], linePositionX, linePositionY); + } + + if(this.style.fill) + { + this.context.fillText(lines[i], linePositionX, linePositionY); + } + + // if(dropShadow) + } + + + 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.crop.width = this.texture.frame.width = this.canvas.width; + this.texture.crop.height = 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(j === 0 || 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 destroyBaseTexture {Boolean} whether to destroy the base texture as well + */ +PIXI.Text.prototype.destroy = function(destroyBaseTexture) +{ + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this.texture.destroy(destroyBaseTexture === undefined ? true : destroyBaseTexture); +}; + +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(); + + + /** + * 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); + + window.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; + + window.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.rebuildInteractiveGraph(); + } + + // 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; + } +}; + +PIXI.InteractionManager.prototype.rebuildInteractiveGraph = function() +{ + this.dirty = false; + + var len = this.interactiveItems.length; + + for (var 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); +}; + +/** + * 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) +{ + if(this.dirty) + { + this.rebuildInteractiveGraph(); + } + + 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) +{ + if(this.dirty) + { + this.rebuildInteractiveGraph(); + } + + 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() +{ + if(this.dirty) + { + this.rebuildInteractiveGraph(); + } + + 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) +{ + if(this.dirty) + { + this.rebuildInteractiveGraph(); + } + + 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) +{ + if(this.dirty) + { + this.rebuildInteractiveGraph(); + } + + 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; + } + + for (var j = 0; j < this.interactiveItems.length; j++) + { + var item = this.interactiveItems[j]; + if(item.touchmove && item.__touchData && item.__touchData[touchEvent.identifier]) 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) +{ + if(this.dirty) + { + this.rebuildInteractiveGraph(); + } + + 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 = item.__touchData || {}; + item.__touchData[touchEvent.identifier] = 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) +{ + if(this.dirty) + { + this.rebuildInteractiveGraph(); + } + + //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]; + + if(item.__touchData && item.__touchData[touchEvent.identifier]) { + + item.__hit = this.hitTest(item, item.__touchData[touchEvent.identifier]); + + // 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[touchEvent.identifier] = null; + } + } + // 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 interactionManager + * @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 listeners + * @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 ].unshift( 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 = listeners[ event.type ].length-1; i >= 0; i--) { +// 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 ) { + + if ( listeners[ type ] === undefined ) return; + + 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); +}; + +/** + * This helper function will automatically detect which renderer you should be using. + * This function is very similar to the autoDetectRenderer function except that is will return a canvas renderer for android. + * Even thought both android chrome suports webGL the canvas implementation perform better at the time of writing. + * This function will likely change and update as webGL performance imporoves on thease devices. + * @class getRecommendedRenderer + * @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.autoDetectRecommendedRenderer = 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; + } + } )(); + + var isAndroid = /Android/i.test(navigator.userAgent); + + if( webgl && !isAndroid) + { + 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) +{ + this._UID = PIXI._UID++; + + /** + * @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._glTextures[gl.id]); + + // Extended texture data + if (uniform.textureData) + { + var data = uniform.textureData; + + // GLTexture = mag linear, min linear_mipmap_linear, wrap repeat + gl.generateMipmap(gl.TEXTURE_2D); + // GLTextureLinear = mag/min linear, wrap clamp + // GLTextureNearestRepeat = mag/min NEAREST, wrap repeat + // GLTextureNearest = mag/min nearest, wrap clamp + // AudioTexture = whatever + luminance + width 512, height 2, border 0 + // KeyTexture = whatever + luminance + width 256, height 2, border 0 + + // magFilter can be: gl.LINEAR, gl.LINEAR_MIPMAP_LINEAR or gl.NEAREST + // wrapS/T can be: gl.CLAMP_TO_EDGE or gl.REPEAT + + var magFilter = (data.magFilter) ? data.magFilter : gl.LINEAR; + var minFilter = (data.minFilter) ? data.minFilter : gl.LINEAR; + var wrapS = (data.wrapS) ? data.wrapS : gl.CLAMP_TO_EDGE; + var wrapT = (data.wrapT) ? data.wrapT : gl.CLAMP_TO_EDGE; + var format = (data.luminance) ? gl.LUMINANCE : gl.RGBA; + + if (data.repeat) + { + wrapS = gl.REPEAT; + wrapT = gl.REPEAT; + } + + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, !!data.flipY); + + if (data.width) + { + var width = (data.width) ? data.width : 512; + var height = (data.height) ? data.height : 2; + var border = (data.border) ? data.border : 0; + + // void texImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, ArrayBufferView? pixels); + gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, border, format, gl.UNSIGNED_BYTE, null); + } + else + { + // void texImage2D(GLenum target, GLint level, GLenum internalformat, GLenum format, GLenum type, ImageData? pixels); + gl.texImage2D(gl.TEXTURE_2D, 0, format, gl.RGBA, gl.UNSIGNED_BYTE, uniform.value.baseTexture.source); + } + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, magFilter); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, minFilter); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrapS); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrapT); + } + + gl.uniform1i(uniform.uniformLocation, this.textureCount); + + uniform._init = true; + + this.textureCount++; + +}; + +/** +* Updates the shader uniform values. +* +* @method syncUniforms +*/ +PIXI.PixiShader.prototype.syncUniforms = function() +{ + this.textureCount = 1; + var uniform; + var gl = this.gl; + + // This would probably be faster in an array and it would guarantee key order + for (var key in this.uniforms) + { + uniform = this.uniforms[key]; + + if (uniform.glValueLength === 1) + { + if (uniform.glMatrix === true) + { + uniform.glFunc.call(gl, uniform.uniformLocation, uniform.transpose, uniform.value); + } + else + { + uniform.glFunc.call(gl, uniform.uniformLocation, uniform.value); + } + } + else if (uniform.glValueLength === 2) + { + uniform.glFunc.call(gl, uniform.uniformLocation, uniform.value.x, uniform.value.y); + } + else if (uniform.glValueLength === 3) + { + uniform.glFunc.call(gl, uniform.uniformLocation, uniform.value.x, uniform.value.y, uniform.value.z); + } + else if (uniform.glValueLength === 4) + { + uniform.glFunc.call(gl, uniform.uniformLocation, uniform.value.x, uniform.value.y, uniform.value.z, uniform.value.w); + } + else if (uniform.type === 'sampler2D') + { + if (uniform._init) + { + gl.activeTexture(gl['TEXTURE' + this.textureCount]); + gl.bindTexture(gl.TEXTURE_2D, uniform.value.baseTexture._glTextures[gl.id] || PIXI.createWebGLTexture( uniform.value.baseTexture, gl)); + gl.uniform1i(uniform.uniformLocation, this.textureCount); + this.textureCount++; + } + else + { + this.initSampler2D(uniform); + } + } + } + +}; + +/** +* Destroys the shader +* @method destroy +*/ +PIXI.PixiShader.prototype.destroy = function() +{ + this.gl.deleteProgram( this.program ); + this.uniforms = null; + this.gl = null; + + this.attributes = null; +}; + +/** +* The Default Vertex shader source +* @property defaultVertexSrc +* @type String +*/ +PIXI.PixiShader.defaultVertexSrc = [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + 'attribute 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) +{ + this._UID = PIXI._UID++; + + /** + * @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(gl) +{ + this._UID = PIXI._UID++; + + this.gl = gl; + + /** + * @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 = vec4(1.0, 0.0, 0.0, 1.0);',//gl_FragColor * alpha;', + '}' + ]; + + /** + * @property {array} fragmentSrc - The fragment shader. + */ + this.vertexSrc = [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + 'uniform mat3 translationMatrix;', + 'uniform vec2 projectionVector;', + 'uniform vec2 offsetVector;', + // 'uniform float alpha;', + // 'uniform vec3 tint;', + 'varying vec2 vTextureCoord;', + // 'varying vec4 vColor;', + + 'void main(void) {', + ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', + ' v -= offsetVector.xyx;', + ' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);', + ' vTextureCoord = aTextureCoord;', + // ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ]; + + this.init(); +}; + +/** +* Initialises the shader +* @method init +* +*/ +PIXI.StripShader.prototype.init = function() +{ + var gl = this.gl; + + var program = PIXI.compileProgram(gl, this.vertexSrc, this.fragmentSrc); + gl.useProgram(program); + + // get and store the uniforms for the shader + this.uSampler = gl.getUniformLocation(program, 'uSampler'); + this.projectionVector = gl.getUniformLocation(program, 'projectionVector'); + this.offsetVector = gl.getUniformLocation(program, 'offsetVector'); + this.colorAttribute = gl.getAttribLocation(program, 'aColor'); + //this.dimensions = gl.getUniformLocation(this.program, 'dimensions'); + + // get and store the attributes + this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition'); + this.aTextureCoord = gl.getAttribLocation(program, 'aTextureCoord'); + + this.attributes = [this.aVertexPosition, this.aTextureCoord]; + + this.translationMatrix = gl.getUniformLocation(program, 'translationMatrix'); + this.alpha = gl.getUniformLocation(program, 'alpha'); + + this.program = program; +}; + +/** + * @author Mat Groves http://matgroves.com/ @Doormat23 + */ + +/** +* @class PrimitiveShader +* @constructor +* @param gl {WebGLContext} the current WebGL drawing context +*/ +PIXI.PrimitiveShader = function(gl) +{ + this._UID = PIXI._UID++; + + /** + * @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 + */ + +/** +* @class ComplexPrimitiveShader +* @constructor +* @param gl {WebGLContext} the current WebGL drawing context +*/ +PIXI.ComplexPrimitiveShader = function(gl) +{ + this._UID = PIXI._UID++; + /** + * @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 vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + '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 = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ]; + + this.init(); +}; + +/** +* Initialises the shader +* @method init +* +*/ +PIXI.ComplexPrimitiveShader.prototype.init = function() +{ + + var gl = this.gl; + + var program = PIXI.compileProgram(gl, this.vertexSrc, this.fragmentSrc); + gl.useProgram(program); + + // get and store the uniforms for the shader + this.projectionVector = gl.getUniformLocation(program, 'projectionVector'); + this.offsetVector = gl.getUniformLocation(program, 'offsetVector'); + this.tintColor = gl.getUniformLocation(program, 'tint'); + this.color = gl.getUniformLocation(program, 'color'); + + + // get and store the attributes + this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition'); + // this.colorAttribute = gl.getAttribLocation(program, 'aColor'); + + this.attributes = [this.aVertexPosition, this.colorAttribute]; + + this.translationMatrix = gl.getUniformLocation(program, 'translationMatrix'); + this.alpha = gl.getUniformLocation(program, 'alpha'); + + this.program = program; +}; + +/** +* Destroys the shader +* @method destroy +* +*/ +PIXI.ComplexPrimitiveShader.prototype.destroy = function() +{ + this.gl.deleteProgram( this.program ); + this.uniforms = null; + this.gl = null; + + this.attribute = null; +}; + +/** + * @author Mat Groves http://matgroves.com/ @Doormat23 + */ + +/** + * 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, + webGLData; + + if(graphics.dirty) + { + PIXI.WebGLGraphics.updateGraphics(graphics, gl); + } + + var webGL = graphics._webGL[gl.id]; + + // This could be speeded up for sure! + + for (var i = 0; i < webGL.data.length; i++) + { + if(webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderSession.stencilManager.pushStencil(graphics, webGLData, renderSession); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderSession.stencilManager.popStencil(graphics, webGLData, renderSession); + + this.last = webGLData.mode; + } + else + { + webGLData = webGL.data[i]; + + + renderSession.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderSession.shaderManager.primitiveShader; + 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, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + } + } +}; + +/** + * 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) +{ + // get the contexts graphics object + var webGL = graphics._webGL[gl.id]; + // if the graphics object does not exist in the webGL context time to create it! + if(!webGL)webGL = graphics._webGL[gl.id] = {lastIndex:0, data:[], gl:gl}; + + // flag the graphics as not dirty as we are about to update it... + graphics.dirty = false; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if(graphics.clearDirty) + { + graphics.clearDirty = false; + + // lop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + graphicsData.reset(); + PIXI.WebGLGraphics.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + if(data.type === PIXI.Graphics.POLY) + { + // MAKE SURE WE HAVE THE CORRECT TYPE.. + if(data.fill) + { + if(data.points.length > 6) + { + if(data.points.length > 5 * 2) + { + webGLData = PIXI.WebGLGraphics.switchMode(webGL, 1); + PIXI.WebGLGraphics.buildComplexPoly(data, webGLData); + } + else + { + webGLData = PIXI.WebGLGraphics.switchMode(webGL, 0); + PIXI.WebGLGraphics.buildPoly(data, webGLData); + } + } + } + + if(data.lineWidth > 0) + { + webGLData = PIXI.WebGLGraphics.switchMode(webGL, 0); + PIXI.WebGLGraphics.buildLine(data, webGLData); + + } + } + else + { + webGLData = PIXI.WebGLGraphics.switchMode(webGL, 0); + + if(data.type === PIXI.Graphics.RECT) + { + PIXI.WebGLGraphics.buildRectangle(data, webGLData); + } + else if(data.type === PIXI.Graphics.CIRC || data.type === PIXI.Graphics.ELIP) + { + PIXI.WebGLGraphics.buildCircle(data, webGLData); + } + else if(data.type === PIXI.Graphics.RREC) + { + PIXI.WebGLGraphics.buildRoundedRectangle(data, webGLData); + } + } + + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + if(webGLData.dirty)webGLData.upload(); + } +}; + + +PIXI.WebGLGraphics.switchMode = function(webGL, type) +{ + var webGLData; + + if(!webGL.data.length) + { + webGLData = PIXI.WebGLGraphics.graphicsDataPool.pop() || new PIXI.WebGLGraphicsData(webGL.gl); + webGLData.mode = type; + webGL.data.push(webGLData); + } + else + { + webGLData = webGL.data[webGL.data.length-1]; + + if(webGLData.mode !== type || type === 1) + { + webGLData = PIXI.WebGLGraphics.graphicsDataPool.pop() || new PIXI.WebGLGraphicsData(webGL.gl); + webGLData.mode = type; + webGL.data.push(webGLData); + } + } + + webGLData.dirty = true; + + return webGLData; +}; + +/** + * 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 rounded rectangle to draw + * + * @static + * @private + * @method buildRoundedRectangle + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {Object} + */ +PIXI.WebGLGraphics.buildRoundedRectangle = function(graphicsData, webGLData) +{ + + var points = graphicsData.points; + var x = points[0]; + var y = points[1]; + var width = points[2]; + var height = points[3]; + var radius = points[4]; + + + var recPoints = []; + recPoints.push(x, y + radius); + recPoints = recPoints.concat(PIXI.WebGLGraphics.quadraticBezierCurve(x, y + height - radius, x, y + height, x + radius, y + height)); + recPoints = recPoints.concat(PIXI.WebGLGraphics.quadraticBezierCurve(x + width - radius, y + height, x + width, y + height, x + width, y + height - radius)); + recPoints = recPoints.concat(PIXI.WebGLGraphics.quadraticBezierCurve(x + width, y + radius, x + width, y, x + width - radius, y)); + recPoints = recPoints.concat(PIXI.WebGLGraphics.quadraticBezierCurve(x + radius, y, x, y, x, y + radius)); + + + 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; + + var triangles = PIXI.PolyK.Triangulate(recPoints); + + var i = 0; + for (i = 0; i < triangles.length; i+=3) + { + indices.push(triangles[i] + vecPos); + indices.push(triangles[i] + vecPos); + indices.push(triangles[i+1] + vecPos); + indices.push(triangles[i+2] + vecPos); + indices.push(triangles[i+2] + vecPos); + } + + for (i = 0; i < recPoints.length; i++) + { + verts.push(recPoints[i], recPoints[++i], r, g, b, alpha); + } + } + + if (graphicsData.lineWidth) { + var tempPoints = graphicsData.points; + + graphicsData.points = recPoints; + + PIXI.WebGLGraphics.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Calcul the points for a quadratic bezier curve. (helper function..) + * Based on : https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @param {number} fromX Origin point x + * @param {number} fromY Origin point x + * @param {number} cpX Control point x + * @param {number} cpY Control point y + * @param {number} toX Destination point x + * @param {number} toY Destination point y + * @return {number[]} + */ +PIXI.WebGLGraphics.quadraticBezierCurve = function(fromX, fromY, cpX, cpY, toX, toY) { + var xa, + ya, + xb, + yb, + x, + y, + n = 20, + points = []; + + function getPt(n1 , n2, perc) { + var diff = n2 - n1; + + return n1 + ( diff * perc ); + } + + var j = 0; + for (var i = 0; i <= n; i++ ) + { + j = i / n; + + // The Green Line + xa = getPt( fromX , cpX , j ); + ya = getPt( fromY , cpY , j ); + xb = getPt( cpX , toX , j ); + yb = getPt( cpY , toY , j ); + + // The Black Dot + x = getPt( xa , xb , j ); + y = getPt( ya , yb , j ); + + points.push(x, y); + } + return points; +}; + +/** + * 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) + { + // need to clone as we are going to slightly modify the shape.. + points = points.slice(); + + 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 complex polygon to draw + * + * @static + * @private + * @method buildPoly + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {Object} + */ +PIXI.WebGLGraphics.buildComplexPoly = function(graphicsData, webGLData) +{ + + //TODO - no need to copy this as it gets turned into a FLoat32Array anyways.. + var points = graphicsData.points.slice(); + if(points.length < 6)return; + + // get first and last point.. figure out the middle! + var indices = webGLData.indices; + webGLData.points = points; + webGLData.alpha = graphicsData.fillAlpha; + webGLData.color = PIXI.hex2rgb(graphicsData.fillColor); + + /* + calclate the bounds.. + */ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + var x,y; + + // get size.. + for (var i = 0; i < points.length; i+=2) + { + x = points[i]; + y = points[i+1]; + + minX = x < minX ? x : minX; + maxX = x > maxX ? x : maxX; + + minY = y < minY ? y : minY; + maxY = y > maxY ? y : maxY; + } + + // add a quad to the end cos there is no point making another buffer! + points.push(minX, minY, + maxX, minY, + maxX, maxY, + minX, maxY); + + // push a quad onto the end.. + + //TODO - this aint needed! + var length = points.length / 2; + for (i = 0; i < length; i++) + { + indices.push( i ); + } + +}; + +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); + } + +}; + +PIXI.WebGLGraphics.graphicsDataPool = []; + +PIXI.WebGLGraphicsData = function(gl) +{ + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.lastIndex = 0; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +}; + +PIXI.WebGLGraphicsData.prototype.reset = function() +{ + this.points = []; + this.indices = []; + this.lastIndex = 0; +}; + +PIXI.WebGLGraphicsData.prototype.upload = function() +{ + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; + +/** + * @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) + * @param preserveDrawingBuffer=false {Boolean} enables drawing buffer preservation, enable this if you need to call toDataUrl on the webgl context + * + */ +PIXI.WebGLRenderer = function(width, height, view, transparent, antialias, preserveDrawingBuffer) +{ + if(!PIXI.defaultRenderer) + { + PIXI.sayHello('webGL'); + 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 value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @property preserveDrawingBuffer + * @type Boolean + */ + this.preserveDrawingBuffer = preserveDrawingBuffer; + + /** + * 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, + preserveDrawingBuffer: preserveDrawingBuffer + }; + + var gl = null; + + ['experimental-webgl', 'webgl'].forEach(function(name) { + try { + gl = gl || this.view.getContext(name, this.options); + } catch(e) {} + }, this); + + if (!gl) { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer' + this); + } + + this.gl = 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.primitiveBatch = new PIXI.WebGLPrimitiveBatch(gl); // primitive batch renderer + 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.stencilManager = new PIXI.WebGLStencilManager(gl); + this.blendModeManager = new PIXI.WebGLBlendModeManager(gl); + + 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.blendModeManager = this.blendModeManager; + // this.renderSession.primitiveBatch = this.primitiveBatch; + this.renderSession.spriteBatch = this.spriteBatch; + this.renderSession.stencilManager = this.stencilManager; + this.renderSession.renderer = this; + + 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(); + + + // interaction + if(stage._interactive) + { + //need to add some events! + if(!stage._interactiveEventsAdded) + { + stage._interactiveEventsAdded = true; + stage.interactionManager.setTarget(this); + } + } + + 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) +{ + this.renderSession.blendModeManager.setBlendMode(PIXI.blendModes.NORMAL); + // 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); + +// this.primitiveBatch.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(); + +// this.primitiveBatch.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..updateWebGLTexture(PIXI.texturesToUpdate[i], this.gl); + + + 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, texture.premultipliedAlpha); + + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.source); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); + 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); + + texture._dirty[gl.id] = false; + } + + 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, texture.premultipliedAlpha); + + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.source); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); + 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); + } + + texture._dirty[gl.id] = false; + } + +}; + +/** + * 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.primitiveBatch.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.primitiveBatch.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.WebGLBlendModeManager = function(gl) +{ + this.gl = gl; + this.currentBlendMode = 99999; +}; + +/** +* Sets-up the given blendMode from WebGL's point of view +* @method setBlendMode +* +* @param blendMode {Number} the blendMode, should be a Pixi const, such as PIXI.BlendModes.ADD +*/ +PIXI.WebGLBlendModeManager.prototype.setBlendMode = function(blendMode) +{ + if(this.currentBlendMode === blendMode)return false; + // console.log("SWAP!") + this.currentBlendMode = blendMode; + + var blendModeWebGL = PIXI.blendModesWebGL[this.currentBlendMode]; + this.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); + + return true; +}; + +PIXI.WebGLBlendModeManager.prototype.destroy = function() +{ + this.gl = null; +}; +/** + * @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); + + this.reverse = false; + this.count = 0; +}; + +/** +* 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 = renderSession.gl; + + if(maskData.dirty) + { + PIXI.WebGLGraphics.updateGraphics(maskData, gl); + } + + if(!maskData._webGL[gl.id].data.length)return; + + renderSession.stencilManager.pushStencil(maskData, maskData._webGL[gl.id].data[0], renderSession); +}; + +/** +* Removes the last filter from the filter stack and doesn't return it +* @method popMask +* +* @param renderSession {RenderSession} an object containing all the useful parameters +*/ +PIXI.WebGLMaskManager.prototype.popMask = function(maskData, renderSession) +{ + var gl = this.gl; + renderSession.stencilManager.popStencil(maskData, maskData._webGL[gl.id].data[0], renderSession); +}; + + +/** +* Destroys the mask stack +* @method destroy +*/ +PIXI.WebGLMaskManager.prototype.destroy = function() +{ + this.maskStack = null; + this.gl = null; +}; +/** + * @author Mat Groves http://matgroves.com/ @Doormat23 + */ + +//BA0285 +//Intercontinental Hotel, 888 Howard Street +//San Francisco + +/** +* @class WebGLStencilManager +* @constructor +* @param gl {WebGLContext} the current WebGL drawing context +* @private +*/ +PIXI.WebGLStencilManager = function(gl) +{ + + this.stencilStack = []; + this.setContext(gl); + this.reverse = true; + this.count = 0; + +}; + +/** +* Sets the drawing context to the one given in parameter +* @method setContext +* @param gl {WebGLContext} the current WebGL drawing context +*/ +PIXI.WebGLStencilManager.prototype.setContext = function(gl) +{ + this.gl = gl; +}; + +/** +* Applies the Mask and adds it to the current filter stack +* @method pushMask +* @param maskData {Array} +* @param renderSession {RenderSession} +*/ +PIXI.WebGLStencilManager.prototype.pushStencil = function(graphics, webGLData, renderSession) +{ + var gl = this.gl; + this.bindGraphics(graphics, webGLData, renderSession); + + if(this.stencilStack.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + this.reverse = true; + this.count = 0; + } + + this.stencilStack.push(webGLData); + + var level = this.count; + + gl.colorMask(false, false, false, false); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + + if(webGLData.mode === 1) + { + + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if(this.reverse) + { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else + { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + if(this.reverse) + { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else + { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + + this.reverse = !this.reverse; + } + else + { + if(!this.reverse) + { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else + { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if(!this.reverse) + { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else + { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + this.count++; +}; + +//TODO this does not belong here! +PIXI.WebGLStencilManager.prototype.bindGraphics = function(graphics, webGLData, renderSession) +{ + //if(this._currentGraphics === graphics)return; + this._currentGraphics = graphics; + + var gl = this.gl; + + // bind the graphics object.. + var projection = renderSession.projection, + offset = renderSession.offset, + shader;// = renderSession.shaderManager.primitiveShader; + + if(webGLData.mode === 1) + { + shader = renderSession.shaderManager.complexPrimativeShader; + + renderSession.shaderManager.setShader( shader ); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, PIXI.hex2rgb(graphics.tint)); + gl.uniform3fv(shader.color, webGLData.color); + + gl.uniform1f(shader.alpha, graphics.worldAlpha * webGLData.alpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0); + + + // now do the rest.. + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } + else + { + //renderSession.shaderManager.activatePrimitiveShader(); + shader = renderSession.shaderManager.primitiveShader; + renderSession.shaderManager.setShader( shader ); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, PIXI.hex2rgb(graphics.tint)); + + gl.uniform1f(shader.alpha, graphics.worldAlpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } +}; + +PIXI.WebGLStencilManager.prototype.popStencil = function(graphics, webGLData, renderSession) +{ + var gl = this.gl; + this.stencilStack.pop(); + + this.count--; + + if(this.stencilStack.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + + } + else + { + + var level = this.count; + + this.bindGraphics(graphics, webGLData, renderSession); + + gl.colorMask(false, false, false, false); + + if(webGLData.mode === 1) + { + this.reverse = !this.reverse; + + if(this.reverse) + { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else + { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if(!this.reverse) + { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else + { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + + } + else + { + // console.log("<<>>") + if(!this.reverse) + { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else + { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if(!this.reverse) + { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else + { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + + } + + //renderSession.shaderManager.deactivatePrimitiveShader(); +}; + +/** +* Destroys the mask stack +* @method destroy +*/ +PIXI.WebGLStencilManager.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 = []; + this.shaderMap = []; + + for (var i = 0; i < this.maxAttibs; i++) { + this.attribState[i] = false; + } + + this.setContext(gl); + // the final one is used for the rendering strips +}; + + +/** +* 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); + + // the next one is used for rendering triangle strips + this.complexPrimativeShader = new PIXI.ComplexPrimitiveShader(gl); + + // this shader is used for the default sprite rendering + this.defaultShader = new PIXI.PixiShader(gl); + + // this shader is used for the fast sprite rendering + this.fastShader = new PIXI.PixiFastShader(gl); + + // the next one is used for rendering triangle strips + this.stripShader = new PIXI.StripShader(gl); + this.setShader(this.defaultShader); +}; + + +/** +* Takes the attributes given in parameters +* @method setAttribs +* @param attribs {Array} attribs +*/ +PIXI.WebGLShaderManager.prototype.setAttribs = function(attribs) +{ + // reset temp state + + var i; + + for (i = 0; i < this.tempAttribState.length; i++) + { + this.tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + var attribId = attribs[i]; + this.tempAttribState[attribId] = true; + } + + var gl = this.gl; + + for (i = 0; i < this.attribState.length; i++) + { + if(this.attribState[i] !== this.tempAttribState[i]) + { + this.attribState[i] = this.tempAttribState[i]; + + if(this.tempAttribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } +}; + +PIXI.WebGLShaderManager.prototype.setShader = function(shader) +{ + if(this._currentId === shader._UID)return false; + + this._currentId = shader._UID; + + this.currentShader = shader; + + this.gl.useProgram(shader.program); + this.setAttribs(shader.attributes); + + return true; +}; + +/** +* Destroys +* @method destroy +*/ +PIXI.WebGLShaderManager.prototype.destroy = function() +{ + this.attribState = null; + + this.tempAttribState = null; + + this.primitiveShader.destroy(); + + this.defaultShader.destroy(); + + this.fastShader.destroy(); + + this.stripShader.destroy(); + + this.gl = null; +}; + + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * + * 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 = 2000;//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); + + this.dirty = true; + + this.textures = []; + this.blendModes = []; +}; + +/** +* +* @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) +{ + var texture = sprite.texture; + + //TODO set blend modes.. + // check texture.. + if(this.currentBatchSize >= this.size) + { + //return; + this.flush(); + this.currentBaseTexture = texture.baseTexture; + } + + // get the uvs for the texture + var uvs = texture._uvs; + // if the uvs have not updated then no point rendering just yet! + if(!uvs)return; + + // get the sprites current alpha + var alpha = sprite.worldAlpha; + var tint = sprite.tint; + + var verticies = this.vertices; + + + // TODO trim?? + var aX = sprite.anchor.x; + var aY = sprite.anchor.y; + + var w0, w1, h0, h1; + + if (texture.trim) + { + // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. + var trim = texture.trim; + + w1 = trim.x - aX * trim.width; + w0 = w1 + texture.crop.width; + + h1 = trim.y - aY * trim.height; + h0 = h1 + texture.crop.height; + + } + else + { + w0 = (texture.frame.width ) * (1-aX); + w1 = (texture.frame.width ) * -aX; + + h0 = texture.frame.height * (1-aY); + h1 = texture.frame.height * -aY; + } + + var index = this.currentBatchSize * 4 * this.vertSize; + + var worldTransform = sprite.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 batchsize + this.textures[this.currentBatchSize] = sprite.texture.baseTexture; + this.blendModes[this.currentBatchSize] = sprite.blendMode; + + 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; + + + // check texture.. + if(this.currentBatchSize >= this.size) + { + //return; + this.flush(); + this.currentBaseTexture = texture.baseTexture; + } + + // 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.tileScaleOffset.x; + tilingSprite.tilePosition.y %= texture.baseTexture.height * tilingSprite.tileScaleOffset.y; + + var offsetX = tilingSprite.tilePosition.x/(texture.baseTexture.width*tilingSprite.tileScaleOffset.x); + var offsetY = tilingSprite.tilePosition.y/(texture.baseTexture.height*tilingSprite.tileScaleOffset.y); + + 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; + var aY = tilingSprite.anchor.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.textures[this.currentBatchSize] = texture.baseTexture; + this.blendModes[this.currentBatchSize] = tilingSprite.blendMode; + 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; + + this.renderSession.shaderManager.setShader(this.renderSession.shaderManager.defaultShader); + + if(this.dirty) + { + this.dirty = false; + // bind the main texture + gl.activeTexture(gl.TEXTURE0); + + // bind the buffers + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + // 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); + + } + + // 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 nextTexture, nextBlendMode; + var batchSize = 0; + var start = 0; + + var currentBaseTexture = null; + var currentBlendMode = this.renderSession.blendModeManager.currentBlendMode; + + for (var i = 0, j = this.currentBatchSize; i < j; i++) { + + nextTexture = this.textures[i]; + nextBlendMode = this.blendModes[i]; + + if(currentBaseTexture !== nextTexture || currentBlendMode !== nextBlendMode) + { + this.renderBatch(currentBaseTexture, batchSize, start); + + start = i; + batchSize = 0; + currentBaseTexture = nextTexture; + currentBlendMode = nextBlendMode; + + this.renderSession.blendModeManager.setBlendMode( currentBlendMode ); + } + + batchSize++; + } + + this.renderBatch(currentBaseTexture, batchSize, start); + + // then reset the batch! + this.currentBatchSize = 0; +}; + +PIXI.WebGLSpriteBatch.prototype.renderBatch = function(texture, size, startIndex) +{ + if(size === 0)return; + + var gl = this.gl; + // bind the current texture + gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id] || PIXI.createWebGLTexture(texture, gl)); + + // check if a texture is dirty.. + if(texture._dirty[gl.id]) + { + PIXI.updateWebGLTexture(this.currentBaseTexture, gl); + } + + // now draw those suckas! + gl.drawElements(gl.TRIANGLES, size * 6, gl.UNSIGNED_SHORT, startIndex * 6 * 2); + + // increment the draw count + this.renderSession.drawCount++; +}; + +/** +* +* @method stop +* +*/ +PIXI.WebGLSpriteBatch.prototype.stop = function() +{ + this.flush(); +}; + +/** +* +* @method start +* +*/ +PIXI.WebGLSpriteBatch.prototype.start = function() +{ + this.dirty = true; +}; + +/** +* Destroys the SpriteBatch +* @method destroy +*/ +PIXI.WebGLSpriteBatch.prototype.destroy = function() +{ + + this.vertices = null; + this.indices = null; + + this.gl.deleteBuffer( this.vertexBuffer ); + this.gl.deleteBuffer( this.indexBuffer ); + + this.currentBaseTexture = null; + + this.gl = null; +}; + + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * + * Heavily inspired by LibGDX's WebGLSpriteBatch: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/WebGLSpriteBatch.java + */ + +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); +}; + +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.renderSession.blendModeManager.currentBlendMode) + { + this.flush(); + this.renderSession.blendModeManager.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]); + + // 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); + + +}; + + + +/** + * @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; + // console.log(this.width) + this.width = projection.x * 2; + this.height = -projection.y * 2; + this.buffer = buffer; +}; + +/** +* Applies the filter and adds it to the current filter stack +* @method pushFilter +* @param filterBlock {Object} the filter that will be pushed to the current filter stack +*/ +PIXI.WebGLFilterManager.prototype.pushFilter = function(filterBlock) +{ + var gl = this.gl; + + var projection = this.renderSession.projection; + var offset = this.renderSession.offset; + + filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); + + + // filter program + // OPTIMISATION - the first filter is free if its a simple color change? + this.filterStack.push(filterBlock); + + var filter = filterBlock.filterPasses[0]; + + this.offsetX += filterBlock._filterArea.x; + this.offsetY += filterBlock._filterArea.y; + + var texture = this.texturePool.pop(); + if(!texture) + { + texture = new PIXI.FilterTexture(this.gl, this.width, this.height); + } + else + { + texture.resize(this.width, this.height); + } + + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; + + var padding = filter.padding; + filterArea.x -= padding; + filterArea.y -= padding; + filterArea.width += padding * 2; + filterArea.height += padding * 2; + + // cap filter to screen size.. + if(filterArea.x < 0)filterArea.x = 0; + if(filterArea.width > this.width)filterArea.width = this.width; + if(filterArea.y < 0)filterArea.y = 0; + if(filterArea.height > this.height)filterArea.height = this.height; + + //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); + + // set view port + gl.viewport(0, 0, filterArea.width, filterArea.height); + + projection.x = filterArea.width/2; + projection.y = -filterArea.height/2; + + offset.x = -filterArea.x; + offset.y = -filterArea.y; + + // update projection + // now restore the regular shader.. + this.renderSession.shaderManager.setShader(this.defaultShader); + gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); + gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); + + gl.colorMask(true, true, true, true); + gl.clearColor(0,0,0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + filterBlock._glFilterTexture = texture; + +}; + + +/** +* Removes the last filter from the filter stack and doesn't return it +* @method popFilter +*/ +PIXI.WebGLFilterManager.prototype.popFilter = function() +{ + var gl = this.gl; + var filterBlock = this.filterStack.pop(); + var filterArea = filterBlock._filterArea; + var texture = filterBlock._glFilterTexture; + var projection = this.renderSession.projection; + var offset = this.renderSession.offset; + + if(filterBlock.filterPasses.length > 1) + { + gl.viewport(0, 0, filterArea.width, 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); + outputTexture.resize(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, true);//this.transparent); + } + else + { + var currentFilter = this.filterStack[this.filterStack.length-1]; + filterArea = currentFilter._filterArea; + + sizeX = filterArea.width; + sizeY = filterArea.height; + + offsetX = filterArea.x; + offsetY = filterArea.y; + + buffer = currentFilter._glFilterTexture.frameBuffer; + } + + + + // TODO need toremove thease global elements.. + projection.x = sizeX/2; + projection.y = -sizeY/2; + + offset.x = offsetX; + offset.y = offsetY; + + filterArea = filterBlock._filterArea; + + var x = filterArea.x-offsetX; + var y = filterArea.y-offsetY; + + // update the buffers.. + // make sure to flip the y! + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = x; + this.vertexArray[1] = y + filterArea.height; + + this.vertexArray[2] = x + filterArea.width; + this.vertexArray[3] = y + filterArea.height; + + this.vertexArray[4] = x; + this.vertexArray[5] = y; + + this.vertexArray[6] = x + filterArea.width; + this.vertexArray[7] = y; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + //console.log(this.vertexArray) + //console.log(this.uvArray) + //console.log(sizeX + " : " + sizeY) + + 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.. + this.renderSession.shaderManager.setShader(this.defaultShader); + gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); + gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); + + // return the texture to the pool + this.texturePool.push(texture); + filterBlock._glFilterTexture = null; +}; + + +/** +* Applies the filter to the specified area +* @method applyFilterPass +* @param filter {AbstractFilter} the filter that needs to be applied +* @param filterArea {texture} TODO - might need an update +* @param width {Number} the horizontal range of the filter +* @param height {Number} the vertical range of the filter +*/ +PIXI.WebGLFilterManager.prototype.applyFilterPass = function(filter, filterArea, width, height) +{ + // use program + var gl = this.gl; + var shader = filter.shaders[gl.id]; + + if(!shader) + { + shader = new PIXI.PixiShader(gl); + + shader.fragmentSrc = filter.fragmentSrc; + shader.uniforms = filter.uniforms; + shader.init(); + + filter.shaders[gl.id] = shader; + } + + // set the shader + this.renderSession.shaderManager.setShader(shader); + +// gl.useProgram(shader.program); + + gl.uniform2f(shader.projectionVector, width/2, -height/2); + gl.uniform2f(shader.offsetVector, 0,0); + + if(filter.uniforms.dimensions) + { + filter.uniforms.dimensions.value[0] = this.width;//width; + filter.uniforms.dimensions.value[1] = this.height;//height; + filter.uniforms.dimensions.value[2] = this.vertexArray[0]; + filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; + } + + // console.log(this.uvArray ) + 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[i].destroy(); + } + + this.texturePool = null; + + //destroy buffers.. + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.uvBuffer); + gl.deleteBuffer(this.colorBuffer); + gl.deleteBuffer(this.indexBuffer); +}; + +/** + * @author Mat Groves http://matgroves.com/ @Doormat23 + */ + +/** +* @class FilterTexture +* @constructor +* @param gl {WebGLContext} the current WebGL drawing context +* @param width {Number} the horizontal range of the filter +* @param height {Number} the vertical range of the filter +* @param scaleMode {Number} Should be one of the PIXI.scaleMode consts +* @private +*/ +PIXI.FilterTexture = function(gl, width, height, scaleMode) +{ + /** + * @property gl + * @type WebGLContext + */ + this.gl = gl; + + // next time to create a frame buffer and texture + this.frameBuffer = gl.createFramebuffer(); + this.texture = gl.createTexture(); + + scaleMode = scaleMode || PIXI.scaleModes.DEFAULT; + + gl.bindTexture(gl.TEXTURE_2D, this.texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer ); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer ); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0); + + // required for masking a mask?? + this.renderBuffer = gl.createRenderbuffer(); + gl.bindRenderbuffer(gl.RENDERBUFFER, this.renderBuffer); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, this.renderBuffer); + + this.resize(width, height); +}; + + +/** +* Clears the filter texture +* @method clear +*/ +PIXI.FilterTexture.prototype.clear = function() +{ + var gl = this.gl; + + gl.clearColor(0,0,0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); +}; + +/** + * Resizes the texture to the specified width and height + * + * @method resize + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ +PIXI.FilterTexture.prototype.resize = function(width, height) +{ + if(this.width === width && this.height === height) return; + + this.width = width; + this.height = height; + + var gl = this.gl; + + gl.bindTexture(gl.TEXTURE_2D, this.texture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + + // update the stencil buffer width and height + gl.bindRenderbuffer(gl.RENDERBUFFER, this.renderBuffer); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, width, height); +}; + +/** +* Destroys the filter texture +* @method destroy +*/ +PIXI.FilterTexture.prototype.destroy = function() +{ + var gl = this.gl; + gl.deleteFramebuffer( this.frameBuffer ); + gl.deleteTexture( this.texture ); + + this.frameBuffer = null; + this.texture = null; +}; + +/** + * @author Mat Groves + * + * + */ +/** + * 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) +{ + if(!PIXI.defaultRenderer) + { + PIXI.sayHello("Canvas"); + 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; + + /** + * 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 true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + */ + roundPixels: false + }; + + 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 (navigator.isCocoonJS && this.view.screencanvas) { + this.context.fillStyle = "black"; + this.context.clear(); + } + + 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(); + } + } + else if (data.type === PIXI.Graphics.RREC) + { + var rx = points[0]; + var ry = points[1]; + var width = points[2]; + var height = points[3]; + var radius = points[4]; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if(data.fillColor || data.fillColor === 0) + { + 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(); + } + else if (data.type === PIXI.Graphics.RREC) + { + var rx = points[0]; + var ry = points[1]; + var width = points[2]; + var height = points[3]; + var radius = points[4]; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + 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 boundsPadding + * @type Number + */ + this.boundsPadding = 10; + + /** + * Used to detect if the graphics object has changed if this is set to true then the graphics object will be recalculated + * + * @type {Boolean} + */ + this.dirty = true; +}; + +// 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; +}; + +/** + * Calculate the points for a quadratic bezier curve. + * Based on : https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @method quadraticCurveTo + * @param {number} cpX Control point x + * @param {number} cpY Control point y + * @param {number} toX Destination point x + * @param {number} toY Destination point y + * @return {PIXI.Graphics} + */ +PIXI.Graphics.prototype.quadraticCurveTo = function(cpX, cpY, toX, toY) +{ + if( this.currentPath.points.length === 0)this.moveTo(0,0); + + var xa, + ya, + n = 20, + points = this.currentPath.points; + if(points.length === 0)this.moveTo(0, 0); + + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; i++ ) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve. + * + * @method bezierCurveTo + * @param {number} cpX Control point x + * @param {number} cpY Control point y + * @param {number} cpX2 Second Control point x + * @param {number} cpY2 Second Control point y + * @param {number} toX Destination point x + * @param {number} toY Destination point y + * @return {PIXI.Graphics} + */ +PIXI.Graphics.prototype.bezierCurveTo = function(cpX, cpY, cpX2, cpY2, toX, toY) +{ + if( this.currentPath.points.length === 0)this.moveTo(0,0); + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i=1; i b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc() method creates an arc/curve (used to create circles, or parts of circles). + * + * @method arc + * @param {number} cx The x-coordinate of the center of the circle + * @param {number} cy The y-coordinate of the center of the circle + * @param {number} radius The radius of the circle + * @param {number} startAngle The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param {number} endAngle The ending angle, in radians + * @param {number} anticlockwise Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} + */ +PIXI.Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + var points = this.currentPath.points; + + if(points.length !== 0 && points[points.length-2] !== startX || points[points.length-1] !== startY) + { + this.moveTo(startX, startY); + points = this.currentPath.points; + } + + if (startAngle === endAngle)return this; + + if( !anticlockwise && endAngle <= startAngle ) + { + endAngle += Math.PI * 2; + } + else if( anticlockwise && startAngle <= endAngle ) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = ( Math.abs(sweep)/ (Math.PI * 2) ) * 40; + + if( sweep === 0 ) return this; + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for(var i=0; i<=segMinus; i++) + { + var real = i + remainder * i; + + + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + 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.drawPath = function(path) +{ + 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.graphicsData.push(this.currentPath); + + this.currentPath.points = this.currentPath.points.concat(path); + 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; +}; + +/** + * @method drawRoundedRect + * + * @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 + * @param radius {Number} Radius of the rectangle corners + */ +PIXI.Graphics.prototype.drawRoundedRect = function( x, y, width, height, 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, width, height, radius], type:PIXI.Graphics.RREC}; + + 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 center of the ellipse + * @param y {Number} The Y coordinate of the center of the ellipse + * @param width {Number} The half width of the ellipse + * @param height {Number} The half 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; + } + + this._cachedSprite.alpha = this.alpha; + PIXI.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + + return; + } + else + { + renderSession.spriteBatch.stop(); + renderSession.blendModeManager.setBlendMode(this.blendMode); + + 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]); + } + + // for (var i = this.graphicsData.length - 1; i >= 0; i--) { + // this.graphicsData[i] + +// }; + + 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 ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + 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.alpha = this.alpha; + + // 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; +PIXI.Graphics.RREC = 4; + + +/** + * @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) +{ + PIXI.DisplayObjectContainer.call( this ); + + this.texture = texture; + + // set up the main bits.. + this.uvs = new PIXI.Float32Array([0, 1, + 1, 1, + 1, 0, + 0,1]); + + this.verticies = new PIXI.Float32Array([0, 0, + 100,0, + 100,100, + 0, 100]); + + this.colors = new PIXI.Float32Array([1, 1, 1, 1]); + + this.indices = new PIXI.Uint16Array([0, 1, 2, 3]); + + + this.dirty = true; +}; + +// constructor +PIXI.Strip.prototype = Object.create(PIXI.DisplayObjectContainer.prototype); +PIXI.Strip.prototype.constructor = PIXI.Strip; + +PIXI.Strip.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; + // render triangle strip.. + + renderSession.spriteBatch.stop(); + + // init! init! + if(!this._vertexBuffer)this._initWebGL(renderSession); + + renderSession.shaderManager.setShader(renderSession.shaderManager.stripShader); + + this._renderStrip(renderSession); + + ///renderSession.shaderManager.activateDefaultShader(); + + renderSession.spriteBatch.start(); + + //TODO check culling +}; + +PIXI.Strip.prototype._initWebGL = function(renderSession) +{ + // build the strip! + var gl = renderSession.gl; + + this._vertexBuffer = gl.createBuffer(); + this._indexBuffer = gl.createBuffer(); + this._uvBuffer = gl.createBuffer(); + this._colorBuffer = gl.createBuffer(); + + gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.verticies, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.STATIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this._colorBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.colors, gl.STATIC_DRAW); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); +}; + +PIXI.Strip.prototype._renderStrip = function(renderSession) +{ + var gl = renderSession.gl; + var projection = renderSession.projection, + offset = renderSession.offset, + shader = renderSession.shaderManager.stripShader; + + + // gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mat4Real); + + gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + + // set uniforms + gl.uniformMatrix3fv(shader.translationMatrix, false, this.worldTransform.toArray(true)); + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + gl.uniform1f(shader.alpha, 1); + + if(!this.dirty) + { + + gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer); + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.verticies); + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + // update the uvs + gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer); + gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.activeTexture(gl.TEXTURE0); + // bind the current texture + gl.bindTexture(gl.TEXTURE_2D, this.texture.baseTexture._glTextures[gl.id] || PIXI.createWebGLTexture(this.texture.baseTexture, gl)); + + // dont need to upload! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer); + + + } + else + { + + this.dirty = false; + gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.verticies, gl.STATIC_DRAW); + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + // update the uvs + gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.STATIC_DRAW); + gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.texture.baseTexture._glTextures[gl.id] || PIXI.createWebGLTexture(this.texture.baseTexture, gl)); + + // dont need to upload! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); + + } + //console.log(gl.TRIANGLE_STRIP) + // + // + gl.drawElements(gl.TRIANGLE_STRIP, this.indices.length, gl.UNSIGNED_SHORT, 0); + + +}; + +PIXI.Strip.prototype._renderCanvas = function(renderSession) +{ + var context = renderSession.context; + + var transform = this.worldTransform; + + if (renderSession.roundPixels) + { + context.setTransform(transform.a, transform.c, transform.b, transform.d, transform.tx | 0, transform.ty | 0); + } + else + { + context.setTransform(transform.a, transform.c, transform.b, transform.d, transform.tx, transform.ty); + } + + var strip = this; + // draw triangles!! + var verticies = strip.verticies; + var uvs = strip.uvs; + + var length = verticies.length/2; + this.count++; + + for (var i = 0; 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]; + + if(true) + { + + //expand(); + var centerX = (x0 + x1 + x2)/3; + var centerY = (y0 + y1 + y2)/3; + + var normX = x0 - centerX; + var normY = y0 - centerY; + + var dist = Math.sqrt( normX * normX + normY * normY ); + x0 = centerX + (normX / dist) * (dist + 3); + y0 = centerY + (normY / dist) * (dist + 3); + + // + + normX = x1 - centerX; + normY = y1 - centerY; + + dist = Math.sqrt( normX * normX + normY * normY ); + x1 = centerX + (normX / dist) * (dist + 3); + y1 = centerY + (normY / dist) * (dist + 3); + + normX = x2 - centerX; + normY = y2 - centerY; + + dist = Math.sqrt( normX * normX + normY * normY ); + x2 = centerX + (normX / dist) * (dist + 3); + y2 = centerY + (normY / dist) * (dist + 3); + + } + + 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(); + } +}; + +/* + * 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; + + this.verticies = new PIXI.Float32Array(points.length * 4); + this.uvs = new PIXI.Float32Array(points.length * 4); + this.colors = new PIXI.Float32Array(points.length * 2); + this.indices = new PIXI.Uint16Array(points.length * 2); + + + this.refresh(); +}; + + +// constructor +PIXI.Rope.prototype = Object.create( PIXI.Strip.prototype ); +PIXI.Rope.prototype.constructor = PIXI.Rope; + +/* + * Refreshes + * + * @method refresh + */ +PIXI.Rope.prototype.refresh = function() +{ + var points = this.points; + if(points.length < 1) return; + + var uvs = this.uvs; + + var lastPoint = points[0]; + var indices = this.indices; + var colors = this.colors; + + this.count-=0.2; + + uvs[0] = 0; + uvs[1] = 0; + uvs[2] = 0; + uvs[3] = 1; + + colors[0] = 1; + colors[1] = 1; + + indices[0] = 0; + indices[1] = 1; + + var total = points.length, + point, index, amount; + + for (var i = 1; i < total; i++) + { + point = points[i]; + index = i * 4; + // time to do some smart drawing! + amount = i / (total-1); + + if(i%2) + { + uvs[index] = amount; + uvs[index+1] = 0; + + uvs[index+2] = amount; + uvs[index+3] = 1; + } + else + { + uvs[index] = amount; + uvs[index+1] = 0; + + uvs[index+2] = amount; + uvs[index+3] = 1; + } + + index = i * 2; + colors[index] = 1; + colors[index+1] = 1; + + index = i * 2; + indices[index] = index; + indices[index + 1] = index + 1; + + lastPoint = point; + } +}; + +/* + * Updates the object transform for rendering + * + * @method updateTransform + * @private + */ +PIXI.Rope.prototype.updateTransform = function() +{ + + var points = this.points; + if(points.length < 1)return; + + var lastPoint = points[0]; + var nextPoint; + var perp = {x:0, y:0}; + + this.count-=0.2; + + var verticies = this.verticies; + var total = points.length, + point, index, ratio, perpLength, num; + + for (var i = 0; i < total; i++) + { + point = points[i]; + index = i * 4; + + if(i < points.length-1) + { + nextPoint = points[i+1]; + } + else + { + nextPoint = point; + } + + perp.y = -(nextPoint.x - lastPoint.x); + perp.x = nextPoint.y - lastPoint.y; + + ratio = (1 - (i / (total-1))) * 10; + + if(ratio > 1) ratio = 1; + + perpLength = Math.sqrt(perp.x * perp.x + perp.y * perp.y); + num = this.texture.height / 2; //(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio; + perp.x /= perpLength; + perp.y /= perpLength; + + perp.x *= num; + perp.y *= num; + + 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 Sprite + * @constructor + * @param texture {Texture} the texture of the tiling sprite + * @param width {Number} the width of the tiling sprite + * @param height {Number} the height of the tiling sprite + */ +PIXI.TilingSprite = function(texture, width, height) +{ + PIXI.Sprite.call( this, texture); + + /** + * The 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; + } +}); + +PIXI.TilingSprite.prototype.setTexture = function(texture) +{ + if (this.texture === texture) return; + + this.texture = texture; + + this.refreshTexture = true; + + this.cachedTint = 0xFFFFFF; +}; + +/** +* Renders the object using the WebGL renderer +* +* @method _renderWebGL +* @param renderSession {RenderSession} +* @private +*/ +PIXI.TilingSprite.prototype._renderWebGL = function(renderSession) +{ + if (this.visible === false || this.alpha === 0) return; + var i,j; + + 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.refreshTexture) + { + this.generateTilingTexture(true); + + if (this.tilingTexture && this.tilingTexture.needsUpdate) + { + //TODO - tweaking + PIXI.updateWebGLTexture(this.tilingTexture.baseTexture, renderSession.gl); + this.tilingTexture.needsUpdate = false; + // this.tilingTexture._uvs = null; + } + } + 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; +}; + + + +/** + * When the texture is updated, this event will fire to update the scale and frame + * + * @method onTextureUpdate + * @param event + * @private + */ +PIXI.TilingSprite.prototype.onTextureUpdate = function() +{ + // overriding the sprite version of this! +}; + + +/** +* +* @method generateTilingTexture +* +* @param forcePowerOfTwo {Boolean} Whether we want to force the texture to be a power of two +*/ +PIXI.TilingSprite.prototype.generateTilingTexture = function(forcePowerOfTwo) +{ + if (!this.texture.baseTexture.hasLoaded) return; + + var texture = this.texture; + var frame = texture.frame; + var targetWidth, targetHeight; + + // Check that the frame is the same size as the base texture. + var isFrame = frame.width !== texture.baseTexture.width || frame.height !== texture.baseTexture.height; + + var newTextureRequired = false; + + if (!forcePowerOfTwo) + { + if (isFrame) + { + targetWidth = frame.width; + targetHeight = frame.height; + + newTextureRequired = true; + } + } + else + { + targetWidth = PIXI.getNextPowerOfTwo(frame.width); + targetHeight = PIXI.getNextPowerOfTwo(frame.height); + + if (frame.width !== targetWidth || frame.height !== targetHeight) newTextureRequired = true; + } + + if (newTextureRequired) + { + var canvasBuffer; + + if (this.tilingTexture && this.tilingTexture.isTiling) + { + canvasBuffer = this.tilingTexture.canvasBuffer; + canvasBuffer.resize(targetWidth, targetHeight); + this.tilingTexture.baseTexture.width = targetWidth; + this.tilingTexture.baseTexture.height = targetHeight; + this.tilingTexture.needsUpdate = true; + } + else + { + canvasBuffer = new PIXI.CanvasBuffer(targetWidth, targetHeight); + + this.tilingTexture = PIXI.Texture.fromCanvas(canvasBuffer.canvas); + this.tilingTexture.canvasBuffer = canvasBuffer; + this.tilingTexture.isTiling = true; + } + + canvasBuffer.context.drawImage(texture.baseTexture.source, + texture.crop.x, + texture.crop.y, + texture.crop.width, + texture.crop.height, + 0, + 0, + targetWidth, + targetHeight); + + this.tileScaleOffset.x = frame.width / targetWidth; + this.tileScaleOffset.y = frame.height / targetHeight; + } + else + { + // TODO - switching? + if (this.tilingTexture && this.tilingTexture.isTiling) + { + // destroy the tiling texture! + // TODO could store this somewhere? + this.tilingTexture.destroy(true); + } + + this.tileScaleOffset.x = 1; + this.tileScaleOffset.y = 1; + this.tilingTexture = texture; + } + + this.refreshTexture = false; + 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 / 5; + }, + setFrame: function (frameIndex, time, r, g, b, a) { + 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 = { + animationSpeed: 1, + current: null, + previous: null, + currentTime: 0, + previousTime: 0, + currentLoop: false, + previousLoop: false, + mixTime: 0, + mixDuration: 0, + update: function (delta) { + this.currentTime += (delta * this.animationSpeed); //timeScale: Multiply delta by the speed of animation required. + 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.substr(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); + + slotContainer.alpha = slot.a; + slot.currentSprite.tint = PIXI.rgb2hex([slot.r,slot.g,slot.b]); + } + + 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; + + //TODO will be used for futer pixi 1.5... + this.id = PIXI.BaseTextureCacheIdGenerator++; + + /** + * Controls if RGB channels should be premultiplied by Alpha (WebGL only) + * + * @property + * @type Boolean + * @default TRUE + */ + this.premultipliedAlpha = true; + + // used for webGL + this._glTextures = []; + + // used for webGL teture updateing... + this._dirty = []; + + if(!source)return; + + if((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + 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; + + for (var i = 0; i < scope._glTextures.length; i++) + { + scope._dirty[i] = true; + } + + // add it to somewhere... + scope.dispatchEvent( { type: 'loaded', content: scope } ); + }; + this.source.onerror = function() { + scope.dispatchEvent( { type: 'error', content: scope } ); + }; + } + + this.imageUrl = null; + this._powerOf2 = false; + + + +}; + +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]; + delete PIXI.TextureCache[this.imageUrl]; + this.imageUrl = null; + this.source.src = null; + } + else if (this.source && this.source._pixiId) + { + delete PIXI.BaseTextureCache[this.source._pixiId]; + } + 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]; + + if(crossorigin === undefined && imageUrl.indexOf('data:') === -1) crossorigin = true; + + if(!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//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; +}; + +/** + * Helper function that returns a base texture based on a canvas element + * If the image is not in the base texture cache it will be created and loaded + * + * @static + * @method fromCanvas + * @param canvas {Canvas} The canvas element source of the texture + * @param scaleMode {Number} Should be one of the PIXI.scaleMode consts + * @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 ); + + /** + * Does this Texture have any frame data assigned to it? + * + * @property noFrame + * @type Boolean + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new PIXI.Rectangle(0,0,1,1); + } + + if (baseTexture instanceof PIXI.Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @property baseTexture + * @type BaseTexture + */ + this.baseTexture = baseTexture; + + /** + * The frame specifies the region of the base texture that this texture uses + * + * @property frame + * @type Rectangle + */ + this.frame = frame; + + /** + * The trim point + * + * @property trim + * @type Rectangle + */ + this.trim = null; + + /** + * This will let the renderer know if the texture is valid. If its not then it cannot be rendered. + * + * @property valid + * @type Boolean + */ + this.valid = false; + + /** + * The context scope under which events are run. + * + * @property scope + * @type Object + */ + this.scope = this; + + /** + * The WebGL UV data cache. + * + * @private + * @property _uvs + * @type Object + */ + this._uvs = null; + + /** + * The width of the Texture in pixels. + * + * @property width + * @type Number + */ + this.width = 0; + + /** + * The height of the Texture in pixels. + * + * @property height + * @type Number + */ + this.height = 0; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @property crop + * @type Rectangle + */ + this.crop = new PIXI.Rectangle(0, 0, 1, 1); + + if (baseTexture.hasLoaded) + { + if (this.noFrame) frame = new PIXI.Rectangle(0, 0, baseTexture.width, baseTexture.height); + this.setFrame(frame); + } + 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(); + + this.valid = false; +}; + +/** + * Specifies the region of the baseTexture that this texture will use. + * + * @method setFrame + * @param frame {Rectangle} The frame of the texture to set it to + */ +PIXI.Texture.prototype.setFrame = function(frame) +{ + this.noFrame = false; + + this.frame = frame; + this.width = frame.width; + this.height = frame.height; + + this.crop.x = frame.x; + this.crop.y = frame.y; + this.crop.width = frame.width; + this.crop.height = frame.height; + + if (!this.trim && (frame.x + frame.width > this.baseTexture.width || frame.y + frame.height > this.baseTexture.height)) + { + throw new Error('Texture Error: frame does not fit inside the base Texture dimensions ' + this); + } + + this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; + + if (this.trim) + { + this.width = this.trim.width; + this.height = this.trim.height; + this.frame.width = this.trim.width; + this.frame.height = this.trim.height; + } + + if (this.valid) PIXI.Texture.frameUpdates.push(this); + +}; + +/** + * Updates the internal WebGL UV cache. + * + * @method _updateWebGLuvs + * @private + */ +PIXI.Texture.prototype._updateWebGLuvs = function() +{ + if(!this._uvs)this._uvs = new PIXI.TextureUvs(); + + var frame = this.crop; + var tw = this.baseTexture.width; + var th = this.baseTexture.height; + + this._uvs.x0 = frame.x / tw; + this._uvs.y0 = frame.y / th; + + this._uvs.x1 = (frame.x + frame.width) / tw; + this._uvs.y1 = frame.y / th; + + this._uvs.x2 = (frame.x + frame.width) / tw; + this._uvs.y2 = (frame.y + frame.height) / th; + + this._uvs.x3 = frame.x / tw; + this._uvs.y3 = (frame.y + frame.height) / th; + +}; + +/** + * Helper function that 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 + * @param scaleMode {Number} Should be one of the PIXI.scaleMode consts + * @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 + * @param scaleMode {Number} Should be one of the PIXI.scaleMode consts + * @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]; + delete PIXI.TextureCache[id]; + delete PIXI.BaseTextureCache[id]; + 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.y3 = 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 + * @param scaleMode {Number} Should be one of the PIXI.scaleMode consts + */ +PIXI.RenderTexture = function(width, height, renderer, scaleMode) +{ + 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); + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @property crop + * @type Rectangle + */ + this.crop = new PIXI.Rectangle(0, 0, this.width, this.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.scaleMode = scaleMode || PIXI.scaleModes.DEFAULT; + + 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.scaleMode); + 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; + } + + this.valid = true; + PIXI.Texture.frameUpdates.push(this); + + +}; + +PIXI.RenderTexture.prototype = Object.create(PIXI.Texture.prototype); +PIXI.RenderTexture.prototype.constructor = PIXI.RenderTexture; + +/** + * Resize the RenderTexture. + * + * @method resize + * @param width {Number} The width to resize to. + * @param height {Number} The height to resize to. + * @param updateBase {Boolean} Should the baseTexture.width and height values be resized as well? + */ +PIXI.RenderTexture.prototype.resize = function(width, height, updateBase) +{ + if (width === this.width && height === this.height) + { + return; + } + + this.width = this.frame.width = this.crop.width = width; + this.height = this.frame.height = this.crop.height = height; + + if (updateBase) + { + this.baseTexture.width = this.width; + this.baseTexture.height = this.height; + } + + if (this.renderer.type === PIXI.WEBGL_RENDERER) + { + this.projection.x = this.width / 2; + this.projection.y = -this.height / 2; + } + + this.textureBuffer.resize(this.width, this.height); +}; + +/** + * Clears the RenderTexture. + * + * @method clear + */ +PIXI.RenderTexture.prototype.clear = function() +{ + if (this.renderer.type === PIXI.WEBGL_RENDERER) + { + this.renderer.gl.bindFramebuffer(this.renderer.gl.FRAMEBUFFER, this.textureBuffer.frameBuffer); + } + + this.textureBuffer.clear(); +}; + +/** + * This function will draw the display object to the texture. + * + * @method renderWebGL + * @param displayObject {DisplayObject} The display object to render this texture on + * @param 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, + 'webp': 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.content); + } + + 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 () { + + var scope = this; + + if(window.XDomainRequest && scope.crossorigin) + { + this.ajaxRequest = new window.XDomainRequest(); + + // XDomainRequest has a few querks. Occasionally it will abort requests + // A way to avoid this is to make sure ALL callbacks are set even if not used + // More info here: http://stackoverflow.com/questions/15786966/xdomainrequest-aborts-post-on-ie-9 + this.ajaxRequest.timeout = 3000; + + this.ajaxRequest.onerror = function () { + scope.onError(); + }; + + this.ajaxRequest.ontimeout = function () { + scope.onError(); + }; + + this.ajaxRequest.onprogress = function() {}; + + } + else if (window.XMLHttpRequest) + { + this.ajaxRequest = new window.XMLHttpRequest(); + } + else + { + this.ajaxRequest = new window.ActiveXObject('Microsoft.XMLHTTP'); + } + + + + this.ajaxRequest.onload = function(){ + + scope.onJSONLoaded(); + }; + + this.ajaxRequest.open('GET',this.url,true); + + this.ajaxRequest.send(); +}; + +/** + * Invoke when JSON file is loaded + * + * @method onJSONLoaded + * @private + */ +PIXI.JsonLoader.prototype.onJSONLoaded = function () { + + if(!this.ajaxRequest.responseText ) + { + this.onError(); + return; + } + + 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 + }); + + PIXI.TextureCache[i].crop = new PIXI.Rectangle(rect.x, rect.y, rect.w, rect.h); + + // Check to see if the sprite is trimmed + if (frameData[i].trimmed) + { + var actualSize = frameData[i].sourceSize; + var realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].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(); + } +}; + +/** + * 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 + + + + + + + + + + + From 432af40849449d3ea0ebc5104887ed6e55cf29e7 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Mon, 13 Oct 2014 18:19:18 -0500 Subject: [PATCH 006/110] feature added velocity-verlet integrator --- src/integrators/velocity-verlet.js | 155 +++++++++++++++++++++++++++++ src/integrators/verlet.js | 39 ++++---- test/integrator-sandbox.html | 31 +++--- 3 files changed, 194 insertions(+), 31 deletions(-) create mode 100644 src/integrators/velocity-verlet.js diff --git a/src/integrators/velocity-verlet.js b/src/integrators/velocity-verlet.js new file mode 100644 index 00000000..e5fffe65 --- /dev/null +++ b/src/integrators/velocity-verlet.js @@ -0,0 +1,155 @@ +Physics.integrator('velocity-verlet', function( parent ){ + + // for this integrator we need to know if the object has been integrated before + // so let's add a mixin to bodies + + Physics.body.mixin({ + + started: function( val ){ + if ( val !== undefined ){ + this._started = true; + } + + return !!this._started; + } + }); + + + return { + /** + * class VelocityVerlet < Integrator + * + * `Physics.integrator('velocity-verlet')`. + * + * The velocity-verlet integrator. + **/ + + // extended + init: function( options ){ + + // call parent init + parent.init.call(this, options); + }, + + // extended + integrateVelocities: function( bodies, dt ){ + + // half the timestep + var dtdt = dt * dt + ,drag = 1 - this.options.drag + ,body = null + ,state + ; + + for ( var i = 0, l = bodies.length; i < l; ++i ){ + + body = bodies[ i ]; + state = body.state; + + // only integrate if the body isn't static + if ( body.treatment !== 'static' ){ + + // v = v_prev + 0.5 * (a_prev + a) * dt + // x = x_prev + v_prev * dt + 0.5 * a_prev * dt * dt + + // use the velocity in vel if the velocity has been changed manually + if ( !body.started() ){ + + // Set old vals on first integration + state.old.acc.clone( state.acc ); + state.old.acc.mult( dt ); + state.old.vel.clone( state.vel ).vsub( state.old.acc ); + state.old.acc.mult( 1/dt ); + } + + // Apply "air resistance". + if ( drag ){ + + state.vel.mult( drag ); + } + + // Apply acceleration + // v += 0.5 * (a_prev + a) * dt + state.vel.vadd( state.old.acc.vadd( state.acc ).mult( 0.5 * dt ) ); + + // Reset accel + state.acc.zero(); + + // + // Angular components + // + + if ( !body.started() ){ + + // Set old vals on first integration + state.old.angular.acc = state.angular.acc; + state.old.angular.vel = state.angular.vel - state.old.angular.acc * dt; + } + + state.angular.vel += 0.5 * (state.angular.acc + state.old.angular.acc) * dtdt; + state.angular.acc = 0; + + body.started( true ); + + } else { + // set the velocity and acceleration to zero! + state.vel.zero(); + state.acc.zero(); + state.angular.vel = 0; + state.angular.acc = 0; + } + } + }, + + // extended + integratePositions: function( bodies, dt ){ + + // half the timestep + var dtdt = dt * dt + ,body = null + ,state + ; + + for ( var i = 0, l = bodies.length; i < l; ++i ){ + + body = bodies[ i ]; + state = body.state; + + // only integrate if the body isn't static + if ( body.treatment !== 'static' ){ + + // x = x_prev + v_prev * dt + 0.5 * a_prev * dt * dt + + // Store old position. + // xold = x + state.old.pos.clone( state.pos ); + + state.old.vel.mult( dt ); + state.old.acc.mult( 0.5 * dtdt ) + state.pos.vadd( state.old.vel ).vadd( state.old.acc ); + + // store calculated velocity + state.old.vel.clone( state.vel ); + + // store old acc + state.old.acc.clone( state.acc ); + + // Reset accel + state.acc.zero(); + + // + // Angular components + // + + + state.old.angular.pos = state.angular.pos; + + state.angular.pos += state.angular.vel * dt + 0.5 * state.old.angular.acc * dtdt; + state.old.angular.vel = state.angular.vel; + state.old.angular.acc = state.angular.acc; + state.angular.acc = 0; + } + } + } + }; +}); diff --git a/src/integrators/verlet.js b/src/integrators/verlet.js index 628923d5..47ca2019 100644 --- a/src/integrators/verlet.js +++ b/src/integrators/verlet.js @@ -16,7 +16,7 @@ Physics.integrator('verlet', function( parent ){ return { - /** + /** * class Verlet < Integrator * * `Physics.integrator('verlet')`. @@ -39,6 +39,8 @@ Physics.integrator('verlet', function( parent ){ ,drag = 1 - this.options.drag ,body = null ,state + ,prevDt = this.prevDt || dt + ,dtMul = (dtdt + dt * prevDt) * 0.5 ; for ( var i = 0, l = bodies.length; i < l; ++i ){ @@ -51,20 +53,20 @@ Physics.integrator('verlet', function( parent ){ // Inspired from https://github.com/soulwire/Coffee-Physics // @licence MIT - // + // // v = x - ox // x = x + (v + a * dt * dt) // use the velocity in vel if the velocity has been changed manually if (state.vel.equals( state.old.vel ) && body.started()){ - + // Get velocity by subtracting old position from curr position state.vel.clone( state.pos ).vsub( state.old.pos ); } else { state.old.pos.clone( state.pos ).vsub( state.vel ); - // so we need to scale the value by dt so it + // so we need to scale the value by dt so it // complies with other integration methods state.vel.mult( dt ); } @@ -77,9 +79,9 @@ Physics.integrator('verlet', function( parent ){ // Apply acceleration // v += a * dt * dt - state.vel.vadd( state.acc.mult( dtdt ) ); + state.vel.vadd( state.acc.mult( dtMul ) ); - // normalize velocity + // normalize velocity state.vel.mult( 1/dt ); // store calculated velocity @@ -90,7 +92,7 @@ Physics.integrator('verlet', function( parent ){ // // Angular components - // + // if (state.angular.vel === state.old.angular.vel && body.started()){ @@ -102,7 +104,7 @@ Physics.integrator('verlet', function( parent ){ state.angular.vel *= dt; } - state.angular.vel += state.angular.acc * dtdt; + state.angular.vel += state.angular.acc * dtMul; state.angular.vel /= dt; state.old.angular.vel = state.angular.vel; state.angular.acc = 0; @@ -126,6 +128,8 @@ Physics.integrator('verlet', function( parent ){ var dtdt = dt * dt ,body = null ,state + ,prevDt = this.prevDt || dt + ,dtcorr = dt/prevDt ; for ( var i = 0, l = bodies.length; i < l; ++i ){ @@ -136,17 +140,17 @@ Physics.integrator('verlet', function( parent ){ // only integrate if the body isn't static if ( body.treatment !== 'static' ){ - // so we need to scale the value by dt so it + // so we need to scale the value by dt so it // complies with other integration methods - state.vel.mult( dt ); - + state.vel.mult( dt * dtcorr ); + // Store old position. // xold = x state.old.pos.clone( state.pos ); state.pos.vadd( state.vel ); - // normalize velocity + // normalize velocity state.vel.mult( 1/dt ); // store calculated velocity @@ -154,11 +158,11 @@ Physics.integrator('verlet', function( parent ){ // // Angular components - // + // + + + state.angular.vel *= dt * dtcorr; - - state.angular.vel *= dt; - state.old.angular.pos = state.angular.pos; state.angular.pos += state.angular.vel; @@ -166,7 +170,8 @@ Physics.integrator('verlet', function( parent ){ state.old.angular.vel = state.angular.vel; } } + + this.prevDt = dt; } }; }); - diff --git a/test/integrator-sandbox.html b/test/integrator-sandbox.html index 2f49d73f..5d3ad1e0 100644 --- a/test/integrator-sandbox.html +++ b/test/integrator-sandbox.html @@ -47,7 +47,7 @@ }); - Physics({timestep: 2, maxIPF: 80},function (world) { + Physics({timestep: 3, maxIPF: 24},function (world) { var viewWidth = parent.innerWidth ,viewHeight = parent.innerHeight @@ -84,7 +84,7 @@ world.render(); }); - // world.add( Physics.integrator('improved-euler') ); + world.add( Physics.integrator('velocity-verlet') ); // constrain objects to these bounds edgeBounce = Physics.behavior('edge-collision-detection', { @@ -109,7 +109,7 @@ world.add( Physics.body('circle', { x: viewWidth/2, - y: viewHeight/2+100, + y: viewHeight - 20,//+100, radius: 20, width: 40, height: 40, @@ -123,16 +123,16 @@ var center = Physics.vector({x: viewWidth/2, y: viewHeight/2 }); world.add([ - Physics.behavior('spring', { k: 0.00001, center: center, body: ball }) - // Physics.behavior('body-collision-detection', { - // checkAll: false - // }), - // Physics.behavior('sweep-prune'), - // Physics.behavior('body-impulse-response'), - // // add gravity - // Physics.behavior('constant-acceleration'), - // Physics.behavior('interactive', {el:renderer.el}), - // edgeBounce + // Physics.behavior('spring', { k: 0.00001, center: center, body: ball }) + Physics.behavior('body-collision-detection', { + checkAll: false + }), + Physics.behavior('sweep-prune'), + Physics.behavior('body-impulse-response'), + // add gravity + Physics.behavior('constant-acceleration'), + Physics.behavior('interactive', {el:renderer.el}), + edgeBounce ]); world.on('render', function(meta){ @@ -163,7 +163,10 @@ renderer.drawCircle(x + center.x, y + center.y, 0.5, '#f66', this.ctx); // graph.endFill(); }; - + // world.timestep( 20 ); + // setInterval(function(){ + // world.timestep(world.timestep() + 20 * (0.5 - Math.random())) + // }, 1000) }); From 3d0130f4a3b2f6bf0aa398770d26f583ae14074a Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Tue, 14 Oct 2014 16:03:17 -0500 Subject: [PATCH 007/110] bug fix: multiple calls to ticker.start fixed. ticker starts automatically --- src/util/ticker.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/util/ticker.js b/src/util/ticker.js index f2231d51..3ce47d29 100644 --- a/src/util/ticker.js +++ b/src/util/ticker.js @@ -7,7 +7,7 @@ **/ (function(window){ - var active = false + var active = true ,ps = Physics.util.pubsub() ,perf = window.performance ; @@ -29,6 +29,8 @@ var time; + window.requestAnimationFrame( step ); + if (!active){ return; } @@ -39,10 +41,12 @@ return; } - window.requestAnimationFrame( step ); ps.emit( 'tick', time ); } + // start stepping + step(); + /** * Physics.util.ticker.start() -> this * @@ -51,7 +55,6 @@ function start(){ active = true; - step(); return this; } From 0abacca98a24d453fa716a6db85567c29ad478c6 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Wed, 12 Nov 2014 15:40:32 -0500 Subject: [PATCH 008/110] re issue #42. remember previous contacts --- src/behaviors/body-collision-detection.js | 20 +++++++++++ src/behaviors/body-impulse-response.js | 41 ++++++++++++----------- src/behaviors/edge-collision-detection.js | 35 +++++++++++++------ 3 files changed, 66 insertions(+), 30 deletions(-) diff --git a/src/behaviors/body-collision-detection.js b/src/behaviors/body-collision-detection.js index fb026d99..44595ce9 100644 --- a/src/behaviors/body-collision-detection.js +++ b/src/behaviors/body-collision-detection.js @@ -289,6 +289,10 @@ Physics.behavior('body-collision-detection', function( parent ){ ,targets = this.getTargets() ,collisions = [] ,ret + ,prevContacts = this.prevContacts || {} + ,contactList = {} + ,pairHash = Physics.util.pairHash + ,hash ; for ( var i = 0, l = candidates.length; i < l; ++i ){ @@ -303,11 +307,17 @@ Physics.behavior('body-collision-detection', function( parent ){ ret = checkPair( pair.bodyA, pair.bodyB ); if ( ret ){ + hash = pairHash( pair.bodyA.uid, pair.bodyB.uid ); + contactList[ hash ] = true; + ret.collidedPreviously = prevContacts[ hash ]; + collisions.push( ret ); } } } + this.prevContacts = contactList; + if ( collisions.length ){ this._world.emit( this.options.channel, { @@ -330,6 +340,10 @@ Physics.behavior('body-collision-detection', function( parent ){ ,bodyB ,collisions = [] ,ret + ,prevContacts = this.prevContacts || {} + ,contactList = {} + ,pairHash = Physics.util.pairHash + ,hash ; for ( var j = 0, l = bodies.length; j < l; j++ ){ @@ -343,11 +357,17 @@ Physics.behavior('body-collision-detection', function( parent ){ ret = checkPair( bodyA, bodyB ); if ( ret ){ + hash = pairHash( bodyA.uid, bodyB.uid ); + contactList[ hash ] = true; + ret.collidedPreviously = prevContacts[ hash ]; + collisions.push( ret ); } } } + this.prevContacts = contactList; + if ( collisions.length ){ this._world.emit( this.options.channel, { diff --git a/src/behaviors/body-impulse-response.js b/src/behaviors/body-impulse-response.js index 9d60cb2b..ae3edfb8 100644 --- a/src/behaviors/body-impulse-response.js +++ b/src/behaviors/body-impulse-response.js @@ -66,24 +66,6 @@ Physics.behavior('body-impulse-response', function( parent ){ return; } - if ( fixedA ){ - - // extract bodies - bodyB.state.pos.vadd( mtv ); - - } else if ( fixedB ){ - - // extract bodies - bodyA.state.pos.vsub( mtv ); - - } else { - - // extract bodies - mtv.mult( 0.5 ); - bodyA.state.pos.vsub( mtv ); - bodyB.state.pos.vadd( mtv ); - } - // inverse masses and moments of inertia. // give fixed bodies infinite mass and moi var invMoiA = fixedA ? 0 : 1 / bodyA.moi @@ -129,6 +111,26 @@ Physics.behavior('body-impulse-response', function( parent ){ return; } + if ( contact ){ + if ( fixedA ){ + + // extract bodies + bodyB.state.pos.vadd( mtv ); + + } else if ( fixedB ){ + + // extract bodies + bodyA.state.pos.vsub( mtv ); + + } else { + + // extract bodies + mtv.mult( 0.5 ); + bodyA.state.pos.vsub( mtv ); + bodyB.state.pos.vadd( mtv ); + } + } + invMoiA = invMoiA === Infinity ? 0 : invMoiA; invMoiB = invMoiB === Infinity ? 0 : invMoiB; @@ -237,7 +239,8 @@ Physics.behavior('body-impulse-response', function( parent ){ col.bodyB, col.norm, col.pos, - col.mtv + col.mtv, + col.collidedPreviously ); } } diff --git a/src/behaviors/edge-collision-detection.js b/src/behaviors/edge-collision-detection.js index fe13b530..addedacb 100644 --- a/src/behaviors/edge-collision-detection.js +++ b/src/behaviors/edge-collision-detection.js @@ -1,4 +1,4 @@ -/** +/** * class EdgeCollisionDetectionBehavior < Behavior * * `Physics.behavior('edge-collision-detection')`. @@ -19,7 +19,7 @@ Physics.behavior('edge-collision-detection', function( parent ){ * - bounds (Physics.aabb): The boundary * - dummy: (Body): The dummy body to publish as the static other body it collides with * + (Array): The collision data - * + * * Check if a body collides with the boundary */ var checkGeneral = function checkGeneral( body, bounds, dummy ){ @@ -144,7 +144,7 @@ Physics.behavior('edge-collision-detection', function( parent ){ * - bounds (Physics.aabb): The boundary * - dummy: (Body): The dummy body to publish as the static other body it collides with * + (Array): The collision data - * + * * Check if a body collides with the boundary */ var checkEdgeCollide = function checkEdgeCollide( body, bounds, dummy ){ @@ -171,8 +171,8 @@ Physics.behavior('edge-collision-detection', function( parent ){ this.setAABB( this.options.aabb ); this.restitution = this.options.restitution; - - this.body = Physics.body('point', { + + this.body = Physics.body('point', { treatment: 'static', restitution: this.options.restitution, cof: this.options.cof @@ -182,7 +182,7 @@ Physics.behavior('edge-collision-detection', function( parent ){ /** * EdgeCollisionDetectionBehavior#setAABB( aabb ) -> this * - aabb (Physics.aabb): The aabb to use as the boundary - * + * * Set the boundaries of the edge. **/ setAABB: function( aabb ){ @@ -198,7 +198,7 @@ Physics.behavior('edge-collision-detection', function( parent ){ }, max: { x: (aabb.x + aabb.hw), - y: (aabb.y + aabb.hh) + y: (aabb.y + aabb.hh) } }; @@ -220,11 +220,11 @@ Physics.behavior('edge-collision-detection', function( parent ){ /** internal * EdgeCollisionDetectionBehavior#checkAll( data ) * - data (Object): Event data - * + * * Event callback to check all bodies for collisions with the edge **/ checkAll: function( data ){ - + var bodies = this.getTargets() ,dt = data.dt ,body @@ -232,6 +232,10 @@ Physics.behavior('edge-collision-detection', function( parent ){ ,ret ,bounds = this._edges ,dummy = this.body + ,prevContacts = this.prevContacts || {} + ,contactList = {} + ,pairHash = Physics.util.pairHash + ,hash ; for ( var i = 0, l = bodies.length; i < l; i++ ){ @@ -240,15 +244,24 @@ Physics.behavior('edge-collision-detection', function( parent ){ // only detect dynamic bodies if ( body.treatment === 'dynamic' ){ - + ret = checkEdgeCollide( body, bounds, dummy ); if ( ret ){ + hash = pairHash( body.uid, dummy.uid ); + + for ( var j = 0, ll = ret.length; j < ll; j++ ){ + contactList[ hash ] = true; + ret[ j ].collidedPreviously = prevContacts[ hash ]; + } + collisions.push.apply( collisions, ret ); } } } + this.prevContacts = contactList; + if ( collisions.length ){ this._world.emit( this.options.channel, { @@ -258,4 +271,4 @@ Physics.behavior('edge-collision-detection', function( parent ){ } }; -}); \ No newline at end of file +}); From 7ff450eb78afa2ea9486a55f10232c98d364d3f5 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Wed, 12 Nov 2014 15:40:49 -0500 Subject: [PATCH 009/110] created better velocity verlet integrator --- src/integrators/velocity-verlet-alt.js | 153 ++++++++++++++++++++++ src/integrators/velocity-verlet.js | 88 +++++++++---- test/comparison-sandbox.html | 169 +++++++++++++++++++++++++ test/integrator-sandbox.html | 20 ++- 4 files changed, 400 insertions(+), 30 deletions(-) create mode 100644 src/integrators/velocity-verlet-alt.js create mode 100644 test/comparison-sandbox.html diff --git a/src/integrators/velocity-verlet-alt.js b/src/integrators/velocity-verlet-alt.js new file mode 100644 index 00000000..97f0f902 --- /dev/null +++ b/src/integrators/velocity-verlet-alt.js @@ -0,0 +1,153 @@ +Physics.integrator('velocity-verlet-alt', function( parent ){ + + // for this integrator we need to know if the object has been integrated before + // so let's add a mixin to bodies + + Physics.body.mixin({ + + started: function( val ){ + if ( val !== undefined ){ + this._started = true; + } + + return !!this._started; + } + }); + + + return { + /** + * class VelocityVerlet < Integrator + * + * `Physics.integrator('velocity-verlet')`. + * + * The velocity-verlet integrator. + **/ + + // extended + init: function( options ){ + + // call parent init + parent.init.call(this, options); + }, + + // extended + integrateVelocities: function( bodies, dt ){ + + // half the timestep + var dtdt = dt * dt + ,drag = 1 - this.options.drag + ,body = null + ,state + ; + + for ( var i = 0, l = bodies.length; i < l; ++i ){ + + body = bodies[ i ]; + state = body.state; + + // only integrate if the body isn't static + if ( body.treatment !== 'static' ){ + + // v = v_prev + 0.5 * (a_prev + a) * dt + // x = x_prev + v_prev * dt + 0.5 * a_prev * dt * dt + + // use the velocity in vel if the velocity has been changed manually + if ( !body.started() ){ + + // Set old vals on first integration + state.old.acc.clone( state.acc ); + state.old.acc.mult( dt ); + state.old.vel.clone( state.vel ).vsub( state.old.acc ); + state.old.acc.mult( 1/dt ); + } + + // Apply "air resistance". + if ( drag ){ + + state.vel.mult( drag ); + } + + // Apply acceleration + // v += 0.5 * (a_prev + a) * dt + state.vel.vadd( state.old.acc.vadd( state.acc ).mult( 0.5 * dt ) ); + + // Reset accel + // state.acc.zero(); + + // + // Angular components + // + + if ( !body.started() ){ + + // Set old vals on first integration + state.old.angular.acc = state.angular.acc; + state.old.angular.vel = state.angular.vel - state.old.angular.acc * dt; + } + + state.angular.vel += 0.5 * (state.angular.acc + state.old.angular.acc) * dt; + state.angular.acc = 0; + + body.started( true ); + + } else { + // set the velocity and acceleration to zero! + state.vel.zero(); + state.acc.zero(); + state.angular.vel = 0; + state.angular.acc = 0; + } + } + }, + + // extended + integratePositions: function( bodies, dt ){ + + // half the timestep + var dtdt = dt * dt + ,body = null + ,state + ; + + for ( var i = 0, l = bodies.length; i < l; ++i ){ + + body = bodies[ i ]; + state = body.state; + + // only integrate if the body isn't static + if ( body.treatment !== 'static' ){ + + // x = x_prev + v_prev * dt + 0.5 * a_prev * dt * dt + + // Store old position. + // xold = x + state.old.pos.clone( state.pos ); + + state.old.vel.mult( dt ); + state.old.acc.mult( 0.5 * dtdt ); + state.pos.vadd( state.old.vel ).vadd( state.old.acc ); + + // store calculated velocity + state.old.vel.clone( state.vel ); + + // store old acc + state.old.acc.clone( state.acc ); + + // Reset accel + state.acc.zero(); + + // + // Angular components + // + state.old.angular.pos = state.angular.pos; + + state.angular.pos += state.angular.vel * dt + 0.5 * state.old.angular.acc * dtdt; + state.old.angular.vel = state.angular.vel; + state.old.angular.acc = state.angular.acc; + state.angular.acc = 0; + } + } + } + }; +}); diff --git a/src/integrators/velocity-verlet.js b/src/integrators/velocity-verlet.js index e5fffe65..4e572897 100644 --- a/src/integrators/velocity-verlet.js +++ b/src/integrators/velocity-verlet.js @@ -31,6 +31,41 @@ Physics.integrator('velocity-verlet', function( parent ){ parent.init.call(this, options); }, + /** + * Integrator#integrate( bodies, dt ) -> this + * - bodies (Array): List of bodies to integrate + * - dt (Number): Timestep size + * + * Integrate bodies by timestep. + * + * Will emit `integrate:velocities` and `integrate:positions` + * events on the world. + **/ + integrate: function( bodies, dt ){ + + var world = this._world; + + this.integratePositions( bodies, dt ); + + if ( world ){ + world.emit('integrate:positions', { + bodies: bodies, + dt: dt + }); + } + + this.integrateVelocities( bodies, dt ); + + if ( world ){ + world.emit('integrate:velocities', { + bodies: bodies, + dt: dt + }); + } + + return this; + }, + // extended integrateVelocities: function( bodies, dt ){ @@ -52,16 +87,6 @@ Physics.integrator('velocity-verlet', function( parent ){ // v = v_prev + 0.5 * (a_prev + a) * dt // x = x_prev + v_prev * dt + 0.5 * a_prev * dt * dt - // use the velocity in vel if the velocity has been changed manually - if ( !body.started() ){ - - // Set old vals on first integration - state.old.acc.clone( state.acc ); - state.old.acc.mult( dt ); - state.old.vel.clone( state.vel ).vsub( state.old.acc ); - state.old.acc.mult( 1/dt ); - } - // Apply "air resistance". if ( drag ){ @@ -70,23 +95,22 @@ Physics.integrator('velocity-verlet', function( parent ){ // Apply acceleration // v += 0.5 * (a_prev + a) * dt + state.old.vel.clone( state.vel ); state.vel.vadd( state.old.acc.vadd( state.acc ).mult( 0.5 * dt ) ); // Reset accel + state.old.acc.clone( state.acc ); state.acc.zero(); // // Angular components // - if ( !body.started() ){ + state.old.angular.vel = state.angular.vel; + state.old.angular.acc = state.angular.acc; - // Set old vals on first integration - state.old.angular.acc = state.angular.acc; - state.old.angular.vel = state.angular.vel - state.old.angular.acc * dt; - } + state.angular.vel += 0.5 * (state.angular.acc + state.old.angular.acc) * dt; - state.angular.vel += 0.5 * (state.angular.acc + state.old.angular.acc) * dtdt; state.angular.acc = 0; body.started( true ); @@ -120,34 +144,42 @@ Physics.integrator('velocity-verlet', function( parent ){ // x = x_prev + v_prev * dt + 0.5 * a_prev * dt * dt + // use the velocity in vel if the velocity has been changed manually + if ( !body.started() ){ + + // Set old vals on first integration + state.old.acc.clone( state.acc ); + state.old.acc.mult( dt ); + state.old.vel.clone( state.vel ).vsub( state.old.acc ); + state.old.acc.mult( 1/dt ); + } + // Store old position. // xold = x state.old.pos.clone( state.pos ); state.old.vel.mult( dt ); - state.old.acc.mult( 0.5 * dtdt ) + state.old.acc.mult( 0.5 * dtdt ); state.pos.vadd( state.old.vel ).vadd( state.old.acc ); - // store calculated velocity - state.old.vel.clone( state.vel ); - - // store old acc - state.old.acc.clone( state.acc ); - - // Reset accel - state.acc.zero(); + // revert + state.old.vel.mult( 1/dt ); + state.old.acc.mult( 2 / dtdt ); // // Angular components // + if ( !body.started() ){ + // Set old vals on first integration + state.old.angular.acc = state.angular.acc; + state.old.angular.vel = state.angular.vel - state.old.angular.acc * dt; + } + state.old.angular.pos = state.angular.pos; state.angular.pos += state.angular.vel * dt + 0.5 * state.old.angular.acc * dtdt; - state.old.angular.vel = state.angular.vel; - state.old.angular.acc = state.angular.acc; - state.angular.acc = 0; } } } diff --git a/test/comparison-sandbox.html b/test/comparison-sandbox.html new file mode 100644 index 00000000..80ac8561 --- /dev/null +++ b/test/comparison-sandbox.html @@ -0,0 +1,169 @@ + + + + + + + + + + + + diff --git a/test/integrator-sandbox.html b/test/integrator-sandbox.html index 5d3ad1e0..b1f06431 100644 --- a/test/integrator-sandbox.html +++ b/test/integrator-sandbox.html @@ -47,7 +47,7 @@ }); - Physics({timestep: 3, maxIPF: 24},function (world) { + Physics({timestep: 2, maxIPF: 24},function (world) { var viewWidth = parent.innerWidth ,viewHeight = parent.innerHeight @@ -84,7 +84,7 @@ world.render(); }); - world.add( Physics.integrator('velocity-verlet') ); + world.add( Physics.integrator('verlet') ); // constrain objects to these bounds edgeBounce = Physics.behavior('edge-collision-detection', { @@ -115,10 +115,26 @@ height: 40, mass: 1.4, //cof: 0, + vy: -0.2, + // vx: 0.1, restitution: 1, styles: { fillStyle: 'red' } })); + // world.add( Physics.body('circle', { + // x: viewWidth/2, + // y: viewHeight - 61,//+100, + // radius: 20, + // width: 40, + // height: 40, + // mass: 1.4, + // //cof: 0, + // vy: -0.2, + // // vx: 0.1, + // restitution: 1, + // styles: { fillStyle: 'red' } + // })); + var ball = world.getBodies()[0]; var center = Physics.vector({x: viewWidth/2, y: viewHeight/2 }); From 75f7911bdc702e86a547a74e6b65913bd2e13157 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Wed, 12 Nov 2014 16:18:59 -0500 Subject: [PATCH 010/110] minor change --- src/integrators/verlet.js | 8 ++++---- test/integrator-sandbox.html | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/integrators/verlet.js b/src/integrators/verlet.js index 47ca2019..346da500 100644 --- a/src/integrators/verlet.js +++ b/src/integrators/verlet.js @@ -81,7 +81,7 @@ Physics.integrator('verlet', function( parent ){ // v += a * dt * dt state.vel.vadd( state.acc.mult( dtMul ) ); - // normalize velocity + // restore velocity state.vel.mult( 1/dt ); // store calculated velocity @@ -150,8 +150,8 @@ Physics.integrator('verlet', function( parent ){ state.pos.vadd( state.vel ); - // normalize velocity - state.vel.mult( 1/dt ); + // restore velocity + state.vel.mult( 1 / (dt * dtcorr) ); // store calculated velocity state.old.vel.clone( state.vel ); @@ -166,7 +166,7 @@ Physics.integrator('verlet', function( parent ){ state.old.angular.pos = state.angular.pos; state.angular.pos += state.angular.vel; - state.angular.vel /= dt; + state.angular.vel /= dt * dtcorr; state.old.angular.vel = state.angular.vel; } } diff --git a/test/integrator-sandbox.html b/test/integrator-sandbox.html index b1f06431..da318c68 100644 --- a/test/integrator-sandbox.html +++ b/test/integrator-sandbox.html @@ -84,7 +84,7 @@ world.render(); }); - world.add( Physics.integrator('verlet') ); + world.add( Physics.integrator('velocity-verlet') ); // constrain objects to these bounds edgeBounce = Physics.behavior('edge-collision-detection', { From 481d67d13ba2eb4e484f6a284033c5b3d0e8e488 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Thu, 13 Nov 2014 21:23:30 -0500 Subject: [PATCH 011/110] fix for timestep rounding errors --- src/core/world.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/world.js b/src/core/world.js index 6aad31c4..5868d44d 100644 --- a/src/core/world.js +++ b/src/core/world.js @@ -413,7 +413,7 @@ if ( dt ){ - this._dt = dt; + this._dt = +dt.toPrecision(4); // only keep 4 decimal places of precision otherwise we get rounding errors // calculate the maximum jump in time over which to do iterations this._maxJump = dt * this.options.maxIPF; From 555775cddaa6fdafefea5e4d6502d68f037c05cf Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Thu, 13 Nov 2014 22:26:39 -0500 Subject: [PATCH 012/110] fix for pubsub priorities --- src/util/pubsub.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/util/pubsub.js b/src/util/pubsub.js index 0f9bf111..0e58b913 100644 --- a/src/util/pubsub.js +++ b/src/util/pubsub.js @@ -1,5 +1,7 @@ (function(){ + var defaultPriority = 1; + function getPriority( val ){ return val._priority_; } @@ -67,12 +69,12 @@ fn._bindfn_ = orig; fn._one_ = orig._one_; - } else if (!priority) { + } else if ( priority === undefined ) { priority = scope; } - fn._priority_ = priority; + fn._priority_ = priority === undefined ? defaultPriority : priority; idx = Physics.util.sortedIndex( listeners, fn, getPriority ); From 9c55dbe8f3ef2b9642553e2a75a02b72ca8a6398 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Thu, 13 Nov 2014 22:26:39 -0500 Subject: [PATCH 013/110] fix: circle collisions without overlap were faulty --- src/behaviors/body-collision-detection.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/behaviors/body-collision-detection.js b/src/behaviors/body-collision-detection.js index 44595ce9..28b9fec7 100644 --- a/src/behaviors/body-collision-detection.js +++ b/src/behaviors/body-collision-detection.js @@ -193,8 +193,8 @@ Physics.behavior('body-collision-detection', function( parent ){ bodyA: bodyA, bodyB: bodyB, norm: d.normalize().values(), - mtv: d.mult( -overlap ).values(), - pos: d.normalize().mult( bodyA.geometry.radius ).values(), + pos: d.mult( bodyA.geometry.radius ).values(), + mtv: d.mult( -overlap/bodyA.geometry.radius ).values(), overlap: -overlap }; } From 26f9aaa0d32bb4c1d158d9133746fc5d3fbe7b26 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Thu, 13 Nov 2014 23:23:29 -0500 Subject: [PATCH 014/110] fix for #76 polygon collision detection contact points were being reported incorrectly --- src/behaviors/body-collision-detection.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/behaviors/body-collision-detection.js b/src/behaviors/body-collision-detection.js index 28b9fec7..e8aefa2f 100644 --- a/src/behaviors/body-collision-detection.js +++ b/src/behaviors/body-collision-detection.js @@ -143,13 +143,18 @@ Physics.behavior('body-collision-detection', function( parent ){ } // calc overlap - overlap = Math.max(0, (support.marginA + support.marginB) - result.distance); + overlap = (support.marginA + support.marginB) - result.distance; + + if ( overlap <= 0 ){ + return scratch.done(false); + } + collision.overlap = overlap; // @TODO: for now, just let the normal be the mtv collision.norm = d.clone( result.closest.b ).vsub( tmp.clone( result.closest.a ) ).normalize().values(); collision.mtv = d.mult( overlap ).values(); // get a corresponding hull point for one of the core points.. relative to body A - collision.pos = d.clone( collision.norm ).mult( support.margin ).vadd( tmp.clone( result.closest.a ) ).vsub( bodyA.state.pos ).values(); + collision.pos = d.clone( collision.norm ).mult( support.marginA ).vadd( tmp.clone( result.closest.a ) ).vsub( bodyA.state.pos ).values(); } return scratch.done( collision ); From 62269307996254c222d57ff09996114f9b05b366 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Thu, 13 Nov 2014 23:24:15 -0500 Subject: [PATCH 015/110] added collision sandbox --- test/collision-sandbox.html | 290 ++++++++++++++++++++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 test/collision-sandbox.html diff --git a/test/collision-sandbox.html b/test/collision-sandbox.html new file mode 100644 index 00000000..d8254027 --- /dev/null +++ b/test/collision-sandbox.html @@ -0,0 +1,290 @@ + + + + + + + +
+ + + + + From 1de6667cf74d9dd8a9d7f3b1cc2ebd14e95f377d Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Thu, 13 Nov 2014 23:23:29 -0500 Subject: [PATCH 016/110] fix sweep-prune not resetting between steps --- src/behaviors/sweep-prune.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/behaviors/sweep-prune.js b/src/behaviors/sweep-prune.js index 7612a0df..30778368 100644 --- a/src/behaviors/sweep-prune.js +++ b/src/behaviors/sweep-prune.js @@ -196,6 +196,10 @@ Physics.behavior('sweep-prune', function( parent ){ }; } + if ( doCreate){ + c.flag = 1; + } + return c; }, @@ -315,10 +319,6 @@ Physics.behavior('sweep-prune', function( parent ){ if ( c ){ - if ( c.flag > collisionFlag ){ - c.flag = 1; - } - // if it's greater than the axis index, set the flag // to = 0. // if not, increment the flag by one. From 22bc57b4bb2485727d581c8afe8fdf9f0e1d4367 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Fri, 14 Nov 2014 14:03:13 -0500 Subject: [PATCH 017/110] apply propper friction for contacts --- src/behaviors/body-impulse-response.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/behaviors/body-impulse-response.js b/src/behaviors/body-impulse-response.js index ae3edfb8..65dfb677 100644 --- a/src/behaviors/body-impulse-response.js +++ b/src/behaviors/body-impulse-response.js @@ -102,7 +102,7 @@ Physics.behavior('body-impulse-response', function( parent ){ ,impulse ,sign ,max - ,inContact = false + ,inContact = contact ; // if moving away from each other... don't bother. From 2b784ea5b739bc237f1628d8af7262b8c040a685 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Fri, 14 Nov 2014 14:49:18 -0500 Subject: [PATCH 018/110] added hook for "beforeStep" --- src/core/world.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/world.js b/src/core/world.js index 5868d44d..6f44d138 100644 --- a/src/core/world.js +++ b/src/core/world.js @@ -654,6 +654,8 @@ // the target time for the world time to step to target = time + worldDiff - dt; + this.emit('beforeStep'); + if ( time <= target ){ while ( time <= target ){ From e241fd9ab219a4138088e7f3138330737f7e900f Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Fri, 14 Nov 2014 14:49:34 -0500 Subject: [PATCH 019/110] added sweep-prune interval draw --- test/collision-sandbox.html | 68 +++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/test/collision-sandbox.html b/test/collision-sandbox.html index d8254027..f2617ba0 100644 --- a/test/collision-sandbox.html +++ b/test/collision-sandbox.html @@ -46,6 +46,7 @@ var debugEl = document.getElementById('debug'); var doDebug = false; + var sp; function debug(){ var lines = Array.prototype.join.call(arguments, '
'); @@ -105,7 +106,7 @@ world.add([ - Physics.behavior('sweep-prune') + sp = Physics.behavior('sweep-prune') ,Physics.behavior('body-collision-detection', { checkAll: false }) @@ -142,7 +143,7 @@ } }); - document.addEventListener('mousemove', function( e ){ + document.addEventListener('mousemove', Physics.util.throttle(function( e ){ var pos; if ( grab ){ @@ -153,7 +154,7 @@ world.step(); } - }); + }, 1000/60)); document.addEventListener('mouseup', function(){ @@ -204,7 +205,7 @@ var polygons = [ {x:410, y:220, v:[{x: 0, y: 80}, {x: 80, y: 0}, {x: 0, y: -80},{x: -30, y: -30}, {x: -30, y: 30}] }, - {x:290, y:320, v:[{x: 0, y: 80}, {x: 80, y: 0}, {x: 0, y: -80},{x: -30, y: -30}, {x: -30, y: 30}] } + {x:290, y:320, v:[{x: 0, y: 80}, {x: 80, y: 0}, {x: 0, y: -80},{x: -30, y: -30}, {x: -30, y: 30}], angle: Math.PI } ]; for(var i = 0;i < polygons.length;i++){ @@ -213,7 +214,8 @@ x: polygons[i].x, y: polygons[i].y, vertices: polygons[i].v, - + angle: polygons[i].angle, + styles: bodyStyles }) ); @@ -261,19 +263,73 @@ } } - world.on('collisions:candidates', function(){ + var intervalStyles = { + min: { strokeStyle: 'rgba( 0, 0, 200, 0.3 )', lineWidth: 2, fillStyle: 'transparent' } + ,max: { strokeStyle: 'rgba( 200, 0, 0, 0.3 )', lineWidth: 2, fillStyle: 'transparent' } + }; + + function drawInterval( intr ){ + var ctx = layer.ctx; + var scratch = Physics.scratchpad(); + var from = scratch.vector().set( intr.val.x, 0 ); + var to = scratch.vector().set( intr.val.x, intr.tracker.body.state.pos.y ); + + renderer.drawLine( from, to, intervalStyles[ intr.type ? 'max' : 'min' ], ctx ); + renderer.drawCircle( from.x, from.y, 4, intervalStyles[ intr.type ? 'max' : 'min' ], ctx ); + + from.set( 0, intr.val.y ); + to.set( intr.tracker.body.state.pos.x, intr.val.y ); + + renderer.drawLine( from, to, intervalStyles[ intr.type ? 'max' : 'min' ], ctx ); + renderer.drawCircle( from.x, from.y, 4, intervalStyles[ intr.type ? 'max' : 'min' ], ctx ); + + scratch.done(); + } + + var colsDetected, candidatesDetected; + + world.on('beforeStep', function(){ // clear layer.el.width = layer.el.width; + + candidatesDetected = false; + colsDetected = false; + }); + + world.on('collisions:candidates', function( data ){ + + candidatesDetected = data.candidates; + }, this, 100); + world.on('collisions:detected', function( data ){ var cols = data.collisions; + colsDetected = cols; + for ( var i = 0, l = cols.length; i < l; i++ ){ drawContact( cols[i] ); } }); + world.on('step', function(){ + + var intr; + for ( var xy = 0; xy < 2; xy++ ){ + intr = sp.intervalLists[ xy ]; + for ( var i = 0, l = intr.length; i < l; i++ ){ + + drawInterval( intr[ i ] ); + } + } + + debug( + 'candidates: ' + ((candidatesDetected && candidatesDetected.length)|0), + 'collisions: ' + ((colsDetected && colsDetected.length)|0) + ); + }); + world.step(); } From f36553a64c1c831c9e9b282c1e8c5b6c8fb79d16 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Fri, 14 Nov 2014 16:31:12 -0500 Subject: [PATCH 020/110] fix re #76. sweep prune had wrong aabb dims --- src/behaviors/sweep-prune.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/behaviors/sweep-prune.js b/src/behaviors/sweep-prune.js index 30778368..40e7e022 100644 --- a/src/behaviors/sweep-prune.js +++ b/src/behaviors/sweep-prune.js @@ -360,10 +360,7 @@ Physics.behavior('sweep-prune', function( parent ){ var tr ,intr - ,scratch = Physics.scratchpad() - ,pos = scratch.vector() ,aabb - ,span = scratch.vector() ,list = this.tracked ,i = list.length ; @@ -373,17 +370,13 @@ Physics.behavior('sweep-prune', function( parent ){ tr = list[ i ]; intr = tr.interval; - pos.clone( tr.body.state.pos ); aabb = tr.body.aabb(); - span.set( aabb.hw, aabb.hh ); // copy the position (plus or minus) the aabb half-dimensions // into the min/max intervals - intr.min.val.clone( pos ).vsub( span ); - intr.max.val.clone( pos ).vadd( span ); + intr.min.val.clone( aabb ).sub( aabb.hw, aabb.hh ); + intr.max.val.clone( aabb ).add( aabb.hw, aabb.hh ); } - - scratch.done(); }, /** internal From 77c04b1bc4a0f298eefa56aa7b3bff23b5f627cb Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Sat, 15 Nov 2014 17:22:15 -0500 Subject: [PATCH 021/110] friction and contact tweaks to impulse response --- src/behaviors/body-impulse-response.js | 42 ++- test/collision-sandbox.html | 67 +++-- test/contact-sandbox.html | 346 +++++++++++++++++++++++++ test/integrator-sandbox.html | 25 +- 4 files changed, 436 insertions(+), 44 deletions(-) create mode 100644 test/contact-sandbox.html diff --git a/src/behaviors/body-impulse-response.js b/src/behaviors/body-impulse-response.js index 65dfb677..5680f7d3 100644 --- a/src/behaviors/body-impulse-response.js +++ b/src/behaviors/body-impulse-response.js @@ -73,7 +73,7 @@ Physics.behavior('body-impulse-response', function( parent ){ ,invMassA = fixedA ? 0 : 1 / bodyA.mass ,invMassB = fixedB ? 0 : 1 / bodyB.mass // coefficient of restitution between bodies - ,cor = contact ? 0 : bodyA.restitution * bodyB.restitution + ,cor = bodyA.restitution * bodyB.restitution // coefficient of friction between bodies ,cof = bodyA.cof * bodyB.cof // normal vector @@ -105,13 +105,11 @@ Physics.behavior('body-impulse-response', function( parent ){ ,inContact = contact ; - // if moving away from each other... don't bother. - if (vproj >= 0){ - scratch.done(); - return; - } - if ( contact ){ + if ( mtv.normSq() < 1 ){ + mtv.mult( 0.5 ); + } + if ( fixedA ){ // extract bodies @@ -131,6 +129,12 @@ Physics.behavior('body-impulse-response', function( parent ){ } } + // if moving away from each other... don't bother. + if (vproj >= 0){ + scratch.done(); + return; + } + invMoiA = invMoiA === Infinity ? 0 : invMoiA; invMoiB = invMoiB === Infinity ? 0 : invMoiB; @@ -177,21 +181,15 @@ Physics.behavior('body-impulse-response', function( parent ){ // allowed amount // maximum impulse allowed by kinetic friction - max = vreg / ( invMassA + invMassB + (invMoiA * rAproj * rAproj) + (invMoiB * rBproj * rBproj) ); - - if (!inContact){ - // the sign of vreg ( plus or minus 1 ) - sign = vreg < 0 ? -1 : 1; - - // get impulse due to friction - impulse *= sign * cof; - // make sure the impulse isn't giving the system energy - impulse = (sign === 1) ? Math.min( impulse, max ) : Math.max( impulse, max ); - - } else { - - impulse = max; - } + max = Math.abs(vreg) / ( invMassA + invMassB + (invMoiA * rAproj * rAproj) + (invMoiB * rBproj * rBproj) ); + // the sign of vreg ( plus or minus 1 ) + sign = vreg < 0 ? -1 : 1; + + // get impulse due to friction + impulse = cof * Math.abs( impulse ); + // constrain the impulse within the "friction cone" ( max < mu * impulse) + impulse = Math.min( impulse, max ); + impulse *= sign; if ( fixedA ){ diff --git a/test/collision-sandbox.html b/test/collision-sandbox.html index f2617ba0..3b2a72bb 100644 --- a/test/collision-sandbox.html +++ b/test/collision-sandbox.html @@ -60,7 +60,7 @@ width: viewWidth, height: viewHeight, meta: true, - //debug:true, + // debug:true, styles: { 'circle': { strokeStyle: 'grey', @@ -88,6 +88,12 @@ world.render(); }); + edgeBounce = Physics.behavior('edge-collision-detection', { + aabb: viewportBounds, + restitution: 0.5, + cof: 1 + }); + world.add( Physics.integrator('velocity-verlet') ); // resize events @@ -100,26 +106,28 @@ renderer.el.height = viewHeight; viewportBounds = Physics.aabb(0, 0, viewWidth, viewHeight); - // edgeBounce.setAABB(viewportBounds); + edgeBounce.setAABB(viewportBounds); }, true); world.add([ - - sp = Physics.behavior('sweep-prune') + edgeBounce + ,sp = Physics.behavior('sweep-prune') ,Physics.behavior('body-collision-detection', { checkAll: false }) - // ,Physics.behavior('body-impulse-response') + ,Physics.behavior('body-impulse-response') // add gravity - // ,Physics.behavior('constant-acceleration') + ,Physics.behavior('constant-acceleration') + // ,Physics.behavior('newtonian') + ,Physics.behavior('interactive', {el: renderer.el.parentNode}) ]); - // Physics.util.ticker.on(function (time, dt) { - // - // world.step(time); - // }); + Physics.util.ticker.on(function (time, dt) { + + world.step(time); + }); } function initEvents( world ){ @@ -173,7 +181,7 @@ function addBodies( world ){ - var bodyStyles = { strokeStyle: '#888', fillStyle: 'transparent', lineWidth: 2 }; + var bodyStyles = { strokeStyle: '#888', fillStyle: 'transparent', lineWidth: 2, angleIndicator: 'rgba(200, 200, 100, 1)' }; world.add( Physics.body('circle', { x: viewWidth/2, @@ -199,7 +207,7 @@ //cof: 0, //vy: -0.2, // vx: 0.1, - restitution: 1, + restitution: 0.9, styles: bodyStyles })); @@ -216,7 +224,32 @@ vertices: polygons[i].v, angle: polygons[i].angle, - styles: bodyStyles + styles: bodyStyles, + restitution: 0.5, + cof: 1 + }) + ); + } + + var rectangles = [ + { x: 100, y: 60, w: 50, h: 20, mass: 1 } + ,{ x: 300, y: 60, w: 100, h: 80, mass: 2 } + ,{ x: 500, y: 100, w: 150, h: 300, mass: 3 } + ]; + + for(var i = 0;i < rectangles.length;i++){ + world.add( + Physics.body('rectangle', { + x: rectangles[i].x, + y: rectangles[i].y, + width: rectangles[i].w, + height: rectangles[i].h, + angle: rectangles[i].angle, + mass: rectangles[i].mass, + + styles: bodyStyles, + restitution: 0.6, + cof: 1 }) ); } @@ -264,8 +297,8 @@ } var intervalStyles = { - min: { strokeStyle: 'rgba( 0, 0, 200, 0.3 )', lineWidth: 2, fillStyle: 'transparent' } - ,max: { strokeStyle: 'rgba( 200, 0, 0, 0.3 )', lineWidth: 2, fillStyle: 'transparent' } + min: { strokeStyle: 'rgba( 0, 0, 200, 0.1 )', lineWidth: 2, fillStyle: 'transparent' } + ,max: { strokeStyle: 'rgba( 200, 0, 0, 0.1 )', lineWidth: 2, fillStyle: 'transparent' } }; function drawInterval( intr ){ @@ -334,10 +367,10 @@ } - Physics({ timestep: 2, maxIPF: 24 }, [ + Physics({ timestep: 6, maxIPF: 24 }, [ setup ,addBodies - ,initEvents + //,initEvents ,setupRenderer ]); diff --git a/test/contact-sandbox.html b/test/contact-sandbox.html new file mode 100644 index 00000000..20fac075 --- /dev/null +++ b/test/contact-sandbox.html @@ -0,0 +1,346 @@ + + + + + + + +
+ + + + + diff --git a/test/integrator-sandbox.html b/test/integrator-sandbox.html index da318c68..6650df72 100644 --- a/test/integrator-sandbox.html +++ b/test/integrator-sandbox.html @@ -107,17 +107,32 @@ }, true); - world.add( Physics.body('circle', { - x: viewWidth/2, + // world.add( Physics.body('circle', { + // x: viewWidth/2, + // y: viewHeight - 20,//+100, + // radius: 20, + // width: 40, + // height: 40, + // mass: 1.4, + // //cof: 0, + // vy: -0.2, + // // vx: 0.1, + // restitution: 1, + // styles: { fillStyle: 'red' } + // })); + + world.add( Physics.body('rectangle', { + x: viewWidth/4, y: viewHeight - 20,//+100, radius: 20, width: 40, height: 40, mass: 1.4, - //cof: 0, + // cof: 0, vy: -0.2, // vx: 0.1, - restitution: 1, + angle: 0.1, + restitution: 0.8, styles: { fillStyle: 'red' } })); @@ -173,7 +188,7 @@ var sx = 1; var sy = 300; // console.log(center.angle(ball.state.pos)) - var x = sx * center.dist(ball.state.pos) * (center.angle(ball.state.pos) > 0 ? 1 : -1); + var x = sx * (center.y - ball.state.pos.y);// * (center.angle(ball.state.pos) > 0 ? 1 : -1); var y = sy * ball.state.vel.norm() * (ball.state.vel.angle() > 0 ? 1 : -1); // console.log(x, y) renderer.drawCircle(x + center.x, y + center.y, 0.5, '#f66', this.ctx); From f21a905c644926ef854e1a4d35d13080c0716643 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Sat, 15 Nov 2014 17:31:16 -0500 Subject: [PATCH 022/110] added customizable options --- src/behaviors/body-impulse-response.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/behaviors/body-impulse-response.js b/src/behaviors/body-impulse-response.js index 5680f7d3..25f9e3bc 100644 --- a/src/behaviors/body-impulse-response.js +++ b/src/behaviors/body-impulse-response.js @@ -7,12 +7,21 @@ * * Additional options include: * - check: channel to listen to for collisions (default: `collisions:detected`). + * - mtvThreshold: apply partial extraction of bodies if the minimum transit vector is less than this value ( default: `1`) + * this will depend on your simulation characteristic length scale + * - bodyExtractDropoff: every body overlap correction (underneith mtvThreshold) will only extract by this fraction (0..1). Helps with stablizing contacts. (default: `0.5`) **/ Physics.behavior('body-impulse-response', function( parent ){ var defaults = { // channel to listen to for collisions check: 'collisions:detected' + // apply partial extraction of bodies if the minimum transit vector is less than this value + // this will depend on your simulation characteristic length scale + ,mtvThreshold: 1 + // every body overlap correction (underneith mtvThreshold) will only extract by this fraction (0..1) + // helps with stablizing contacts. + ,bodyExtractDropoff: 0.5 }; return { @@ -106,8 +115,8 @@ Physics.behavior('body-impulse-response', function( parent ){ ; if ( contact ){ - if ( mtv.normSq() < 1 ){ - mtv.mult( 0.5 ); + if ( mtv.normSq() < this.options.mtvThreshold ){ + mtv.mult( this.options.bodyExtractDropoff ); } if ( fixedA ){ From f94ca2f1ff160d73ae17ea0e5a0ebcdfcd3a62f7 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Sat, 15 Nov 2014 18:57:39 -0500 Subject: [PATCH 023/110] feature: added deep copy option to options helper --- src/util/helpers.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/util/helpers.js b/src/util/helpers.js index ccaeb3b6..42b25481 100644 --- a/src/util/helpers.js +++ b/src/util/helpers.js @@ -99,6 +99,16 @@ Physics.util.throttle = function throttle( fn, delay, scope ){ * }); * ``` **/ +// deep copy callback to extend deeper into options +var deepCopyFn = function( a, b ){ + + if ( Physics.util.isPlainObject( b ) ){ + + return Physics.util.extend({}, a, b, deepCopyFn ); + } + + return b !== undefined ? b : a; +}; Physics.util.options = function( def, target ){ var _def = {} @@ -107,9 +117,9 @@ Physics.util.options = function( def, target ){ ; // set options - fn = function fn( options ){ + fn = function fn( options, deep ){ - Physics.util.extend(target, options, null); + Physics.util.extend(target, options, deep ? deepCopyFn : null); for ( var i = 0, l = callbacks.length; i < l; ++i ){ callbacks[ i ]( target ); } @@ -117,9 +127,9 @@ Physics.util.options = function( def, target ){ }; // add defaults - fn.defaults = function defaults( def ){ - Physics.util.extend( _def, def ); - Physics.util.defaults( target, _def ); + fn.defaults = function defaults( def, deep ){ + Physics.util.extend( _def, def, deep ? deepCopyFn : null ); + Physics.util.defaults( target, _def, deep ? deepCopyFn : null ); return _def; }; From b22cb055451e9986620e43fa8142d72c01a08983 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Sat, 15 Nov 2014 19:00:00 -0500 Subject: [PATCH 024/110] change: renderers now implement .options helper --- src/core/renderer.js | 3 ++- src/renderers/canvas.js | 19 +++++-------------- src/renderers/pixi-renderer.js | 18 +++++------------- 3 files changed, 12 insertions(+), 28 deletions(-) diff --git a/src/core/renderer.js b/src/core/renderer.js index c5f81601..16310699 100644 --- a/src/core/renderer.js +++ b/src/core/renderer.js @@ -56,7 +56,8 @@ var el = typeof options.el === 'string' ? document.getElementById(options.el) : options.el ; - this.options = Physics.util.extend({}, defaults, options); + this.options = Physics.util.options(defaults); + this.options( options ); this.el = el ? el : document.body; this.drawMeta = Physics.util.throttle( Physics.util.bind(this.drawMeta, this), this.options.metaRefresh ); diff --git a/src/renderers/canvas.js b/src/renderers/canvas.js index 95bd6e71..3cab2516 100644 --- a/src/renderers/canvas.js +++ b/src/renderers/canvas.js @@ -84,17 +84,6 @@ Physics.renderer('canvas', function( proto ){ offset: { x: 0, y: 0 } }; - // deep copy callback to extend deeper into options - var deep = function( a, b ){ - - if ( Physics.util.isPlainObject( b ) ){ - - return Physics.util.extend({}, a, b, deep ); - } - - return b !== undefined ? b : a; - }; - return { // extended @@ -106,9 +95,11 @@ Physics.renderer('canvas', function( proto ){ proto.init.call(this, options); // further options - this.options = Physics.util.extend({}, defaults, this.options, deep); - this.options.offset = Physics.vector( this.options.offset ); - + this.options.defaults( defaults, true ); + this.options.onChange(function(){ + self.options.offset = Physics.vector( self.options.offset ); + }); + this.options( options, true ); // hidden canvas this.hiddenCanvas = document.createElement('canvas'); diff --git a/src/renderers/pixi-renderer.js b/src/renderers/pixi-renderer.js index 8ebdf837..14449e2a 100644 --- a/src/renderers/pixi-renderer.js +++ b/src/renderers/pixi-renderer.js @@ -77,17 +77,6 @@ Physics.renderer('pixi', function( parent ){ } }; - // deep copy callback to extend deeper into options - var deep = function( a, b ){ - - if ( Physics.util.isPlainObject( b ) ){ - - return Physics.util.extend({}, a, b, deep ); - } - - return b !== undefined ? b : a; - }; - return { // extended @@ -101,8 +90,11 @@ Physics.renderer('pixi', function( parent ){ parent.init.call(this, options); // further options - this.options = Physics.util.extend({}, defaults, this.options, deep); - this.options.offset = Physics.vector( this.options.offset ); + this.options.defaults( defaults, true ); + this.options.onChange(function(){ + self.options.offset = Physics.vector( self.options.offset ); + }); + this.options( options, true ); // Hook in PIXI stage here this.stage = new PIXI.Stage(this.options.styles.color); From e745947ed7b72128a3f3df8980b7a3c2607d03ed Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Sat, 15 Nov 2014 19:00:29 -0500 Subject: [PATCH 025/110] added special debug renderer --- lib/dat.gui.js | 82 ++++++++++++++++++++++++ src/renderers/canvas.js | 24 +++----- src/renderers/debug.js | 120 ++++++++++++++++++++++++++++++++++++ test/collision-sandbox.html | 37 ++++------- 4 files changed, 222 insertions(+), 41 deletions(-) create mode 100644 lib/dat.gui.js create mode 100644 src/renderers/debug.js diff --git a/lib/dat.gui.js b/lib/dat.gui.js new file mode 100644 index 00000000..5dd45a89 --- /dev/null +++ b/lib/dat.gui.js @@ -0,0 +1,82 @@ +var dat=dat||{};dat.gui=dat.gui||{};dat.utils=dat.utils||{};dat.controllers=dat.controllers||{};dat.dom=dat.dom||{};dat.color=dat.color||{};dat.utils.css=function(){return{load:function(e,a){a=a||document;var b=a.createElement("link");b.type="text/css";b.rel="stylesheet";b.href=e;a.getElementsByTagName("head")[0].appendChild(b)},inject:function(e,a){a=a||document;var b=document.createElement("style");b.type="text/css";b.innerHTML=e;a.getElementsByTagName("head")[0].appendChild(b)}}}(); +dat.utils.common=function(){var e=Array.prototype.forEach,a=Array.prototype.slice;return{BREAK:{},extend:function(b){this.each(a.call(arguments,1),function(a){for(var f in a)this.isUndefined(a[f])||(b[f]=a[f])},this);return b},defaults:function(b){this.each(a.call(arguments,1),function(a){for(var f in a)this.isUndefined(b[f])&&(b[f]=a[f])},this);return b},compose:function(){var b=a.call(arguments);return function(){for(var d=a.call(arguments),f=b.length-1;0<=f;f--)d=[b[f].apply(this,d)];return d[0]}}, +each:function(a,d,f){if(e&&a.forEach===e)a.forEach(d,f);else if(a.length===a.length+0)for(var c=0,p=a.length;cthis.__max&&(a=this.__max);void 0!==this.__step&&0!=a%this.__step&&(a=Math.round(a/this.__step)*this.__step);return b.superclass.prototype.setValue.call(this,a)},min:function(a){this.__min=a;return this},max:function(a){this.__max=a;return this},step:function(a){this.__step=a;return this}});return b}(dat.controllers.Controller,dat.utils.common); +dat.controllers.NumberControllerBox=function(e,a,b){var d=function(f,c,e){function k(){var a=parseFloat(n.__input.value);b.isNaN(a)||n.setValue(a)}function l(a){var c=r-a.clientY;n.setValue(n.getValue()+c*n.__impliedStep);r=a.clientY}function q(){a.unbind(window,"mousemove",l);a.unbind(window,"mouseup",q)}this.__truncationSuspended=!1;d.superclass.call(this,f,c,e);var n=this,r;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"change",k);a.bind(this.__input, +"blur",function(){k();n.__onFinishChange&&n.__onFinishChange.call(n,n.getValue())});a.bind(this.__input,"mousedown",function(c){a.bind(window,"mousemove",l);a.bind(window,"mouseup",q);r=c.clientY});a.bind(this.__input,"keydown",function(a){13===a.keyCode&&(n.__truncationSuspended=!0,this.blur(),n.__truncationSuspended=!1)});this.updateDisplay();this.domElement.appendChild(this.__input)};d.superclass=e;b.extend(d.prototype,e.prototype,{updateDisplay:function(){var a=this.__input,c;if(this.__truncationSuspended)c= +this.getValue();else{c=this.getValue();var b=Math.pow(10,this.__precision);c=Math.round(c*b)/b}a.value=c;return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.NumberController,dat.dom.dom,dat.utils.common); +dat.controllers.NumberControllerSlider=function(e,a,b,d,f){function c(a,c,d,b,f){return b+(a-c)/(d-c)*(f-b)}var p=function(d,b,f,e,r){function y(d){d.preventDefault();var b=a.getOffset(h.__background),f=a.getWidth(h.__background);h.setValue(c(d.clientX,b.left,b.left+f,h.__min,h.__max));return!1}function g(){a.unbind(window,"mousemove",y);a.unbind(window,"mouseup",g);h.__onFinishChange&&h.__onFinishChange.call(h,h.getValue())}p.superclass.call(this,d,b,{min:f,max:e,step:r});var h=this;this.__background= +document.createElement("div");this.__foreground=document.createElement("div");a.bind(this.__background,"mousedown",function(c){a.bind(window,"mousemove",y);a.bind(window,"mouseup",g);y(c)});a.addClass(this.__background,"slider");a.addClass(this.__foreground,"slider-fg");this.updateDisplay();this.__background.appendChild(this.__foreground);this.domElement.appendChild(this.__background)};p.superclass=e;p.useDefaultStyles=function(){b.inject(f)};d.extend(p.prototype,e.prototype,{updateDisplay:function(){var a= +(this.getValue()-this.__min)/(this.__max-this.__min);this.__foreground.style.width=100*a+"%";return p.superclass.prototype.updateDisplay.call(this)}});return p}(dat.controllers.NumberController,dat.dom.dom,dat.utils.css,dat.utils.common,"/**\n * dat-gui JavaScript Controller Library\n * http://code.google.com/p/dat-gui\n *\n * Copyright 2011 Data Arts Team, Google Creative Lab\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n */\n\n.slider {\n box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);\n height: 1em;\n border-radius: 1em;\n background-color: #eee;\n padding: 0 0.5em;\n overflow: hidden;\n}\n\n.slider-fg {\n padding: 1px 0 2px 0;\n background-color: #aaa;\n height: 1em;\n margin-left: -0.5em;\n padding-right: 0.5em;\n border-radius: 1em 0 0 1em;\n}\n\n.slider-fg:after {\n display: inline-block;\n border-radius: 1em;\n background-color: #fff;\n border: 1px solid #aaa;\n content: '';\n float: right;\n margin-right: -1em;\n margin-top: -1px;\n height: 0.9em;\n width: 0.9em;\n}"); +dat.controllers.FunctionController=function(e,a,b){var d=function(b,c,e){d.superclass.call(this,b,c);var k=this;this.__button=document.createElement("div");this.__button.innerHTML=void 0===e?"Fire":e;a.bind(this.__button,"click",function(a){a.preventDefault();k.fire();return!1});a.addClass(this.__button,"button");this.domElement.appendChild(this.__button)};d.superclass=e;b.extend(d.prototype,e.prototype,{fire:function(){this.__onChange&&this.__onChange.call(this);this.__onFinishChange&&this.__onFinishChange.call(this, +this.getValue());this.getValue().call(this.object)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); +dat.controllers.BooleanController=function(e,a,b){var d=function(b,c){d.superclass.call(this,b,c);var e=this;this.__prev=this.getValue();this.__checkbox=document.createElement("input");this.__checkbox.setAttribute("type","checkbox");a.bind(this.__checkbox,"change",function(){e.setValue(!e.__prev)},!1);this.domElement.appendChild(this.__checkbox);this.updateDisplay()};d.superclass=e;b.extend(d.prototype,e.prototype,{setValue:function(a){a=d.superclass.prototype.setValue.call(this,a);this.__onFinishChange&& +this.__onFinishChange.call(this,this.getValue());this.__prev=this.getValue();return a},updateDisplay:function(){!0===this.getValue()?(this.__checkbox.setAttribute("checked","checked"),this.__checkbox.checked=!0):this.__checkbox.checked=!1;return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); +dat.color.toString=function(e){return function(a){if(1==a.a||e.isUndefined(a.a)){for(a=a.hex.toString(16);6>a.length;)a="0"+a;return"#"+a}return"rgba("+Math.round(a.r)+","+Math.round(a.g)+","+Math.round(a.b)+","+a.a+")"}}(dat.utils.common); +dat.color.interpret=function(e,a){var b,d,f=[{litmus:a.isString,conversions:{THREE_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i);return null===a?!1:{space:"HEX",hex:parseInt("0x"+a[1].toString()+a[1].toString()+a[2].toString()+a[2].toString()+a[3].toString()+a[3].toString())}},write:e},SIX_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9]{6})$/i);return null===a?!1:{space:"HEX",hex:parseInt("0x"+a[1].toString())}},write:e},CSS_RGB:{read:function(a){a=a.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); +return null===a?!1:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3])}},write:e},CSS_RGBA:{read:function(a){a=a.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/);return null===a?!1:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3]),a:parseFloat(a[4])}},write:e}}},{litmus:a.isNumber,conversions:{HEX:{read:function(a){return{space:"HEX",hex:a,conversionName:"HEX"}},write:function(a){return a.hex}}}},{litmus:a.isArray,conversions:{RGB_ARRAY:{read:function(a){return 3!= +a.length?!1:{space:"RGB",r:a[0],g:a[1],b:a[2]}},write:function(a){return[a.r,a.g,a.b]}},RGBA_ARRAY:{read:function(a){return 4!=a.length?!1:{space:"RGB",r:a[0],g:a[1],b:a[2],a:a[3]}},write:function(a){return[a.r,a.g,a.b,a.a]}}}},{litmus:a.isObject,conversions:{RGBA_OBJ:{read:function(c){return a.isNumber(c.r)&&a.isNumber(c.g)&&a.isNumber(c.b)&&a.isNumber(c.a)?{space:"RGB",r:c.r,g:c.g,b:c.b,a:c.a}:!1},write:function(a){return{r:a.r,g:a.g,b:a.b,a:a.a}}},RGB_OBJ:{read:function(c){return a.isNumber(c.r)&& +a.isNumber(c.g)&&a.isNumber(c.b)?{space:"RGB",r:c.r,g:c.g,b:c.b}:!1},write:function(a){return{r:a.r,g:a.g,b:a.b}}},HSVA_OBJ:{read:function(c){return a.isNumber(c.h)&&a.isNumber(c.s)&&a.isNumber(c.v)&&a.isNumber(c.a)?{space:"HSV",h:c.h,s:c.s,v:c.v,a:c.a}:!1},write:function(a){return{h:a.h,s:a.s,v:a.v,a:a.a}}},HSV_OBJ:{read:function(d){return a.isNumber(d.h)&&a.isNumber(d.s)&&a.isNumber(d.v)?{space:"HSV",h:d.h,s:d.s,v:d.v}:!1},write:function(a){return{h:a.h,s:a.s,v:a.v}}}}}];return function(){d=!1; +var c=1\n\n Here\'s the new load parameter for your GUI\'s constructor:\n\n \n\n
\n\n Automatically save\n values to localStorage on exit.\n\n
The values saved to localStorage will\n override those passed to dat.GUI\'s constructor. This makes it\n easier to work incrementally, but localStorage is fragile,\n and your friends may not see the same values you do.\n \n
\n \n
\n\n', +".dg {\n /** Clear list styles */\n /* Auto-place container */\n /* Auto-placed GUI's */\n /* Line items that don't contain folders. */\n /** Folder names */\n /** Hides closed items */\n /** Controller row */\n /** Name-half (left) */\n /** Controller-half (right) */\n /** Controller placement */\n /** Shorter number boxes when slider is present. */\n /** Ensure the entire boolean and function row shows a hand */ }\n .dg ul {\n list-style: none;\n margin: 0;\n padding: 0;\n width: 100%;\n clear: both; }\n .dg.ac {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: 0;\n z-index: 0; }\n .dg:not(.ac) .main {\n /** Exclude mains in ac so that we don't hide close button */\n overflow: hidden; }\n .dg.main {\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear; }\n .dg.main.taller-than-window {\n overflow-y: auto; }\n .dg.main.taller-than-window .close-button {\n opacity: 1;\n /* TODO, these are style notes */\n margin-top: -1px;\n border-top: 1px solid #2c2c2c; }\n .dg.main ul.closed .close-button {\n opacity: 1 !important; }\n .dg.main:hover .close-button,\n .dg.main .close-button.drag {\n opacity: 1; }\n .dg.main .close-button {\n /*opacity: 0;*/\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear;\n border: 0;\n position: absolute;\n line-height: 19px;\n height: 20px;\n /* TODO, these are style notes */\n cursor: pointer;\n text-align: center;\n background-color: #000; }\n .dg.main .close-button:hover {\n background-color: #111; }\n .dg.a {\n float: right;\n margin-right: 15px;\n overflow-x: hidden; }\n .dg.a.has-save > ul {\n margin-top: 27px; }\n .dg.a.has-save > ul.closed {\n margin-top: 0; }\n .dg.a .save-row {\n position: fixed;\n top: 0;\n z-index: 1002; }\n .dg li {\n -webkit-transition: height 0.1s ease-out;\n -o-transition: height 0.1s ease-out;\n -moz-transition: height 0.1s ease-out;\n transition: height 0.1s ease-out; }\n .dg li:not(.folder) {\n cursor: auto;\n height: 27px;\n line-height: 27px;\n overflow: hidden;\n padding: 0 4px 0 5px; }\n .dg li.folder {\n padding: 0;\n border-left: 4px solid rgba(0, 0, 0, 0); }\n .dg li.title {\n cursor: pointer;\n margin-left: -4px; }\n .dg .closed li:not(.title),\n .dg .closed ul li,\n .dg .closed ul li > * {\n height: 0;\n overflow: hidden;\n border: 0; }\n .dg .cr {\n clear: both;\n padding-left: 3px;\n height: 27px; }\n .dg .property-name {\n cursor: default;\n float: left;\n clear: left;\n width: 40%;\n overflow: hidden;\n text-overflow: ellipsis; }\n .dg .c {\n float: left;\n width: 60%; }\n .dg .c input[type=text] {\n border: 0;\n margin-top: 4px;\n padding: 3px;\n width: 100%;\n float: right; }\n .dg .has-slider input[type=text] {\n width: 30%;\n /*display: none;*/\n margin-left: 0; }\n .dg .slider {\n float: left;\n width: 66%;\n margin-left: -5px;\n margin-right: 0;\n height: 19px;\n margin-top: 4px; }\n .dg .slider-fg {\n height: 100%; }\n .dg .c input[type=checkbox] {\n margin-top: 9px; }\n .dg .c select {\n margin-top: 5px; }\n .dg .cr.function,\n .dg .cr.function .property-name,\n .dg .cr.function *,\n .dg .cr.boolean,\n .dg .cr.boolean * {\n cursor: pointer; }\n .dg .selector {\n display: none;\n position: absolute;\n margin-left: -9px;\n margin-top: 23px;\n z-index: 10; }\n .dg .c:hover .selector,\n .dg .selector.drag {\n display: block; }\n .dg li.save-row {\n padding: 0; }\n .dg li.save-row .button {\n display: inline-block;\n padding: 0px 6px; }\n .dg.dialogue {\n background-color: #222;\n width: 460px;\n padding: 15px;\n font-size: 13px;\n line-height: 15px; }\n\n/* TODO Separate style and structure */\n#dg-new-constructor {\n padding: 10px;\n color: #222;\n font-family: Monaco, monospace;\n font-size: 10px;\n border: 0;\n resize: none;\n box-shadow: inset 1px 1px 1px #888;\n word-wrap: break-word;\n margin: 12px 0;\n display: block;\n width: 440px;\n overflow-y: scroll;\n height: 100px;\n position: relative; }\n\n#dg-local-explain {\n display: none;\n font-size: 11px;\n line-height: 17px;\n border-radius: 3px;\n background-color: #333;\n padding: 8px;\n margin-top: 10px; }\n #dg-local-explain code {\n font-size: 10px; }\n\n#dat-gui-save-locally {\n display: none; }\n\n/** Main type */\n.dg {\n color: #eee;\n font: 11px 'Lucida Grande', sans-serif;\n text-shadow: 0 -1px 0 #111;\n /** Auto place */\n /* Controller row,
  • */\n /** Controllers */ }\n .dg.main {\n /** Scrollbar */ }\n .dg.main::-webkit-scrollbar {\n width: 5px;\n background: #1a1a1a; }\n .dg.main::-webkit-scrollbar-corner {\n height: 0;\n display: none; }\n .dg.main::-webkit-scrollbar-thumb {\n border-radius: 5px;\n background: #676767; }\n .dg li:not(.folder) {\n background: #1a1a1a;\n border-bottom: 1px solid #2c2c2c; }\n .dg li.save-row {\n line-height: 25px;\n background: #dad5cb;\n border: 0; }\n .dg li.save-row select {\n margin-left: 5px;\n width: 108px; }\n .dg li.save-row .button {\n margin-left: 5px;\n margin-top: 1px;\n border-radius: 2px;\n font-size: 9px;\n line-height: 7px;\n padding: 4px 4px 5px 4px;\n background: #c5bdad;\n color: #fff;\n text-shadow: 0 1px 0 #b0a58f;\n box-shadow: 0 -1px 0 #b0a58f;\n cursor: pointer; }\n .dg li.save-row .button.gears {\n background: #c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;\n height: 7px;\n width: 8px; }\n .dg li.save-row .button:hover {\n background-color: #bab19e;\n box-shadow: 0 -1px 0 #b0a58f; }\n .dg li.folder {\n border-bottom: 0; }\n .dg li.title {\n padding-left: 16px;\n background: black url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;\n cursor: pointer;\n border-bottom: 1px solid rgba(255, 255, 255, 0.2); }\n .dg .closed li.title {\n background-image: url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==); }\n .dg .cr.boolean {\n border-left: 3px solid #806787; }\n .dg .cr.function {\n border-left: 3px solid #e61d5f; }\n .dg .cr.number {\n border-left: 3px solid #2fa1d6; }\n .dg .cr.number input[type=text] {\n color: #2fa1d6; }\n .dg .cr.string {\n border-left: 3px solid #1ed36f; }\n .dg .cr.string input[type=text] {\n color: #1ed36f; }\n .dg .cr.function:hover, .dg .cr.boolean:hover {\n background: #111; }\n .dg .c input[type=text] {\n background: #303030;\n outline: none; }\n .dg .c input[type=text]:hover {\n background: #3c3c3c; }\n .dg .c input[type=text]:focus {\n background: #494949;\n color: #fff; }\n .dg .c .slider {\n background: #303030;\n cursor: ew-resize; }\n .dg .c .slider-fg {\n background: #2fa1d6; }\n .dg .c .slider:hover {\n background: #3c3c3c; }\n .dg .c .slider:hover .slider-fg {\n background: #44abda; }\n", +dat.controllers.factory=function(e,a,b,d,f,c,p){return function(k,l,q,n){var r=k[l];if(p.isArray(q)||p.isObject(q))return new e(k,l,q);if(p.isNumber(r))return p.isNumber(q)&&p.isNumber(n)?new b(k,l,q,n):new a(k,l,{min:q,max:n});if(p.isString(r))return new d(k,l);if(p.isFunction(r))return new f(k,l,"");if(p.isBoolean(r))return new c(k,l)}}(dat.controllers.OptionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.StringController=function(e,a,b){var d= +function(b,c){function e(){k.setValue(k.__input.value)}d.superclass.call(this,b,c);var k=this;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"keyup",e);a.bind(this.__input,"change",e);a.bind(this.__input,"blur",function(){k.__onFinishChange&&k.__onFinishChange.call(k,k.getValue())});a.bind(this.__input,"keydown",function(a){13===a.keyCode&&this.blur()});this.updateDisplay();this.domElement.appendChild(this.__input)};d.superclass=e;b.extend(d.prototype, +e.prototype,{updateDisplay:function(){a.isActive(this.__input)||(this.__input.value=this.getValue());return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common),dat.controllers.FunctionController,dat.controllers.BooleanController,dat.utils.common),dat.controllers.Controller,dat.controllers.BooleanController,dat.controllers.FunctionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.OptionController, +dat.controllers.ColorController=function(e,a,b,d,f){function c(a,b,d,c){a.style.background="";f.each(l,function(e){a.style.cssText+="background: "+e+"linear-gradient("+b+", "+d+" 0%, "+c+" 100%); "})}function p(a){a.style.background="";a.style.cssText+="background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);";a.style.cssText+="background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"; +a.style.cssText+="background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"}var k=function(e,n){function r(b){t(b);a.bind(window,"mousemove",t);a.bind(window, +"mouseup",l)}function l(){a.unbind(window,"mousemove",t);a.unbind(window,"mouseup",l)}function g(){var a=d(this.value);!1!==a?(s.__color.__state=a,s.setValue(s.__color.toOriginal())):this.value=s.__color.toString()}function h(){a.unbind(window,"mousemove",u);a.unbind(window,"mouseup",h)}function t(b){b.preventDefault();var d=a.getWidth(s.__saturation_field),c=a.getOffset(s.__saturation_field),e=(b.clientX-c.left+document.body.scrollLeft)/d;b=1-(b.clientY-c.top+document.body.scrollTop)/d;1 +b&&(b=0);1e&&(e=0);s.__color.v=b;s.__color.s=e;s.setValue(s.__color.toOriginal());return!1}function u(b){b.preventDefault();var d=a.getHeight(s.__hue_field),c=a.getOffset(s.__hue_field);b=1-(b.clientY-c.top+document.body.scrollTop)/d;1b&&(b=0);s.__color.h=360*b;s.setValue(s.__color.toOriginal());return!1}k.superclass.call(this,e,n);this.__color=new b(this.getValue());this.__temp=new b(0);var s=this;this.domElement=document.createElement("div");a.makeSelectable(this.domElement,!1); +this.__selector=document.createElement("div");this.__selector.className="selector";this.__saturation_field=document.createElement("div");this.__saturation_field.className="saturation-field";this.__field_knob=document.createElement("div");this.__field_knob.className="field-knob";this.__field_knob_border="2px solid ";this.__hue_knob=document.createElement("div");this.__hue_knob.className="hue-knob";this.__hue_field=document.createElement("div");this.__hue_field.className="hue-field";this.__input=document.createElement("input"); +this.__input.type="text";this.__input_textShadow="0 1px 1px ";a.bind(this.__input,"keydown",function(a){13===a.keyCode&&g.call(this)});a.bind(this.__input,"blur",g);a.bind(this.__selector,"mousedown",function(b){a.addClass(this,"drag").bind(window,"mouseup",function(b){a.removeClass(s.__selector,"drag")})});var v=document.createElement("div");f.extend(this.__selector.style,{width:"122px",height:"102px",padding:"3px",backgroundColor:"#222",boxShadow:"0px 1px 3px rgba(0,0,0,0.3)"});f.extend(this.__field_knob.style, +{position:"absolute",width:"12px",height:"12px",border:this.__field_knob_border+(0.5>this.__color.v?"#fff":"#000"),boxShadow:"0px 1px 3px rgba(0,0,0,0.5)",borderRadius:"12px",zIndex:1});f.extend(this.__hue_knob.style,{position:"absolute",width:"15px",height:"2px",borderRight:"4px solid #fff",zIndex:1});f.extend(this.__saturation_field.style,{width:"100px",height:"100px",border:"1px solid #555",marginRight:"3px",display:"inline-block",cursor:"pointer"});f.extend(v.style,{width:"100%",height:"100%", +background:"none"});c(v,"top","rgba(0,0,0,0)","#000");f.extend(this.__hue_field.style,{width:"15px",height:"100px",display:"inline-block",border:"1px solid #555",cursor:"ns-resize"});p(this.__hue_field);f.extend(this.__input.style,{outline:"none",textAlign:"center",color:"#fff",border:0,fontWeight:"bold",textShadow:this.__input_textShadow+"rgba(0,0,0,0.7)"});a.bind(this.__saturation_field,"mousedown",r);a.bind(this.__field_knob,"mousedown",r);a.bind(this.__hue_field,"mousedown",function(b){u(b);a.bind(window, +"mousemove",u);a.bind(window,"mouseup",h)});this.__saturation_field.appendChild(v);this.__selector.appendChild(this.__field_knob);this.__selector.appendChild(this.__saturation_field);this.__selector.appendChild(this.__hue_field);this.__hue_field.appendChild(this.__hue_knob);this.domElement.appendChild(this.__input);this.domElement.appendChild(this.__selector);this.updateDisplay()};k.superclass=e;f.extend(k.prototype,e.prototype,{updateDisplay:function(){var a=d(this.getValue());if(!1!==a){var e=!1; +f.each(b.COMPONENTS,function(b){if(!f.isUndefined(a[b])&&!f.isUndefined(this.__color.__state[b])&&a[b]!==this.__color.__state[b])return e=!0,{}},this);e&&f.extend(this.__color.__state,a)}f.extend(this.__temp.__state,this.__color.__state);this.__temp.a=1;var k=0.5>this.__color.v||0.5a&&(a+=1);return{h:360*a,s:e/c,v:c/255}},rgb_to_hex:function(a,b,d){a=this.hex_with_component(0,2,a);a=this.hex_with_component(a,1,b);return a=this.hex_with_component(a,0,d)},component_from_hex:function(a,b){return a>>8*b&255},hex_with_component:function(a,b,d){return d<<(e=8*b)|a&~(255<
    + @@ -45,16 +32,8 @@ var viewWidth = parent.innerWidth ,viewHeight = parent.innerHeight; - var debugEl = document.getElementById('debug'); - var doDebug = false; - var sp; var bodyStyles = { strokeStyle: '#888', fillStyle: 'transparent', lineWidth: 2, angleIndicator: 'rgba(200, 200, 100, 1)' }; - function debug(){ - var lines = Array.prototype.join.call(arguments, '
    '); - debugEl.innerHTML = lines; - } - function setup(world) { var renderer = Physics.renderer('debug', { @@ -62,7 +41,6 @@ width: viewWidth, height: viewHeight, meta: true, - drawRealPosition: true, styles: { 'circle': bodyStyles ,'rectangle': bodyStyles @@ -105,7 +83,7 @@ world.add([ edgeBounce - ,sp = Physics.behavior('sweep-prune') + ,Physics.behavior('sweep-prune') ,Physics.behavior('body-collision-detection', { checkAll: false }) @@ -162,20 +140,10 @@ grab = false; world.step(); }); - - document.addEventListener('keydown', function( e ){ - if ( e.keyCode === 13 ){ - doDebug = true; - world.step(); - doDebug = false; - } - }); } function addBodies( world ){ - - world.add( Physics.body('circle', { x: viewWidth/2, y: viewHeight - 20,//+100, @@ -245,122 +213,10 @@ } - function setupRenderer( world ){ - - var renderer = world.renderer(); - var styles = { - lineWidth: 2 - ,strokeStyle: 'red' - }; - - var firstPoint = { - fillStyle: 'green' - }; - var secondPoint = { - fillStyle: 'blue' - }; - - var layer = renderer.addLayer('contacts', null, { - zIndex: 2 - }); - - layer.render = function(){}; - - function drawContact( c ){ - var ctx = layer.ctx; - var from = Physics.vector( c.pos ).vadd( c.bodyA.state.pos ); - var to = Physics.vector( from ).vsub( Physics.vector(c.mtv) ) - - renderer.drawLine( from, to, styles, ctx ); - renderer.drawCircle( from.x, from.y, 2, firstPoint, ctx ); - renderer.drawCircle( to.x, to.y, 2, secondPoint, ctx ); - - if ( doDebug ){ - var pad = 100; - ctx.fillStyle = '#333'; - ctx.strokeWidth = 1; - ctx.font = '14px monospace'; - ctx.fillText('pos: (' + c.pos.x.toFixed(0) + ', ' + c.pos.y.toFixed(0) + ')', from.x + pad, from.y ); - ctx.fillText('mtv: (' + c.mtv.x.toFixed(0) + ', ' + c.mtv.y.toFixed(0) + ')', from.x + pad, from.y + 16 ); - } - } - - var intervalStyles = { - min: { strokeStyle: 'rgba( 0, 0, 200, 0.1 )', lineWidth: 2, fillStyle: 'transparent' } - ,max: { strokeStyle: 'rgba( 200, 0, 0, 0.1 )', lineWidth: 2, fillStyle: 'transparent' } - }; - - function drawInterval( intr ){ - var ctx = layer.ctx; - var scratch = Physics.scratchpad(); - var from = scratch.vector().set( intr.val.x, 0 ); - var to = scratch.vector().set( intr.val.x, intr.tracker.body.state.pos.y ); - - renderer.drawLine( from, to, intervalStyles[ intr.type ? 'max' : 'min' ], ctx ); - renderer.drawCircle( from.x, from.y, 4, intervalStyles[ intr.type ? 'max' : 'min' ], ctx ); - - from.set( 0, intr.val.y ); - to.set( intr.tracker.body.state.pos.x, intr.val.y ); - - renderer.drawLine( from, to, intervalStyles[ intr.type ? 'max' : 'min' ], ctx ); - renderer.drawCircle( from.x, from.y, 4, intervalStyles[ intr.type ? 'max' : 'min' ], ctx ); - - scratch.done(); - } - - var colsDetected, candidatesDetected; - - world.on('beforeStep', function(){ - // clear - layer.el.width = layer.el.width; - - candidatesDetected = false; - colsDetected = false; - }); - - world.on('collisions:candidates', function( data ){ - - candidatesDetected = data.candidates; - - }, this, 100); - - - world.on('collisions:detected', function( data ){ - var cols = data.collisions; - - colsDetected = cols; - - for ( var i = 0, l = cols.length; i < l; i++ ){ - drawContact( cols[i] ); - } - }); - - world.on('step', function(){ - - var intr; - for ( var xy = 0; xy < 2; xy++ ){ - intr = sp.intervalLists[ xy ]; - for ( var i = 0, l = intr.length; i < l; i++ ){ - - drawInterval( intr[ i ] ); - } - } - - debug( - 'candidates: ' + ((candidatesDetected && candidatesDetected.length)|0), - 'collisions: ' + ((colsDetected && colsDetected.length)|0) - ); - }); - - world.step(); - } - - Physics({ timestep: 6, maxIPF: 24 }, [ setup ,addBodies //,initEvents - ,setupRenderer ]); diff --git a/test/contact-sandbox.html b/test/contact-sandbox.html index 20fac075..2d80a824 100644 --- a/test/contact-sandbox.html +++ b/test/contact-sandbox.html @@ -16,64 +16,36 @@ .pjs-meta { position: absolute; - bottom: 0; - right: 0; - font-size: 20px; + top: 1em; + right: 1em; + font-size: 16px; font-family: monospace; } - #debug { - position: absolute; - top: 2em; - left: 2em; - background: rgba( 255, 255, 255, 0.4 ); - color: #333; - - font-size: 14px; - line-height: 1.3; - font-family: monospace; - } -
    + From 470cbddbc69cdab541010d5910f64e18f7c49d9d Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Mon, 17 Nov 2014 17:05:08 -0500 Subject: [PATCH 027/110] minor additions to debug renderer --- src/math/vector.js | 148 ++++++++++++++++++++--------------------- src/renderers/debug.js | 73 +++++++++++++++++--- 2 files changed, 136 insertions(+), 85 deletions(-) diff --git a/src/math/vector.js b/src/math/vector.js index 60b9be3b..4479b64a 100644 --- a/src/math/vector.js +++ b/src/math/vector.js @@ -13,9 +13,9 @@ ,typedArrays = !!window.Float64Array ; - /** + /** * class Physics.vector - * + * * The vector class and factory function. * * Call `Physics.vector` with the same arguments as @@ -53,7 +53,7 @@ * - x (Number): The x coordinate * - y (Number): The y coordinate * - vect (Vectorish): A vector-like object to clone - * + * * Vector Constructor. **/ var Vector = function Vector( x, y ) { @@ -95,9 +95,9 @@ }; Object.defineProperties( Vector.prototype, { - /** + /** * Physics.vector#x - * + * * Getter/setter property for the x coordinate. **/ x: { @@ -110,9 +110,9 @@ this._[0] = x; } }, - /** + /** * Physics.vector#y - * + * * Getter/setter property for the y coordinate. **/ y: { @@ -127,15 +127,15 @@ } }); - // + // // Methods - // + // /** * Physics.vector#set( x, y ) -> this * - x (Number): x coordinate * - y (Number): y coordinate - * + * * Sets the x and y components of this vector. **/ Vector.prototype.set = function( x, y ) { @@ -150,7 +150,7 @@ /** deprecated: 0.6.0..1.0.0 * Physics.vector#get( idx ) -> Number * - idx (Number): The coordinate index (0 or 1) - * + * * Get the x or y component by index. **/ Vector.prototype.get = function( n ){ @@ -161,7 +161,7 @@ /** * Physics.vector#vadd( v ) -> this * - v (Physics.vector): vector to add - * + * * Add a [[Physics.vector]] to `this`. **/ Vector.prototype.vadd = function( v ) { @@ -176,7 +176,7 @@ /** * Physics.vector#vsub( v ) -> this * - v (Physics.vector): vector to subtract - * + * * Subtract a [[Physics.vector]] from `this`. **/ Vector.prototype.vsub = function( v ) { @@ -192,11 +192,11 @@ * Physics.vector#add( x, y ) -> this * - x (Number): amount to add to the x coordinate * - y (Number): amount to add to the y coordinate - * + * * Add scalars [[Physics.vector]] to the coordinates. **/ Vector.prototype.add = function( x, y ){ - + this.recalc = true; this._[0] += +x || 0; @@ -208,11 +208,11 @@ * Physics.vector#sub( x, y ) -> this * - x (Number): amount to subtract from the x coordinate * - y (Number): amount to subtract from the y coordinate - * + * * Subtract scalars [[Physics.vector]] from the coordinates. **/ Vector.prototype.sub = function( x, y ){ - + this.recalc = true; this._[0] -= x; @@ -223,13 +223,13 @@ /** * Physics.vector#mult( m ) -> this * - m (Number): amount to multiply this vector by - * + * * Multiply this by a scalar quantity. * * Same as scaling the vector by an amount `m`. **/ Vector.prototype.mult = function( m ) { - + if ( !this.recalc ){ this._[4] *= m * m; @@ -241,10 +241,10 @@ return this; }; - /** + /** * Physics.vector#dot( v ) -> Number * - v (Physics.vector): The other vector - * + * * Compute the dot product of this vector with `v`. **/ Vector.prototype.dot = function( v ) { @@ -252,10 +252,10 @@ return (this._[0] * v._[0]) + (this._[1] * v._[1]); }; - /** + /** * Physics.vector#cross( v ) -> Number * - v (Physics.vector): The other vector - * + * * Compute the (left-handed) cross product of this vector with `v`. **/ Vector.prototype.cross = function( v ) { @@ -266,7 +266,7 @@ /** * Physics.vector#proj( v ) -> Number * - v (Physics.vector): The other vector - * + * * Compute the [scalar projection](http://en.wikipedia.org/wiki/Vector_projection#Scalar_projection_2) of this along `v`. **/ Vector.prototype.proj = function( v ){ @@ -278,7 +278,7 @@ /** * Physics.vector#vproj( v ) -> this * - v (Physics.vector): The other vector - * + * * Compute the [vector projection](http://en.wikipedia.org/wiki/Vector_projection#Vector_projection_2) of this along `v` and copy the result into this vector. **/ Vector.prototype.vproj = function( v ){ @@ -291,7 +291,7 @@ * Physics.vector#angle( [v] ) -> Number * - v (Physics.vector): The other vector * + (Number): The angle in radians between this vector and the x-axis OR `v` if specified - * + * * Compute the angle between `this` and vector `v` or this and x axis. **/ Vector.prototype.angle = function( v ){ @@ -299,7 +299,7 @@ var ang; if ( this.equals( Vector.zero ) ){ - + if ( v ){ return v.angle(); } else { @@ -311,10 +311,10 @@ if ( v && !v.equals( Vector.zero ) ){ ang = atan2( this._[1] * v._[0] - this._[0] * v._[1], this._[0] * v._[0] + this._[1] * v._[1]); } else { - ang = atan2( this._[ 1 ], this._[ 0 ] ); + ang = atan2( this._[ 1 ], this._[ 0 ] ); } } - + while (ang > Math.PI){ ang -= TWOPI; } @@ -330,7 +330,7 @@ * Physics.vector#angle2( left, right ) -> Number * - left (Physics.vector): The position on the left * - right (Physics.vector): The position on the right - * + * * Compute the angle created between three points; left -> this -> right. **/ Vector.prototype.angle2 = function( left, right ){ @@ -355,7 +355,7 @@ /** * Physics.vector#norm() -> Number - * + * * Compute the norm (length) of this vector. **/ Vector.prototype.norm = function() { @@ -365,13 +365,13 @@ this._[4] = (this._[0] * this._[0] + this._[1] * this._[1]); this._[3] = sqrt( this._[4] ); } - + return this._[3]; }; /** * Physics.vector#normSq() -> Number - * + * * Compute the norm (length) squared of this vector. **/ Vector.prototype.normSq = function() { @@ -388,14 +388,14 @@ /** * Physics.vector#dist( v ) -> Number * - v (Physics.vector): The other vector - * + * * Compute the distance from this vector to another vector `v`. **/ Vector.prototype.dist = function( v ) { - + var dx, dy; return sqrt( - (dx = (v._[0] - this._[0])) * dx + + (dx = (v._[0] - this._[0])) * dx + (dy = (v._[1] - this._[1])) * dy ); }; @@ -403,14 +403,14 @@ /** * Physics.vector#distSq( v ) -> Number * - v (Physics.vector): The other vector - * + * * Compute the distance squared from this vector to another vector `v`. **/ Vector.prototype.distSq = function( v ) { var dx, dy; return ( - (dx = (v._[0] - this._[0])) * dx + + (dx = (v._[0] - this._[0])) * dx + (dy = (v._[1] - this._[1])) * dy ); }; @@ -418,7 +418,7 @@ /** * Physics.vector#perp( [ccw] ) -> this * - ccw (Boolean): flag to indicate that we should rotate counterclockwise - * + * * Change this vector into a vector that will be perpendicular. * * In other words, rotate by (+-) 90 degrees. @@ -448,7 +448,7 @@ /** * Physics.vector#normalize() -> this - * + * * Normalise this vector, making it a unit vector. **/ Vector.prototype.normalize = function() { @@ -474,7 +474,7 @@ /** * Physics.vector#transform( t ) -> this * - t (Physics.transform): The transformation to apply - * + * * Apply a [[Physics.transform]] to this vector. **/ Vector.prototype.transform = function( t ){ @@ -490,7 +490,7 @@ // rotate about origin "o" then translate return this.set( - this._[ 0 ] * cosA - this._[ 1 ] * sinA + x + t.v._[ 0 ], + this._[ 0 ] * cosA - this._[ 1 ] * sinA + x + t.v._[ 0 ], this._[ 0 ] * sinA + this._[ 1 ] * cosA + y + t.v._[ 1 ] ); }; @@ -498,7 +498,7 @@ /** * Physics.vector#transformInv( t ) -> this * - t (Physics.transform): The transformation to apply the inverse of - * + * * Apply an inverse [[Physics.transform]] to this vector. **/ Vector.prototype.transformInv = function( t ){ @@ -514,7 +514,7 @@ // inverse translate then inverse rotate about origin "o" return this.set( - this._[ 0 ] * cosA + this._[ 1 ] * sinA + x, + this._[ 0 ] * cosA + this._[ 1 ] * sinA + x, - this._[ 0 ] * sinA + this._[ 1 ] * cosA + y ); }; @@ -525,10 +525,10 @@ * - t (Physics.transform): The transformation to apply the rotational part of * - ang (Number): The angle (in radians), to rotate by * - o (Vectorish): The point of origin of the rotation - * + * * Rotate this vector. - * - * An angle and rotation origin can be specified, + * + * An angle and rotation origin can be specified, * or a transform can be specified and only the rotation * portion of that transform will be applied **/ @@ -551,16 +551,16 @@ } else { sinA = t.sinA; cosA = t.cosA; - + x = t.o._[ 0 ]; y = t.o._[ 1 ]; } - + this._[ 0 ] -= x; this._[ 1 ] -= y; return this.set( - this._[ 0 ] * cosA - this._[ 1 ] * sinA + x, + this._[ 0 ] * cosA - this._[ 1 ] * sinA + x, this._[ 0 ] * sinA + this._[ 1 ] * cosA + y ); }; @@ -568,16 +568,16 @@ /** * Physics.vector#rotateInv( t ) -> this * - t (Physics.transform): The transformation to apply the inverse rotational part of - * + * * Apply the inverse rotation of a transform. - * - * Only the inverse rotation portion of + * + * Only the inverse rotation portion of * that transform will be applied. **/ Vector.prototype.rotateInv = function( t ){ return this.set( - (this._[ 0 ] - t.o._[ 0 ]) * t.cosA + (this._[ 1 ] - t.o._[ 1 ]) * t.sinA + t.o._[ 0 ], + (this._[ 0 ] - t.o._[ 0 ]) * t.cosA + (this._[ 1 ] - t.o._[ 1 ]) * t.sinA + t.o._[ 0 ], -(this._[ 0 ] - t.o._[ 0 ]) * t.sinA + (this._[ 1 ] - t.o._[ 1 ]) * t.cosA + t.o._[ 1 ] ); }; @@ -585,10 +585,10 @@ /** * Physics.vector#translate( t ) -> this * - t (Physics.transform): The transformation to apply the translational part of - * + * * Apply the translation of a transform. - * - * Only the translation portion of + * + * Only the translation portion of * that transform will be applied. **/ Vector.prototype.translate = function( t ){ @@ -599,10 +599,10 @@ /** * Physics.vector#translateInv( t ) -> this * - t (Physics.transform): The transformation to apply the inverse translational part of - * + * * Apply the inverse translation of a transform. - * - * Only the inverse translation portion of + * + * Only the inverse translation portion of * that transform will be applied. **/ Vector.prototype.translateInv = function( t ){ @@ -616,10 +616,10 @@ * - v (Vectorish): The vector-like object to clone * + (this): If `v` is specified as an argument * + (Physics.vector): A new vector instance that clones this vector, if no argument is specified - * + * * Create a clone of this vector, or clone another vector into this instance. * - * This is especially useful in vector algorithms + * This is especially useful in vector algorithms * that use temporary vectors (which most should). * You can create temporary vectors and then do things like... * ``` @@ -630,7 +630,7 @@ * ``` **/ Vector.prototype.clone = function( v ) { - + // http://jsperf.com/vector-storage-test if ( v ){ @@ -639,7 +639,7 @@ return this.set( v.x, v.y ); } - + this.recalc = v.recalc; if (!v.recalc){ @@ -659,7 +659,7 @@ /** * Physics.vector#swap( v ) -> this * - v (Physics.vector): The other vector - * + * * Swap values with other vector. **/ Vector.prototype.swap = function( v ){ @@ -676,7 +676,7 @@ /** * Physics.vector#values() -> Object - * + * * Get the coordinate values as an object literal. **/ Vector.prototype.values = function(){ @@ -690,7 +690,7 @@ /** * Physics.vector#zero() -> this - * + * * Set the coordinates of this vector to zero. **/ Vector.prototype.zero = function() { @@ -705,7 +705,7 @@ /** * Physics.vector#negate() -> this - * + * * Flip this vector in the opposite direction. **/ Vector.prototype.negate = function( component ){ @@ -725,9 +725,9 @@ * Physics.vector#clamp( minV, maxV ) -> this * - minV (Vectorish): The minimum vector * - maxV (Vectorish): The maximum vector - * + * * Constrain vector components to minima and maxima. - * + * * The vector analog of [scalar clamping](http://en.wikipedia.org/wiki/Clamping_(graphics)). **/ Vector.prototype.clamp = function( minV, maxV ){ @@ -740,7 +740,7 @@ /** * Physics.vector#toString() -> String - * + * * Get a formatted string of this vector's coordinates. **/ Vector.prototype.toString = function(){ @@ -752,7 +752,7 @@ /** * Physics.vector#equals( v ) -> Boolean * - v (Physics.vector): The other vector - * + * * Determine if this vector equals another. **/ Vector.prototype.equals = function( v ){ @@ -764,7 +764,7 @@ /** * Physics.vector.axis = Array - * + * * Read-only axis vectors for general reference. * * Example: @@ -781,7 +781,7 @@ /** * Physics.vector.zero = zeroVector - * + * * Read-only zero vector for reference **/ Vector.zero = new Vector(0, 0); diff --git a/src/renderers/debug.js b/src/renderers/debug.js index f8deb330..d9e1e58b 100644 --- a/src/renderers/debug.js +++ b/src/renderers/debug.js @@ -122,6 +122,8 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ world.on('sweep-prune:intervals', this.storeIntervals, this ); world.on('collisions:detected', this.storeCollisions, this ); world.on('render', this.reset, this); + + this.updateGui(); }, disconnect: function( world ){ @@ -183,22 +185,63 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ initGui: function(){ - var gui = new window.dat.GUI({ autoPlace: false }) + var self = this + ,gui = this.gui = new window.dat.GUI({ autoPlace: false }) ,el = document.getElementById('my-gui-container') ,op = this.options + ,getset + ,f ; - gui.add( op, 'drawAABB' ); - gui.add( op, 'drawRealPosition' ); - gui.add( op, 'drawIntervals' ); - gui.add( op, 'drawContacts' ); + getset = { + get timestep(){ + return self._world ? self._world.timestep() : 6; + } + ,set timestep( dt ){ + if ( self._world ) { + self._world.timestep( dt ); + } + } + ,get maxIPF(){ + return self._world ? self._world.options.maxIPF : 16; + } + ,set maxIPF( m ){ + if ( self._world ){ + self._world.options({ maxIPF: m }); + } + } + }; - gui.addColor( op, 'aabbColor' ); - gui.addColor( op, 'realBodyStyle' ); - gui.addColor( op, 'intervalMinColor' ); - gui.addColor( op, 'intervalMaxColor' ); - gui.addColor( op, 'mtvColor' ); - gui.addColor( op, 'contactColor' ); + function pauseWorld(){ + if ( self._world ){ + if ( self._world.isPaused() ){ + self._world.unpause(); + } else { + self._world.pause(); + } + } + } + + f = gui.addFolder( 'General' ); + f.add( getset, 'timestep', 1, 20).step( 1 ); + f.add( getset, 'maxIPF', 1, 100).step( 1 ); + f.add( { pause: pauseWorld }, 'pause'); + f.open(); + + f = gui.addFolder( 'Draw Options' ); + f.add( op, 'drawAABB' ); + f.add( op, 'drawRealPosition' ); + f.add( op, 'drawIntervals' ); + f.add( op, 'drawContacts' ); + f.open(); + + f = gui.addFolder( 'Colors' ); + f.addColor( op, 'aabbColor' ); + f.addColor( op, 'realBodyStyle' ); + f.addColor( op, 'intervalMinColor' ); + f.addColor( op, 'intervalMaxColor' ); + f.addColor( op, 'mtvColor' ); + f.addColor( op, 'contactColor' ); gui.domElement.style.zIndex = '100'; gui.domElement.style.position = 'absolute'; @@ -207,6 +250,14 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ this.el.parentNode.appendChild( gui.domElement ); }, + updateGui: function(){ + var gui = this.gui; + // Iterate over all controllers + for (var i in gui.__controllers) { + gui.__controllers[i].updateDisplay(); + } + }, + drawBody: function( body, view, ctx, offset ){ var pos = body.state.pos From 454a99a1ad55861b8f1f59b3aec54cc8856d5dbb Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Mon, 17 Nov 2014 17:14:39 -0500 Subject: [PATCH 028/110] minor fixes to unit tests --- src/renderers/canvas.js | 1 + src/renderers/pixi-renderer.js | 2 ++ test/grunt.tmpl | 29 ++++++++++++++++++++++++++++- test/r.js.spec.helper.js | 2 +- test/requirejs.spec.helper.js | 2 +- test/spec/world-add-remove.spec.js | 14 ++++++++++---- 6 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/renderers/canvas.js b/src/renderers/canvas.js index 09ff6c06..313ca01a 100644 --- a/src/renderers/canvas.js +++ b/src/renderers/canvas.js @@ -340,6 +340,7 @@ Physics.renderer('canvas', function( proto ){ ,view ,i ,l = bodies.length + ,t = self._interpolateTime ,stack = (l || layer.id !== 'main') ? bodies : self._world._bodies ; diff --git a/src/renderers/pixi-renderer.js b/src/renderers/pixi-renderer.js index 14449e2a..31478c1a 100644 --- a/src/renderers/pixi-renderer.js +++ b/src/renderers/pixi-renderer.js @@ -82,6 +82,8 @@ Physics.renderer('pixi', function( parent ){ // extended init: function( options ){ + var self = this; + if (typeof PIXI === 'undefined') { throw "PIXI obj not present - cannot continue "; } diff --git a/test/grunt.tmpl b/test/grunt.tmpl index dd3231e5..d9b3074e 100644 --- a/test/grunt.tmpl +++ b/test/grunt.tmpl @@ -4,6 +4,33 @@ Jasmine Spec Runner + <% with (scripts) { %> <% [].concat(jasmine, vendor, helpers, src, specs, reporters, start).forEach(function(script){ %> @@ -11,4 +38,4 @@ <% }; %> - \ No newline at end of file + diff --git a/test/r.js.spec.helper.js b/test/r.js.spec.helper.js index b6306608..230f9df2 100644 --- a/test/r.js.spec.helper.js +++ b/test/r.js.spec.helper.js @@ -1 +1 @@ -require(["physicsjs/geometries/circle","physicsjs/geometries/convex-polygon","physicsjs/geometries/rectangle","physicsjs/bodies/circle","physicsjs/bodies/convex-polygon","physicsjs/bodies/rectangle","physicsjs/behaviors/attractor","physicsjs/behaviors/body-collision-detection","physicsjs/behaviors/body-impulse-response","physicsjs/behaviors/constant-acceleration","physicsjs/behaviors/edge-collision-detection","physicsjs/behaviors/interactive","physicsjs/behaviors/newtonian","physicsjs/behaviors/sweep-prune","physicsjs/behaviors/verlet-constraints","physicsjs/integrators/improved-euler","physicsjs/renderers/canvas","physicsjs/renderers/dom","physicsjs/renderers/pixi-renderer"]); \ No newline at end of file +require(["physicsjs/geometries/circle","physicsjs/geometries/convex-polygon","physicsjs/geometries/rectangle","physicsjs/bodies/circle","physicsjs/bodies/convex-polygon","physicsjs/bodies/rectangle","physicsjs/behaviors/attractor","physicsjs/behaviors/body-collision-detection","physicsjs/behaviors/body-impulse-response","physicsjs/behaviors/constant-acceleration","physicsjs/behaviors/edge-collision-detection","physicsjs/behaviors/interactive","physicsjs/behaviors/newtonian","physicsjs/behaviors/sweep-prune","physicsjs/behaviors/verlet-constraints","physicsjs/integrators/improved-euler","physicsjs/integrators/velocity-verlet-alt","physicsjs/integrators/velocity-verlet","physicsjs/renderers/canvas","physicsjs/renderers/debug","physicsjs/renderers/dom","physicsjs/renderers/pixi-renderer"]); \ No newline at end of file diff --git a/test/requirejs.spec.helper.js b/test/requirejs.spec.helper.js index 8c4fc6ee..d87e724a 100644 --- a/test/requirejs.spec.helper.js +++ b/test/requirejs.spec.helper.js @@ -1 +1 @@ -var cfg = {"modules":["physicsjs/geometries/circle","physicsjs/geometries/convex-polygon","physicsjs/geometries/rectangle","physicsjs/bodies/circle","physicsjs/bodies/convex-polygon","physicsjs/bodies/rectangle","physicsjs/behaviors/attractor","physicsjs/behaviors/body-collision-detection","physicsjs/behaviors/body-impulse-response","physicsjs/behaviors/constant-acceleration","physicsjs/behaviors/edge-collision-detection","physicsjs/behaviors/interactive","physicsjs/behaviors/newtonian","physicsjs/behaviors/sweep-prune","physicsjs/behaviors/verlet-constraints","physicsjs/integrators/improved-euler","physicsjs/renderers/canvas","physicsjs/renderers/dom","physicsjs/renderers/pixi-renderer"]}; \ No newline at end of file +var cfg = {"modules":["physicsjs/geometries/circle","physicsjs/geometries/convex-polygon","physicsjs/geometries/rectangle","physicsjs/bodies/circle","physicsjs/bodies/convex-polygon","physicsjs/bodies/rectangle","physicsjs/behaviors/attractor","physicsjs/behaviors/body-collision-detection","physicsjs/behaviors/body-impulse-response","physicsjs/behaviors/constant-acceleration","physicsjs/behaviors/edge-collision-detection","physicsjs/behaviors/interactive","physicsjs/behaviors/newtonian","physicsjs/behaviors/sweep-prune","physicsjs/behaviors/verlet-constraints","physicsjs/integrators/improved-euler","physicsjs/integrators/velocity-verlet-alt","physicsjs/integrators/velocity-verlet","physicsjs/renderers/canvas","physicsjs/renderers/debug","physicsjs/renderers/dom","physicsjs/renderers/pixi-renderer"]}; \ No newline at end of file diff --git a/test/spec/world-add-remove.spec.js b/test/spec/world-add-remove.spec.js index ae3d2813..4f4d9b1c 100644 --- a/test/spec/world-add-remove.spec.js +++ b/test/spec/world-add-remove.spec.js @@ -86,7 +86,7 @@ describe("Adding and Removing things from world", function() { it("should add an array of items", function() { - world.add([ + world.add([ circle, square, sweepPrune, @@ -103,7 +103,7 @@ describe("Adding and Removing things from world", function() { it("should remove an array of items", function() { - world.remove([ + world.remove([ circle, square, sweepPrune, @@ -118,9 +118,15 @@ describe("Adding and Removing things from world", function() { expect( callbacks.removedBehaviors.calls.length ).toEqual( 2 ); }); + it("should behave nicely for empty arrays", function() { + + world.add([]); + world.remove([]); + }); + it("should not allow duplicates", function() { - world.add([ + world.add([ circle, circle, square, @@ -137,4 +143,4 @@ describe("Adding and Removing things from world", function() { expect( callbacks.removedBehaviors.calls.length ).toEqual( 0 ); }); -}); \ No newline at end of file +}); From 72d595f3a25f3c1709c83f4e32ffd4d87dbba3f1 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Mon, 17 Nov 2014 17:37:12 -0500 Subject: [PATCH 029/110] fixes to unit tests --- gruntfile.js | 7 +++++-- lib/raf.js | 25 +++++++++++++++++++++++++ test/grunt.tmpl | 41 ----------------------------------------- 3 files changed, 30 insertions(+), 43 deletions(-) create mode 100644 lib/raf.js delete mode 100644 test/grunt.tmpl diff --git a/gruntfile.js b/gruntfile.js index e7d8177e..015ac481 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -239,13 +239,14 @@ module.exports = function(grunt) { dev : { src : config.devFull, options : { + helpers: 'lib/raf.js', specs : 'test/spec/*.spec.js', template : 'test/grunt.tmpl' } }, devRequireJS : { options : { - helpers: 'test/requirejs.spec.helper.js', + helpers: ['lib/raf.js', 'test/requirejs.spec.helper.js'], specs : 'test/requirejs.spec.js', template : require('grunt-template-jasmine-requirejs'), templateOptions: { @@ -265,13 +266,14 @@ module.exports = function(grunt) { dist : { src : config.distFull, options : { + helpers: 'lib/raf.js', specs : 'test/spec/*.spec.js', template : 'test/grunt.tmpl' } }, distRequireJS : { options : { - helpers: 'test/requirejs.spec.helper.js', + helpers: ['lib/raf.js', 'test/requirejs.spec.helper.js'], specs : 'test/requirejs.spec.js', template : require('grunt-template-jasmine-requirejs'), templateOptions: { @@ -281,6 +283,7 @@ module.exports = function(grunt) { }, distRequireJSBuild : { options : { + helpers: 'lib/raf.js', specs : 'test/requirejs.build.spec.js', template : require('grunt-template-jasmine-requirejs'), templateOptions: { diff --git a/lib/raf.js b/lib/raf.js new file mode 100644 index 00000000..6ee0f861 --- /dev/null +++ b/lib/raf.js @@ -0,0 +1,25 @@ +(function(window) { + var lastTime = 0; + var vendors = ['ms', 'moz', 'webkit', 'o']; + for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; + window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; + } + + if (!window.requestAnimationFrame){ + window.requestAnimationFrame = function(callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function() { callback(currTime + timeToCall); }, + timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + } + + if (!window.cancelAnimationFrame){ + window.cancelAnimationFrame = function(id) { + clearTimeout(id); + }; + } +}(this)); diff --git a/test/grunt.tmpl b/test/grunt.tmpl deleted file mode 100644 index d9b3074e..00000000 --- a/test/grunt.tmpl +++ /dev/null @@ -1,41 +0,0 @@ - - - - - Jasmine Spec Runner - - - <% with (scripts) { %> - <% [].concat(jasmine, vendor, helpers, src, specs, reporters, start).forEach(function(script){ %> - - <% }) %> - <% }; %> - - - From ded6ad0acb47701fdd22f2584f6eb4019c9f0dc4 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Mon, 17 Nov 2014 17:37:20 -0500 Subject: [PATCH 030/110] fixes to unit tests --- test/grunt.tmpl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 test/grunt.tmpl diff --git a/test/grunt.tmpl b/test/grunt.tmpl new file mode 100644 index 00000000..2010fb96 --- /dev/null +++ b/test/grunt.tmpl @@ -0,0 +1,14 @@ + + + + + Jasmine Spec Runner + + <% with (scripts) { %> + <% [].concat(jasmine, vendor, helpers, src, specs, reporters, start).forEach(function(script){ %> + + <% }) %> + <% }; %> + + + From c7cd3487e4f75921f2affad4da3c2ff080916430 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Mon, 17 Nov 2014 17:37:59 -0500 Subject: [PATCH 031/110] fixes #95. empty arrays handled --- src/core/world.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/world.js b/src/core/world.js index 6f44d138..f9c50239 100644 --- a/src/core/world.js +++ b/src/core/world.js @@ -186,7 +186,7 @@ var i = 0 ,len = arg && arg.length || 0 - ,thing = len ? arg[ 0 ] : arg + ,thing = Physics.util.isArray( arg ) ? arg[ 0 ] : arg ; if ( !thing ){ @@ -234,7 +234,7 @@ var i = 0 ,len = arg && arg.length || 0 - ,thing = len ? arg[ 0 ] : arg + ,thing = Physics.util.isArray( arg ) ? arg[ 0 ] : arg ; if ( !thing ){ From 8f9d8d56df09bfd49a03f0798c2d5619743def7c Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Mon, 17 Nov 2014 22:56:44 -0500 Subject: [PATCH 032/110] fixes #92. pubsub handles fns on prototype better --- src/behaviors/body-collision-detection.js | 4 +-- src/behaviors/body-impulse-response.js | 2 +- src/behaviors/edge-collision-detection.js | 2 +- src/behaviors/interactive.js | 2 +- src/behaviors/sweep-prune.js | 6 ++-- src/behaviors/verlet-constraints.js | 2 +- src/core/behavior.js | 2 +- src/renderers/debug.js | 6 ++-- src/renderers/dom.js | 4 +-- src/util/pubsub.js | 11 +++++-- test/spec/pubsub.spec.js | 35 ++++++++++++++++++++++- 11 files changed, 57 insertions(+), 19 deletions(-) diff --git a/src/behaviors/body-collision-detection.js b/src/behaviors/body-collision-detection.js index e8aefa2f..294c5c31 100644 --- a/src/behaviors/body-collision-detection.js +++ b/src/behaviors/body-collision-detection.js @@ -273,11 +273,11 @@ Physics.behavior('body-collision-detection', function( parent ){ if ( this.options.check === true ){ - world.off( 'integrate:velocities', this.checkAll ); + world.off( 'integrate:velocities', this.checkAll, this ); } else { - world.off( this.options.check, this.check ); + world.off( this.options.check, this.check, this ); } }, diff --git a/src/behaviors/body-impulse-response.js b/src/behaviors/body-impulse-response.js index 25f9e3bc..cd617b0d 100644 --- a/src/behaviors/body-impulse-response.js +++ b/src/behaviors/body-impulse-response.js @@ -46,7 +46,7 @@ Physics.behavior('body-impulse-response', function( parent ){ // extended disconnect: function( world ){ - world.off( this.options.check, this.respond ); + world.off( this.options.check, this.respond, this ); }, /** internal diff --git a/src/behaviors/edge-collision-detection.js b/src/behaviors/edge-collision-detection.js index addedacb..b261d31c 100644 --- a/src/behaviors/edge-collision-detection.js +++ b/src/behaviors/edge-collision-detection.js @@ -214,7 +214,7 @@ Physics.behavior('edge-collision-detection', function( parent ){ // extended disconnect: function( world ){ - world.off( 'integrate:velocities', this.checkAll ); + world.off( 'integrate:velocities', this.checkAll, this ); }, /** internal diff --git a/src/behaviors/interactive.js b/src/behaviors/interactive.js index 312bd356..e48e5e06 100644 --- a/src/behaviors/interactive.js +++ b/src/behaviors/interactive.js @@ -209,7 +209,7 @@ Physics.behavior('interactive', function( parent ){ disconnect: function( world ){ // unsubscribe when disconnected - world.off('integrate:positions', this.behave); + world.off('integrate:positions', this.behave, this); }, // extended diff --git a/src/behaviors/sweep-prune.js b/src/behaviors/sweep-prune.js index 0cbec60a..956259f7 100644 --- a/src/behaviors/sweep-prune.js +++ b/src/behaviors/sweep-prune.js @@ -80,9 +80,9 @@ Physics.behavior('sweep-prune', function( parent ){ // extended disconnect: function( world ){ - world.off( 'add:body', this.trackBody ); - world.off( 'remove:body', this.untrackBody ); - world.off( 'integrate:velocities', this.sweep ); + world.off( 'add:body', this.trackBody, this ); + world.off( 'remove:body', this.untrackBody, this ); + world.off( 'integrate:velocities', this.sweep, this ); this.clear(); }, diff --git a/src/behaviors/verlet-constraints.js b/src/behaviors/verlet-constraints.js index 60400ed3..33b222e0 100644 --- a/src/behaviors/verlet-constraints.js +++ b/src/behaviors/verlet-constraints.js @@ -49,7 +49,7 @@ Physics.behavior('verlet-constraints', function( parent ){ // extended disconnect: function( world ){ - world.off('integrate:positions', this.resolve); + world.off('integrate:positions', this.resolve, this); }, /** diff --git a/src/core/behavior.js b/src/core/behavior.js index 3a0ce180..bb2dad48 100644 --- a/src/core/behavior.js +++ b/src/core/behavior.js @@ -135,7 +135,7 @@ disconnect: function( world ){ if (this.behave){ - world.off('integrate:positions', this.behave); + world.off('integrate:positions', this.behave, this); } }, diff --git a/src/renderers/debug.js b/src/renderers/debug.js index d9e1e58b..b2cad719 100644 --- a/src/renderers/debug.js +++ b/src/renderers/debug.js @@ -128,9 +128,9 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ disconnect: function( world ){ - world.off('sweep-prune:intervals', this.storeIntervals ); - world.off('collisions:detected', this.storeCollisions ); - world.off('render', this.reset); + world.off('sweep-prune:intervals', this.storeIntervals, this ); + world.off('collisions:detected', this.storeCollisions, this ); + world.off('render', this.reset, this); }, storeIntervals: function( intervals ){ diff --git a/src/renderers/dom.js b/src/renderers/dom.js index 73573240..e23c0e68 100644 --- a/src/renderers/dom.js +++ b/src/renderers/dom.js @@ -159,8 +159,8 @@ Physics.renderer('dom', function( proto ){ // extended disconnect: function( world ){ - world.off( 'add:body', this.attach ); - world.off( 'remove:body', this.detach ); + world.off( 'add:body', this.attach, this ); + world.off( 'remove:body', this.detach, this ); }, /** diff --git a/src/util/pubsub.js b/src/util/pubsub.js index 0e58b913..818a54b3 100644 --- a/src/util/pubsub.js +++ b/src/util/pubsub.js @@ -68,6 +68,7 @@ fn = Physics.util.bind( fn, scope ); fn._bindfn_ = orig; fn._one_ = orig._one_; + fn._scope_ = scope; } else if ( priority === undefined ) { @@ -83,15 +84,16 @@ }, /** - * Physics.util.pubsub#off( topic, fn ) -> this + * Physics.util.pubsub#off( topic, fn[, scope] ) -> this * Physics.util.pubsub#off( topicCfg ) -> this * - topic (String): topic The topic name. Specify `true` to remove all listeners for all topics * - topicCfg (Object): A config with key/value pairs of `{ topic: callbackFn, ... }` * - fn (Function): The original callback function. Specify `true` to remove all listeners for specified topic + * - scope (Object): The scope the callback was bound to. This is important if you are binding methods that come from object prototypes. * * Unsubscribe callback(s) from topic(s). **/ - off: function( topic, fn ){ + off: function( topic, fn, scope ){ var listeners ,listn @@ -136,7 +138,10 @@ listn = listeners[ i ]; - if ( listn._bindfn_ === fn || listn === fn ){ + if ( + (listn._bindfn_ === fn || listn === fn) && + ( (!scope) || listn._scope_ === scope) // check the scope too if specified + ){ listeners.splice( i, 1 ); break; } diff --git a/test/spec/pubsub.spec.js b/test/spec/pubsub.spec.js index d89356ef..c3eec6ab 100644 --- a/test/spec/pubsub.spec.js +++ b/test/spec/pubsub.spec.js @@ -57,4 +57,37 @@ describe("PubSub events", function() { expect( callbacks.calledOnce.calls.length ).toEqual( 1 ); expect( callbacks.notCalled ).not.toHaveBeenCalled(); }); -}); \ No newline at end of file + + it("should handle prototype assignment correctly", function() { + + var Cat = function(){ + + this.purred = false; + }; + + Cat.prototype = { + track: function( ps ){ + ps.on('pet', this.purr, this); + } + ,untrack: function( ps ){ + ps.off('pet', this.purr, this); + } + ,purr: function(){ + this.purred = true; + } + }; + + var felix = new Cat() + ,sylvester = new Cat() + ; + + felix.track( ps ); + sylvester.track( ps ); + felix.untrack( ps ); + + ps.emit('pet'); + + expect( sylvester.purred ).toBe( true ); + expect( felix.purred ).toBe( false ); + }); +}); From 374247fc9b16b4065e0192984b6e84d968e155aa Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Mon, 17 Nov 2014 23:49:13 -0500 Subject: [PATCH 033/110] re #35 adding "new" to objects --- src/behaviors/constant-acceleration.js | 2 +- src/behaviors/sweep-prune.js | 4 ++-- src/core/geometry-helpers.js | 14 +++++++------- src/core/geometry.js | 4 ++-- src/core/query.js | 2 +- src/geometries/circle.js | 4 ++-- src/geometries/convex-polygon.js | 4 ++-- src/math/transform.js | 4 ++-- src/renderers/canvas.js | 2 +- src/renderers/pixi-renderer.js | 2 +- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/behaviors/constant-acceleration.js b/src/behaviors/constant-acceleration.js index 8ab02356..90fbdbfa 100644 --- a/src/behaviors/constant-acceleration.js +++ b/src/behaviors/constant-acceleration.js @@ -27,7 +27,7 @@ Physics.behavior('constant-acceleration', function( parent ){ this.options( options ); // extend options - this._acc = Physics.vector(); + this._acc = new Physics.vector(); this.setAcceleration( this.options.acc ); delete this.options.acc; }, diff --git a/src/behaviors/sweep-prune.js b/src/behaviors/sweep-prune.js index 956259f7..d64752ef 100644 --- a/src/behaviors/sweep-prune.js +++ b/src/behaviors/sweep-prune.js @@ -402,13 +402,13 @@ Physics.behavior('sweep-prune', function( parent ){ min: { type: false, //min - val: Physics.vector(), + val: new Physics.vector(), tracker: tracker }, max: { type: true, //max - val: Physics.vector(), + val: new Physics.vector(), tracker: tracker } } diff --git a/src/core/geometry-helpers.js b/src/core/geometry-helpers.js index 8837641b..34ef5dcb 100644 --- a/src/core/geometry-helpers.js +++ b/src/core/geometry-helpers.js @@ -215,7 +215,7 @@ Physics.geometry.getPolygonCentroid = function getPolygonCentroid( hull ){ var scratch = Physics.scratchpad() ,prev = scratch.vector() ,next = scratch.vector() - ,ret = Physics.vector() + ,ret = new Physics.vector() ,tmp ,l = hull.length ; @@ -223,14 +223,14 @@ Physics.geometry.getPolygonCentroid = function getPolygonCentroid( hull ){ if ( l < 2 ){ // it must be a point scratch.done(); - return Physics.vector( hull[0] ); + return new Physics.vector( hull[0] ); } if ( l === 2 ){ // it's a line // get the midpoint scratch.done(); - return Physics.vector((hull[ 1 ].x + hull[ 0 ].x)/2, (hull[ 1 ].y + hull[ 0 ].y)/2 ); + return new Physics.vector((hull[ 1 ].x + hull[ 0 ].x)/2, (hull[ 1 ].y + hull[ 0 ].y)/2 ); } prev.clone( hull[ l - 1 ] ); @@ -275,7 +275,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, // oh.. it's a zero vector. So A and B are both the closest. // just use one of them scratch.done(); - return Physics.vector( linePt1 ); + return new Physics.vector( linePt1 ); } lambdaB = - L.dot( A ) / L.normSq(); @@ -285,15 +285,15 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, // woops.. that means the closest simplex point // isn't on the line it's point B itself scratch.done(); - return Physics.vector( linePt2 ); + return new Physics.vector( linePt2 ); } else if ( lambdaB <= 0 ){ // vice versa scratch.done(); - return Physics.vector( linePt1 ); + return new Physics.vector( linePt1 ); } // guess we'd better do the math now... - p = Physics.vector( linePt2 ).mult( lambdaB ).vadd( A.clone( linePt1 ).mult( lambdaA ) ); + p = new Physics.vector( linePt2 ).mult( lambdaB ).vadd( A.clone( linePt1 ).mult( lambdaA ) ); scratch.done(); return p; }; diff --git a/src/core/geometry.js b/src/core/geometry.js index 31f91905..7896c669 100644 --- a/src/core/geometry.js +++ b/src/core/geometry.js @@ -81,7 +81,7 @@ **/ getFarthestHullPoint: function( dir, result ){ - result = result || Physics.vector(); + result = result || new Physics.vector(); // not implemented. return result.set( 0, 0 ); @@ -103,7 +103,7 @@ **/ getFarthestCorePoint: function( dir, result, margin ){ - result = result || Physics.vector(); + result = result || new Physics.vector(); // not implemented. return result.set( 0, 0 ); diff --git a/src/core/query.js b/src/core/query.js index e6341471..5294eb25 100644 --- a/src/core/query.js +++ b/src/core/query.js @@ -146,7 +146,7 @@ * Get a test function to match any body who's aabb intersects point **/ var $at = function $at( point ){ - point = Physics.vector( point ); + point = new Physics.vector( point ); return function( body ){ var aabb = body.aabb(); return Physics.aabb.contains( aabb, point ); diff --git a/src/geometries/circle.js b/src/geometries/circle.js index fe19dacc..62f028a2 100644 --- a/src/geometries/circle.js +++ b/src/geometries/circle.js @@ -62,7 +62,7 @@ Physics.geometry('circle', function( parent ){ // extended getFarthestHullPoint: function( dir, result ){ - result = result || Physics.vector(); + result = result || new Physics.vector(); return result.clone( dir ).normalize().mult( this.radius ); }, @@ -70,7 +70,7 @@ Physics.geometry('circle', function( parent ){ // extended getFarthestCorePoint: function( dir, result, margin ){ - result = result || Physics.vector(); + result = result || new Physics.vector(); // we can use the center of the circle as the core object // because we can project a point to the hull in any direction diff --git a/src/geometries/convex-polygon.js b/src/geometries/convex-polygon.js index 8a42f663..d8bf99c1 100644 --- a/src/geometries/convex-polygon.js +++ b/src/geometries/convex-polygon.js @@ -76,7 +76,7 @@ Physics.geometry('convex-polygon', function( parent ){ // then add the vertex as a vector to this.vertices for ( var i = 0, l = hull.length; i < l; ++i ){ - verts.push( Physics.vector( hull[ i ] ).translate( transl ) ); + verts.push( new Physics.vector( hull[ i ] ).translate( transl ) ); } this._area = Physics.geometry.getPolygonArea( verts ); @@ -128,7 +128,7 @@ Physics.geometry('convex-polygon', function( parent ){ ,idx ; - result = result || Physics.vector(); + result = result || new Physics.vector(); if ( l < 2 ){ if ( data ){ diff --git a/src/math/transform.js b/src/math/transform.js index 7bcf6880..f088cc7f 100644 --- a/src/math/transform.js +++ b/src/math/transform.js @@ -22,8 +22,8 @@ return new Transform( vect, angle ); } - this.v = Physics.vector(); - this.o = Physics.vector(); // origin of rotation + this.v = new Physics.vector(); + this.o = new Physics.vector(); // origin of rotation if ( vect instanceof Transform ){ diff --git a/src/renderers/canvas.js b/src/renderers/canvas.js index 313ca01a..169d27e0 100644 --- a/src/renderers/canvas.js +++ b/src/renderers/canvas.js @@ -101,7 +101,7 @@ Physics.renderer('canvas', function( proto ){ // further options this.options.defaults( defaults, true ); this.options.onChange(function(){ - self.options.offset = Physics.vector( self.options.offset ); + self.options.offset = new Physics.vector( self.options.offset ); }); this.options( options, true ); diff --git a/src/renderers/pixi-renderer.js b/src/renderers/pixi-renderer.js index 31478c1a..17048b87 100644 --- a/src/renderers/pixi-renderer.js +++ b/src/renderers/pixi-renderer.js @@ -94,7 +94,7 @@ Physics.renderer('pixi', function( parent ){ // further options this.options.defaults( defaults, true ); this.options.onChange(function(){ - self.options.offset = Physics.vector( self.options.offset ); + self.options.offset = new Physics.vector( self.options.offset ); }); this.options( options, true ); From cabe1eb3c8c72bdc4f618b342523f644788515a1 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Mon, 17 Nov 2014 23:52:40 -0500 Subject: [PATCH 034/110] re #35 adding "new" to object creation --- src/behaviors/body-collision-detection.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/behaviors/body-collision-detection.js b/src/behaviors/body-collision-detection.js index 294c5c31..466b0b42 100644 --- a/src/behaviors/body-collision-detection.js +++ b/src/behaviors/body-collision-detection.js @@ -71,8 +71,8 @@ Physics.behavior('body-collision-detection', function( parent ){ }); }; - fn.tA = Physics.transform(); - fn.tB = Physics.transform(); + fn.tA = new Physics.transform(); + fn.tB = new Physics.transform(); } fn.useCore = false; From b76f06103b92bb1bc6c1160074e0520bf083e768 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Tue, 18 Nov 2014 00:45:15 -0500 Subject: [PATCH 035/110] minor optimization --- src/behaviors/sweep-prune.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/behaviors/sweep-prune.js b/src/behaviors/sweep-prune.js index d64752ef..226caae0 100644 --- a/src/behaviors/sweep-prune.js +++ b/src/behaviors/sweep-prune.js @@ -322,7 +322,7 @@ Physics.behavior('sweep-prune', function( parent ){ // if it's the x axis, create a pair c = this.getPair( tr1, tr2, isX ); - if ( c ){ + if ( c && c.flag < collisionFlag ){ // if it's greater than the axis index, set the flag // to = 0. From 2715a056aa3a0f75faf500f6f5cd1e5e5ddc6953 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Wed, 19 Nov 2014 13:31:26 -0500 Subject: [PATCH 036/110] nothing changes --- test/integrator-sandbox.html | 52 +++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/test/integrator-sandbox.html b/test/integrator-sandbox.html index 6650df72..41aefe56 100644 --- a/test/integrator-sandbox.html +++ b/test/integrator-sandbox.html @@ -36,6 +36,14 @@ this.k = opts.k || 1; this.center = Physics.vector( opts.center ); }, + + // connect: function( world ){ + // world.on('integrate:velocities', this.behave, this); + // }, + // + // disconnect: function(){ + // world.off('integrate:velocities', this.behave, this); + // }, behave: function(){ var scratch = Physics.scratchpad(); var tmp = scratch.vector(); @@ -84,7 +92,7 @@ world.render(); }); - world.add( Physics.integrator('velocity-verlet') ); + // world.add( Physics.integrator('velocity-verlet') ); // constrain objects to these bounds edgeBounce = Physics.behavior('edge-collision-detection', { @@ -107,35 +115,35 @@ }, true); - // world.add( Physics.body('circle', { - // x: viewWidth/2, + world.add( Physics.body('circle', { + x: viewWidth/2, + y: viewHeight/2,// - 20,//+100, + radius: 20, + width: 40, + height: 40, + mass: 1.4, + //cof: 0, + vy: -0.2, + // vx: 0.1, + restitution: 1, + styles: { fillStyle: 'red' } + })); + + // world.add( Physics.body('rectangle', { + // x: viewWidth/4, // y: viewHeight - 20,//+100, // radius: 20, // width: 40, // height: 40, // mass: 1.4, - // //cof: 0, + // // cof: 0, // vy: -0.2, // // vx: 0.1, - // restitution: 1, + // angle: 0.1, + // restitution: 0.8, // styles: { fillStyle: 'red' } // })); - world.add( Physics.body('rectangle', { - x: viewWidth/4, - y: viewHeight - 20,//+100, - radius: 20, - width: 40, - height: 40, - mass: 1.4, - // cof: 0, - vy: -0.2, - // vx: 0.1, - angle: 0.1, - restitution: 0.8, - styles: { fillStyle: 'red' } - })); - // world.add( Physics.body('circle', { // x: viewWidth/2, // y: viewHeight - 61,//+100, @@ -154,7 +162,7 @@ var center = Physics.vector({x: viewWidth/2, y: viewHeight/2 }); world.add([ - // Physics.behavior('spring', { k: 0.00001, center: center, body: ball }) + Physics.behavior('spring', { k: 0.00001, center: center, body: ball }), Physics.behavior('body-collision-detection', { checkAll: false }), @@ -191,7 +199,7 @@ var x = sx * (center.y - ball.state.pos.y);// * (center.angle(ball.state.pos) > 0 ? 1 : -1); var y = sy * ball.state.vel.norm() * (ball.state.vel.angle() > 0 ? 1 : -1); // console.log(x, y) - renderer.drawCircle(x + center.x, y + center.y, 0.5, '#f66', this.ctx); + renderer.drawCircle(x + center.x, y + center.y, 0.25, '#f66', this.ctx); // graph.endFill(); }; // world.timestep( 20 ); From 2eb1125b0171047adc5be9c8c6f962935ff091c1 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Wed, 19 Nov 2014 13:42:57 -0500 Subject: [PATCH 037/110] added docs to debug renderer --- src/renderers/debug.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/renderers/debug.js b/src/renderers/debug.js index b2cad719..b6e63523 100644 --- a/src/renderers/debug.js +++ b/src/renderers/debug.js @@ -10,10 +10,19 @@ * * Additional config options: * - * - debug: Draw debug shapes and bounding boxes. (default: `false`) * - metaEl: HTMLElement to write meta information like FPS and IPF into. (default: autogenerated) * - offset: Offset the shapes by this amount. (default: `{ x: 0, y: 0 }`) * - styles: Styles to use to draw the shapes. (see below) + * - drawAABB: whether or not to draw bounding boxes. (default: `true`) + * - drawRealPosition: whether or not to draw the non-interpolated position of bodies. (default: `false`) + * - drawIntervals: whether or not to draw the broadphase (sweep-prune) intervals. (default: `false`) + * - drawContacts: whether or not to draw contact points. (default: `false`) + * - aabbColor: the color of AABBs + * - realBodyStyle: styles used to draw the image of the body at its true non-interpolated position + * - intervalMinColor: color of interval minima + * - intervalMaxColor: color of interval maxima + * - mtvColor: color of minimum transit vector for contacts (overlaps) + * - contactColor: color of contact points * * The styles property should contain _default_ styles for each shape you want to draw. * From 01ce0556a4f144eb05c632530fdc89a27ae61104 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Wed, 19 Nov 2014 14:00:26 -0500 Subject: [PATCH 038/110] cleanup --- src/behaviors/interactive.js | 76 +++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/src/behaviors/interactive.js b/src/behaviors/interactive.js index 2bcb897c..34a9ac8c 100644 --- a/src/behaviors/interactive.js +++ b/src/behaviors/interactive.js @@ -91,8 +91,8 @@ Physics.behavior('interactive', function( parent ){ // vars this.bodies = {}; - this.contactPoints = {}; - this.contactPointsOld = {}; + this.touchPoints = {}; + this.touchPointsOld = {}; this.offsets = {}; this.el = typeof this.options.el === 'string' ? document.getElementById(this.options.el) : this.options.el; @@ -110,22 +110,24 @@ Physics.behavior('interactive', function( parent ){ ,touchId ,touch ,offset + ,touchIndex + ,l ; if ( self._world ){ // Adjust for PointerEvent and older browsers - if (!e.changedTouches) { - e.changedTouches = []; - e.changedTouches.push(e); + if ( !e.changedTouches ) { + e.changedTouches = [ e ]; } - for (var touchIndex = 0; touchIndex < e.changedTouches.length; touchIndex++) { - offset = getElementOffset( e.target ); + offset = getElementOffset( e.target ); + + for ( touchIndex = 0, l = e.changedTouches.length; touchIndex < l; touchIndex++) { touch = e.changedTouches[touchIndex]; - pos = {x: touch.pageX - offset.left, y: touch.pageY - offset.top}; touchId = touch.identifier || touch.pointerId || "mouse"; - body = self._world.findOne({ $at: new Physics.vector( pos.x, pos.y ) }); + pos = { idx: touchId, x: touch.pageX - offset.left, y: touch.pageY - offset.top }; + body = self._world.findOne({ $at: new Physics.vector( pos ) }); if ( body ){ // we're trying to grab a body @@ -139,13 +141,13 @@ Physics.behavior('interactive', function( parent ){ // remember the currently grabbed bodies self.bodies[touchId] = body; // remember the click/touch offset - self.contactPoints[touchId] = self.contactPoints[touchId] || new Physics.vector(); - self.contactPoints[touchId].clone( pos ); + self.touchPoints[touchId] = self.touchPoints[touchId] || new Physics.vector(); + self.touchPoints[touchId].clone( pos ); self.offsets[touchId] = self.offsets[touchId] || new Physics.vector(); self.offsets[touchId].clone( pos ).vsub( body.state.pos ); - // init contactPointsOld here, too, so we don't have to do it in "move" - self.contactPointsOld[touchId] = self.contactPointsOld[touchId] || new Physics.vector(); + // init touchPointsOld here, too, so we don't have to do it in "move" + self.touchPointsOld[touchId] = self.touchPointsOld[touchId] || new Physics.vector(); pos.body = body; self._world.emit('interact:grab', pos); @@ -167,30 +169,32 @@ Physics.behavior('interactive', function( parent ){ ,touchId ,touch ,offset + ,touchIndex + ,l ; if ( self._world ){ // Adjust for PointerEvent and older browsers - if (!e.changedTouches) { - e.changedTouches = []; - e.changedTouches.push(e); + if ( !e.changedTouches ) { + e.changedTouches = [ e ]; } - for (var touchIndex = 0; touchIndex < e.changedTouches.length; touchIndex++) { - offset = getElementOffset( e.target ); + offset = getElementOffset( e.target ); + + for ( touchIndex = 0, l = e.changedTouches.length; touchIndex < l; touchIndex++) { touch = e.changedTouches[touchIndex]; - pos = {x: touch.pageX - offset.left, y: touch.pageY - offset.top}; touchId = touch.identifier || touch.pointerId || "mouse"; + pos = { idx: touchId, x: touch.pageX - offset.left, y: touch.pageY - offset.top }; body = self.bodies[touchId]; - + if ( body ){ time = Physics.util.ticker.now(); // set old mouse position - self.contactPointsOld[touchId].clone( self.contactPoints[touchId] ); + self.touchPointsOld[touchId].clone( self.touchPoints[touchId] ); // get new mouse position - self.contactPoints[touchId].set(pos.x, pos.y); + self.touchPoints[touchId].set(pos.x, pos.y); pos.body = body; } @@ -210,6 +214,8 @@ Physics.behavior('interactive', function( parent ){ ,touch ,offset ,dt = Math.max(Physics.util.ticker.now() - time, self.options.moveThrottle) + ,touchIndex + ,l ; if ( self._world ){ @@ -220,21 +226,21 @@ Physics.behavior('interactive', function( parent ){ e.changedTouches.push(e); } - for (var touchIndex = 0; touchIndex < e.changedTouches.length; touchIndex++) { + for ( touchIndex = 0, l = e.changedTouches.length; touchIndex < l; touchIndex++) { offset = getElementOffset( e.target ); touch = e.changedTouches[touchIndex]; - pos = {x: touch.pageX - offset.left, y: touch.pageY - offset.top}; touchId = touch.identifier || touch.pointerId || "mouse"; + pos = { idx: touchId, x: touch.pageX - offset.left, y: touch.pageY - offset.top }; body = self.bodies[touchId]; // release the body - if (body){ + if ( body ){ // get new mouse position - self.contactPoints[touchId].set(pos.x, pos.y); + self.touchPoints[touchId].set(pos.x, pos.y); body.treatment = prevTreatment; // calculate the release velocity - body.state.vel.clone( self.contactPoints[touchId] ).vsub( self.contactPointsOld[touchId] ).mult( 1 / dt ); + body.state.vel.clone( self.touchPoints[touchId] ).vsub( self.touchPointsOld[touchId] ).mult( 1 / dt ); // make sure it's not too big body.state.vel.clamp( self.options.minVel, self.options.maxVel ); @@ -247,19 +253,19 @@ Physics.behavior('interactive', function( parent ){ self._world.emit('interact:release', pos); // remove vars - delete self.contactPoints[touchId]; - delete self.contactPointsOld[touchId]; + delete self.touchPoints[touchId]; + delete self.touchPointsOld[touchId]; delete self.offsets[touchId]; delete self.bodies[touchId]; - if (body) { - delete body.isGrabbed; + + if ( body ) { + delete body.isGrabbed; } } - } }; - if (window.PointerEvent) { + if ( window.PointerEvent ) { this.el.addEventListener('pointerdown', grab); this.el.addEventListener('pointermove', move); @@ -305,10 +311,10 @@ Physics.behavior('interactive', function( parent ){ // if we have one or more bodies grabbed, we need to move them to the new mouse/finger positions. // we'll do this by adjusting the velocity so they get there at the next step - for (var touchId in self.bodies) { + for ( var touchId in self.bodies ) { body = self.bodies[touchId]; state = body.state; - state.vel.clone( self.contactPoints[touchId] ).vsub( self.offsets[touchId] ).vsub( state.pos ).mult( 1 / dt ); + state.vel.clone( self.touchPoints[touchId] ).vsub( self.offsets[touchId] ).vsub( state.pos ).mult( 1 / dt ); } } }; From f9c54071b7e9316ebdfc119fbe7a4c5c14c552c0 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Wed, 19 Nov 2014 14:15:45 -0500 Subject: [PATCH 039/110] cleanup and optimization of interactive behavior --- src/behaviors/interactive.js | 64 ++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/behaviors/interactive.js b/src/behaviors/interactive.js index 34a9ac8c..726b2146 100644 --- a/src/behaviors/interactive.js +++ b/src/behaviors/interactive.js @@ -90,10 +90,7 @@ Physics.behavior('interactive', function( parent ){ this.options( options ); // vars - this.bodies = {}; - this.touchPoints = {}; - this.touchPointsOld = {}; - this.offsets = {}; + this.bodyData = {}; this.el = typeof this.options.el === 'string' ? document.getElementById(this.options.el) : this.options.el; @@ -110,6 +107,7 @@ Physics.behavior('interactive', function( parent ){ ,touchId ,touch ,offset + ,data ,touchIndex ,l ; @@ -139,17 +137,19 @@ Physics.behavior('interactive', function( parent ){ body.state.angular.vel = 0; body.isGrabbed = true; // remember the currently grabbed bodies - self.bodies[touchId] = body; + data = self.bodyData[touchId] || {}; + data.body = body; // remember the click/touch offset - self.touchPoints[touchId] = self.touchPoints[touchId] || new Physics.vector(); - self.touchPoints[touchId].clone( pos ); + data.pos = data.pos || new Physics.vector(); + data.pos.clone( pos ); - self.offsets[touchId] = self.offsets[touchId] || new Physics.vector(); - self.offsets[touchId].clone( pos ).vsub( body.state.pos ); + data.offset = data.offset || new Physics.vector(); + data.offset.clone( pos ).vsub( body.state.pos ); // init touchPointsOld here, too, so we don't have to do it in "move" - self.touchPointsOld[touchId] = self.touchPointsOld[touchId] || new Physics.vector(); + data.oldPos = data.oldPos || new Physics.vector(); pos.body = body; + self.bodyData[touchId] = data; self._world.emit('interact:grab', pos); } else { @@ -169,6 +169,7 @@ Physics.behavior('interactive', function( parent ){ ,touchId ,touch ,offset + ,data ,touchIndex ,l ; @@ -186,15 +187,16 @@ Physics.behavior('interactive', function( parent ){ touch = e.changedTouches[touchIndex]; touchId = touch.identifier || touch.pointerId || "mouse"; pos = { idx: touchId, x: touch.pageX - offset.left, y: touch.pageY - offset.top }; - body = self.bodies[touchId]; + data = self.bodyData[touchId]; - if ( body ){ + if ( data ){ + body = data.body; time = Physics.util.ticker.now(); // set old mouse position - self.touchPointsOld[touchId].clone( self.touchPoints[touchId] ); + data.oldPos.clone( data.pos ); // get new mouse position - self.touchPoints[touchId].set(pos.x, pos.y); + data.pos.clone( pos ); pos.body = body; } @@ -213,6 +215,7 @@ Physics.behavior('interactive', function( parent ){ ,touchId ,touch ,offset + ,data ,dt = Math.max(Physics.util.ticker.now() - time, self.options.moveThrottle) ,touchIndex ,l @@ -221,9 +224,8 @@ Physics.behavior('interactive', function( parent ){ if ( self._world ){ // Adjust for PointerEvent and older browsers - if (!e.changedTouches) { - e.changedTouches = []; - e.changedTouches.push(e); + if ( !e.changedTouches ) { + e.changedTouches = [ e ]; } for ( touchIndex = 0, l = e.changedTouches.length; touchIndex < l; touchIndex++) { @@ -231,21 +233,24 @@ Physics.behavior('interactive', function( parent ){ touch = e.changedTouches[touchIndex]; touchId = touch.identifier || touch.pointerId || "mouse"; pos = { idx: touchId, x: touch.pageX - offset.left, y: touch.pageY - offset.top }; - body = self.bodies[touchId]; + data = self.bodyData[touchId]; // release the body - if ( body ){ + if ( data ){ + body = data.body; // get new mouse position - self.touchPoints[touchId].set(pos.x, pos.y); + data.pos.clone( pos ); body.treatment = prevTreatment; // calculate the release velocity - body.state.vel.clone( self.touchPoints[touchId] ).vsub( self.touchPointsOld[touchId] ).mult( 1 / dt ); + body.state.vel.clone( data.pos ).vsub( data.oldPos ).mult( 1 / dt ); // make sure it's not too big body.state.vel.clamp( self.options.minVel, self.options.maxVel ); body.isGrabbed = false; pos.body = body; + + delete body.isGrabbed; } // emit before we delete the vars in case @@ -253,14 +258,7 @@ Physics.behavior('interactive', function( parent ){ self._world.emit('interact:release', pos); // remove vars - delete self.touchPoints[touchId]; - delete self.touchPointsOld[touchId]; - delete self.offsets[touchId]; - delete self.bodies[touchId]; - - if ( body ) { - delete body.isGrabbed; - } + delete self.bodyData[touchId]; } } }; @@ -307,14 +305,16 @@ Physics.behavior('interactive', function( parent ){ ,state ,dt = Math.max(data.dt, self.options.moveThrottle) ,body + ,d ; // if we have one or more bodies grabbed, we need to move them to the new mouse/finger positions. // we'll do this by adjusting the velocity so they get there at the next step - for ( var touchId in self.bodies ) { - body = self.bodies[touchId]; + for ( var touchId in self.bodyData ) { + d = self.bodyData[touchId]; + body = d.body; state = body.state; - state.vel.clone( self.touchPoints[touchId] ).vsub( self.offsets[touchId] ).vsub( state.pos ).mult( 1 / dt ); + state.vel.clone( d.pos ).vsub( d.offset ).vsub( state.pos ).mult( 1 / dt ); } } }; From ac0eea1267a8ac39ff07175fa53f944c8369e4c4 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Wed, 19 Nov 2014 14:40:29 -0500 Subject: [PATCH 040/110] re #122. fixes multi-grabbing the same body --- src/behaviors/interactive.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/behaviors/interactive.js b/src/behaviors/interactive.js index 726b2146..d9cc4dbc 100644 --- a/src/behaviors/interactive.js +++ b/src/behaviors/interactive.js @@ -80,7 +80,6 @@ Physics.behavior('interactive', function( parent ){ init: function( options ){ var self = this - ,prevTreatment ,time ; @@ -91,6 +90,7 @@ Physics.behavior('interactive', function( parent ){ // vars this.bodyData = {}; + this.bodyDataByUID = {}; this.el = typeof this.options.el === 'string' ? document.getElementById(this.options.el) : this.options.el; @@ -131,14 +131,16 @@ Physics.behavior('interactive', function( parent ){ // we're trying to grab a body // fix the body in place - prevTreatment = body.treatment; - body.treatment = 'kinematic'; body.state.vel.zero(); body.state.angular.vel = 0; body.isGrabbed = true; // remember the currently grabbed bodies data = self.bodyData[touchId] || {}; data.body = body; + // if we're grabbing the same body twice we don't want to remember the wrong treatment. + data.treatment = self.bodyDataByUID[ body.uid ] ? self.bodyDataByUID[ body.uid ].treatment : body.treatment; + // change its treatment but remember its old treatment + body.treatment = 'kinematic'; // remember the click/touch offset data.pos = data.pos || new Physics.vector(); data.pos.clone( pos ); @@ -150,6 +152,7 @@ Physics.behavior('interactive', function( parent ){ pos.body = body; self.bodyData[touchId] = data; + self.bodyDataByUID[ body.uid ] = data; self._world.emit('interact:grab', pos); } else { @@ -181,7 +184,7 @@ Physics.behavior('interactive', function( parent ){ e.changedTouches = [ e ]; } - offset = getElementOffset( e.target ); + offset = getElementOffset( self.el ); for ( touchIndex = 0, l = e.changedTouches.length; touchIndex < l; touchIndex++) { touch = e.changedTouches[touchIndex]; @@ -229,7 +232,7 @@ Physics.behavior('interactive', function( parent ){ } for ( touchIndex = 0, l = e.changedTouches.length; touchIndex < l; touchIndex++) { - offset = getElementOffset( e.target ); + offset = getElementOffset( self.el ); touch = e.changedTouches[touchIndex]; touchId = touch.identifier || touch.pointerId || "mouse"; pos = { idx: touchId, x: touch.pageX - offset.left, y: touch.pageY - offset.top }; @@ -241,7 +244,7 @@ Physics.behavior('interactive', function( parent ){ // get new mouse position data.pos.clone( pos ); - body.treatment = prevTreatment; + body.treatment = data.treatment; // calculate the release velocity body.state.vel.clone( data.pos ).vsub( data.oldPos ).mult( 1 / dt ); // make sure it's not too big @@ -266,19 +269,19 @@ Physics.behavior('interactive', function( parent ){ if ( window.PointerEvent ) { this.el.addEventListener('pointerdown', grab); - this.el.addEventListener('pointermove', move); - this.el.addEventListener('pointerup', release); + window.addEventListener('pointermove', move); + window.addEventListener('pointerup', release); } else { this.el.addEventListener('mousedown', grab); this.el.addEventListener('touchstart', grab); - this.el.addEventListener('mousemove', move); - this.el.addEventListener('touchmove', move); + window.addEventListener('mousemove', move); + window.addEventListener('touchmove', move); - this.el.addEventListener('mouseup', release); - this.el.addEventListener('touchend', release); + window.addEventListener('mouseup', release); + window.addEventListener('touchend', release); } From f1b805135a73183cb9dd03dd886b7b42798f0dbc Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Wed, 19 Nov 2014 14:52:30 -0500 Subject: [PATCH 041/110] interaction behavior can be removed from world now --- src/behaviors/interactive.js | 58 +++++++++++++++++++++++------------- test/collision-sandbox.html | 17 +++++++++-- 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/src/behaviors/interactive.js b/src/behaviors/interactive.js index d9cc4dbc..cc7fe14b 100644 --- a/src/behaviors/interactive.js +++ b/src/behaviors/interactive.js @@ -101,7 +101,7 @@ Physics.behavior('interactive', function( parent ){ // init events // when there are multiple touchdowns, grab is usually called separately for each, // but we loop through e.changedTouches just in case - var grab = function grab( e ){ + self.grab = function grab( e ){ var pos ,body ,touchId @@ -165,7 +165,7 @@ Physics.behavior('interactive', function( parent ){ // when there are multiple touchdowns, move is called once // and e.changedTouches will have one or more touches in it - var move = Physics.util.throttle(function move( e ){ + self.move = Physics.util.throttle(function move( e ){ var pos ,state ,body @@ -212,7 +212,7 @@ Physics.behavior('interactive', function( parent ){ // when there are multiple touchups, release is called once // and e.changedTouches will have one or more touches in it - var release = function release( e ){ + self.release = function release( e ){ var pos ,body ,touchId @@ -265,33 +265,32 @@ Physics.behavior('interactive', function( parent ){ } } }; + }, + + // extended + connect: function( world ){ + + // subscribe the .behave() method to the position integration step + world.on('integrate:positions', this.behave, this); if ( window.PointerEvent ) { - this.el.addEventListener('pointerdown', grab); - window.addEventListener('pointermove', move); - window.addEventListener('pointerup', release); + this.el.addEventListener('pointerdown', this.grab); + window.addEventListener('pointermove', this.move); + window.addEventListener('pointerup', this.release); } else { - this.el.addEventListener('mousedown', grab); - this.el.addEventListener('touchstart', grab); + this.el.addEventListener('mousedown', this.grab); + this.el.addEventListener('touchstart', this.grab); - window.addEventListener('mousemove', move); - window.addEventListener('touchmove', move); + window.addEventListener('mousemove', this.move); + window.addEventListener('touchmove', this.move); - window.addEventListener('mouseup', release); - window.addEventListener('touchend', release); + window.addEventListener('mouseup', this.release); + window.addEventListener('touchend', this.release); } - - }, - - // extended - connect: function( world ){ - - // subscribe the .behave() method to the position integration step - world.on('integrate:positions', this.behave, this); }, // extended @@ -299,6 +298,25 @@ Physics.behavior('interactive', function( parent ){ // unsubscribe when disconnected world.off('integrate:positions', this.behave, this); + + if ( window.PointerEvent ) { + + this.el.removeEventListener('pointerdown', this.grab); + window.removeEventListener('pointermove', this.move); + window.removeEventListener('pointerup', this.release); + + } else { + + this.el.removeEventListener('mousedown', this.grab); + this.el.removeEventListener('touchstart', this.grab); + + window.removeEventListener('mousemove', this.move); + window.removeEventListener('touchmove', this.move); + + window.removeEventListener('mouseup', this.release); + window.removeEventListener('touchend', this.release); + + } }, // extended diff --git a/test/collision-sandbox.html b/test/collision-sandbox.html index 213b5a9b..c022aaf0 100644 --- a/test/collision-sandbox.html +++ b/test/collision-sandbox.html @@ -80,7 +80,7 @@ edgeBounce.setAABB(viewportBounds); }, true); - + var intr; world.add([ edgeBounce ,Physics.behavior('sweep-prune') @@ -92,9 +92,22 @@ // add gravity ,Physics.behavior('constant-acceleration') // ,Physics.behavior('newtonian') - ,Physics.behavior('interactive', {el: renderer.el.parentNode}) + ,intr = Physics.behavior('interactive', {el: renderer.el.parentNode}) ]); + renderer.gui.add({ + get interaction(){ + return world.has(intr); + } + ,set interaction( val ){ + if ( val ){ + world.add(intr); + } else { + world.remove(intr); + } + } + }, 'interaction'); + Physics.util.ticker.on(function (time, dt) { world.step(time); From 1c251b8cdb310f9c0767721f5aab930aab9b004a Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Wed, 19 Nov 2014 15:02:07 -0500 Subject: [PATCH 042/110] fixes #101. interactive behavior uses .applyTo --- src/behaviors/interactive.js | 2 +- test/collision-sandbox.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/behaviors/interactive.js b/src/behaviors/interactive.js index cc7fe14b..dc28f958 100644 --- a/src/behaviors/interactive.js +++ b/src/behaviors/interactive.js @@ -125,7 +125,7 @@ Physics.behavior('interactive', function( parent ){ touch = e.changedTouches[touchIndex]; touchId = touch.identifier || touch.pointerId || "mouse"; pos = { idx: touchId, x: touch.pageX - offset.left, y: touch.pageY - offset.top }; - body = self._world.findOne({ $at: new Physics.vector( pos ) }); + body = self._world.findOne({ $at: new Physics.vector( pos ), $in: self.getTargets() }); if ( body ){ // we're trying to grab a body diff --git a/test/collision-sandbox.html b/test/collision-sandbox.html index c022aaf0..eb67c1c9 100644 --- a/test/collision-sandbox.html +++ b/test/collision-sandbox.html @@ -33,7 +33,7 @@ ,viewHeight = parent.innerHeight; var bodyStyles = { strokeStyle: '#888', fillStyle: 'transparent', lineWidth: 2, angleIndicator: 'rgba(200, 200, 100, 1)' }; - + var intr; function setup(world) { var renderer = Physics.renderer('debug', { @@ -80,7 +80,7 @@ edgeBounce.setAABB(viewportBounds); }, true); - var intr; + world.add([ edgeBounce ,Physics.behavior('sweep-prune') From b3c9f5d9b64bf3aa1b44f5447ec11a3401ed07cd Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Wed, 19 Nov 2014 15:47:00 -0500 Subject: [PATCH 043/110] fix: renderers now render points too --- src/renderers/canvas.js | 8 +++++++- src/renderers/dom.js | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/renderers/canvas.js b/src/renderers/canvas.js index 169d27e0..25d33eb9 100644 --- a/src/renderers/canvas.js +++ b/src/renderers/canvas.js @@ -64,6 +64,8 @@ Physics.renderer('canvas', function( proto ){ // default styles of drawn objects styles: { + 'point': colors.blue, + 'circle' : { strokeStyle: colors.blue, lineWidth: 1, @@ -592,7 +594,7 @@ Physics.renderer('canvas', function( proto ){ ,name = geometry.name ; - styles = styles || this.options.styles[ name ] || {}; + styles = styles || this.options.styles[ name ] || this.options.styles.circle || {}; // must want an image if ( styles.src ){ @@ -628,6 +630,10 @@ Physics.renderer('canvas', function( proto ){ } else if (name === 'rectangle'){ this.drawRect(0, 0, geometry.width, geometry.height, styles, hiddenCtx); + } else { + + // assume it's a point + this.drawCircle(0, 0, 1, styles, hiddenCtx); } if (styles.angleIndicator){ diff --git a/src/renderers/dom.js b/src/renderers/dom.js index e23c0e68..7b56a3b0 100644 --- a/src/renderers/dom.js +++ b/src/renderers/dom.js @@ -51,6 +51,7 @@ Physics.renderer('dom', function( proto ){ var classpfx = 'pjs-' ,px = 'px' ,cssTransform = pfx('transform') + ,borderRadius = pfx('borderRadius') ; var newEl = function( node, content ){ @@ -95,6 +96,22 @@ Physics.renderer('dom', function( proto ){ } }, + /** internal + * DomRenderer#pointProperties( el, geometry ) + * - el (HTMLElement): The element + * - geometry (Geometry): The body's geometry + * + * Set dom element style properties for a point. + **/ + pointProperties: function( el, geometry ){ + + el.style.width = '2px'; + el.style.height = '2px'; + el.style.marginLeft = '-1px'; + el.style.marginTop = '-1px'; + el.style[ borderRadius ] = '50%'; + }, + /** internal * DomRenderer#circleProperties( el, geometry ) * - el (HTMLElement): The element @@ -110,6 +127,7 @@ Physics.renderer('dom', function( proto ){ el.style.height = (aabb.hh * 2) + px; el.style.marginLeft = (-aabb.hw) + px; el.style.marginTop = (-aabb.hh) + px; + el.style[ borderRadius ] = '50%'; }, /** internal From c2237a913809bfeab96ce6e54f2937443906d5f3 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Wed, 19 Nov 2014 17:19:26 -0500 Subject: [PATCH 044/110] re #109, #108, #103. Major fixes to pixi renderer --- gruntfile.js | 16 +- lib/pixi.js | 16180 +------------------------------ src/renderers/pixi-renderer.js | 233 +- test/pixi-sandbox.html | 223 + 4 files changed, 396 insertions(+), 16256 deletions(-) create mode 100644 test/pixi-sandbox.html diff --git a/gruntfile.js b/gruntfile.js index 015ac481..b998a0cb 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -129,10 +129,18 @@ module.exports = function(grunt) { var deps = ['physicsjs']; var l = path.split('/').length; var pfx = l > 0 ? (new Array( l )).join('../') : './'; - src.replace(/@requires\s([\w-_\/]+)/g, function( match, dep ){ - - // just get the dependency - deps.push( pfx + dep ); + src.replace(/@requires\s([\w-_\/]+(\.js)?)/g, function( match, dep ){ + + var i = dep.indexOf('.js'); + + if ( i > -1 ){ + // must be a 3rd party dep + dep = dep.substr( 0, i ); + deps.push( dep ); + } else { + // just get the dependency + deps.push( pfx + dep ); + } // no effect return match; }); diff --git a/lib/pixi.js b/lib/pixi.js index ed543db6..22396fa8 100644 --- a/lib/pixi.js +++ b/lib/pixi.js @@ -1,16181 +1,17 @@ /** * @license - * pixi.js - v1.6.0 + * pixi.js - v2.1.0 * Copyright (c) 2012-2014, Mat Groves * http://goodboydigital.com/ * - * Compiled: 2014-07-18 + * Compiled: 2014-11-12 * * 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.6.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 -}; - -// used to create uids for various pixi objects.. -PIXI._UID = 0; - -if(typeof(Float32Array) != 'undefined') -{ - PIXI.Float32Array = Float32Array; - PIXI.Uint16Array = Uint16Array; -} -else -{ - PIXI.Float32Array = Array; - PIXI.Uint16Array = Array; -} - -// 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; - - -PIXI.dontSayHello = false; - -PIXI.sayHello = function (type) -{ - if(PIXI.dontSayHello)return; - - if ( navigator.userAgent.toLowerCase().indexOf('chrome') > -1 ) - { - var args = [ - '%c %c %c Pixi.js ' + PIXI.VERSION + ' - ' + type + ' %c ' + ' %c ' + ' http://www.pixijs.com/ %c %c ♥%c♥%c♥ ', - 'background: #ff66a5', - 'background: #ff66a5', - 'color: #ff66a5; background: #030307;', - 'background: #ff66a5', - 'background: #ffc3dc', - 'background: #ff66a5', - 'color: #ff2424; background: #fff', - 'color: #ff2424; background: #fff', - 'color: #ff2424; background: #fff' - ]; - - - - console.log.apply(console, args); - } - else if (window['console']) - { - console.log('Pixi.js ' + PIXI.VERSION + ' - http://www.pixijs.com/'); - } - - PIXI.dontSayHello = true; -}; - -/** - * @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); -}; - -/** - * Sets the point to a new x and y position. - * If y is ommited, both x and y will be set to x. - * - * @method set - * @param [x=0] {Number} position of the point on the x axis - * @param [y=0] {Number} position of the point on the y axis - */ -PIXI.Point.prototype.set = function(x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; - -// constructor -PIXI.Point.prototype.constructor = PIXI.Point; -/** - * @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 center of this circle - * @param y {Number} The Y coordinate of the center 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); -}; - -/** -* Returns the framing rectangle of the circle as a PIXI.Rectangle object -* -* @method getBounds -* @return {Rectangle} the framing rectangle -*/ -PIXI.Circle.prototype.getBounds = function() -{ - return new PIXI.Rectangle(this.x - this.radius, this.y - this.radius, this.width, this.height); -}; - -// 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 center of the ellipse - * @param y {Number} The Y coordinate of the center of the ellipse - * @param width {Number} The half width of this ellipse - * @param height {Number} The half 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.width, this.y - this.height, this.width, this.height); -}; - -// constructor -PIXI.Ellipse.prototype.constructor = PIXI.Ellipse; - -/** - * @author Mat Groves http://matgroves.com/ @Doormat23 - */ - -/** - * The Matrix class is now an object, which makes it a lot faster, - * here is a representation of it : - * | a | b | tx| - * | c | d | ty| - * | 0 | 0 | 1 | - * - * @class Matrix - * @constructor - */ -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} 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) - { - array[0] = this.a; - array[1] = this.c; - array[2] = 0; - array[3] = this.b; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.b; - array[2] = this.tx; - array[3] = this.c; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -PIXI.identityMatrix = new PIXI.Matrix(); - -PIXI.determineMatrixArrayType = function() { - return (typeof Float32Array !== 'undefined') ? Float32Array : Array; -}; - -/** - * 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 - * - * @class Matrix2 - * @constructor - */ -PIXI.Matrix2 = PIXI.determineMatrixArrayType(); - -/** - * @author Mat Groves http://matgroves.com/ @Doormat23 - */ - -/** - * The base class for all objects that are rendered on the screen. - * This is an abstract class and should not be used on its own rather it should be extended. - * - * @class DisplayObject - * @constructor - */ -PIXI.DisplayObject = function() -{ - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @property position - * @type Point - */ - this.position = new PIXI.Point(); - - /** - * 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 like the hitArea this is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle - * - * @property filterArea - * @type Rectangle - */ - this.filterArea = null;//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; - - this._cacheAsBitmap = false; - this._cacheIsDirty = false; - - - /* - * 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; - } -}); - -/** - * Set weather or not a the display objects is cached as a bitmap. - * This basically takes a snap shot of the display object as it is at that moment. It can provide a performance benefit for complex static displayObjects - * To remove filters simply set this property to 'null' - * @property cacheAsBitmap - * @type Boolean - */ -Object.defineProperty(PIXI.DisplayObject.prototype, 'cacheAsBitmap', { - get: function() { - return this._cacheAsBitmap; - }, - set: function(value) { - - if(this._cacheAsBitmap === value)return; - - if(value) - { - //this._cacheIsDirty = true; - this._generateCachedSprite(); - } - else - { - this._destroyCachedSprite(); - } - - this._cacheAsBitmap = 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() -{ - 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; -}; - -PIXI.DisplayObject.prototype.generateTexture = function(renderer) -{ - var bounds = this.getLocalBounds(); - - var renderTexture = new PIXI.RenderTexture(bounds.width | 0, bounds.height | 0, renderer); - renderTexture.render(this, new PIXI.Point(-bounds.x, -bounds.y) ); - - return renderTexture; -}; - -PIXI.DisplayObject.prototype.updateCache = function() -{ - this._generateCachedSprite(); -}; - -PIXI.DisplayObject.prototype._renderCachedSprite = function(renderSession) -{ - this._cachedSprite.worldAlpha = this.worldAlpha; - - if(renderSession.gl) - { - PIXI.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); - } - else - { - PIXI.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); - } -}; - -PIXI.DisplayObject.prototype._generateCachedSprite = function()//renderSession) -{ - this._cacheAsBitmap = false; - var bounds = this.getLocalBounds(); - - if(!this._cachedSprite) - { - var renderTexture = new PIXI.RenderTexture(bounds.width | 0, bounds.height | 0);//, renderSession.renderer); - - this._cachedSprite = new PIXI.Sprite(renderTexture); - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.texture.resize(bounds.width | 0, bounds.height | 0); - } - - //REMOVE filter! - var tempFilters = this._filters; - this._filters = null; - - this._cachedSprite.filters = tempFilters; - this._cachedSprite.texture.render(this, new PIXI.Point(-bounds.x, -bounds.y) ); - - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - this._filters = tempFilters; - - this._cacheAsBitmap = true; -}; - -/** -* Renders the object using the WebGL renderer -* -* @method _renderWebGL -* @param renderSession {RenderSession} -* @private -*/ -PIXI.DisplayObject.prototype._destroyCachedSprite = function() -{ - if(!this._cachedSprite)return; - - this._cachedSprite.texture.destroy(true); - // console.log("DESTROY") - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - - -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) { - - var width = this.getLocalBounds().width; - - if(width !== 0) - { - this.scale.x = value / ( width/this.scale.x ); - } - else - { - this.scale.x = 1; - } - - - this._width = value; - } -}); - - -/** - * The height of the displayObjectContainer, setting this will actually modify the scale to achieve the value set - * - * @property height - * @type Number - */ - -Object.defineProperty(PIXI.DisplayObjectContainer.prototype, 'height', { - get: function() { - return this.scale.y * this.getLocalBounds().height; - }, - set: function(value) { - - var height = this.getLocalBounds().height; - - if(height !== 0) - { - this.scale.y = value / ( height/this.scale.y ); - } - else - { - this.scale.y = 1; - } - - this._height = value; - } -}); - - -/** - * Adds a child to the container. - * - * @method addChild - * @param child {DisplayObject} The DisplayObject to add to the container - */ -PIXI.DisplayObjectContainer.prototype.addChild = function(child) -{ - return this.addChildAt(child, this.children.length); -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @method addChildAt - * @param child {DisplayObject} The child to add - * @param index {Number} The index to place the child in - */ -PIXI.DisplayObjectContainer.prototype.addChildAt = function(child, index) -{ - if(index >= 0 && index <= this.children.length) - { - if(child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - if(this.stage)child.setStageReference(this.stage); - - return child; - } - else - { - throw new Error(child + ' 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('Supplied index does not exist in the child list, or the supplied DisplayObject must be a child of the caller'); - } -}; - -/** - * Removes a child from the container. - * - * @method removeChild - * @param child {DisplayObject} The DisplayObject to remove - */ -PIXI.DisplayObjectContainer.prototype.removeChild = function(child) -{ - return this.removeChildAt( this.children.indexOf( child ) ); -}; - -/** - * Removes a child from the specified index position in the child list of the container. - * - * @method removeChildAt - * @param index {Number} The index to get the child from - */ -PIXI.DisplayObjectContainer.prototype.removeChildAt = function(index) -{ - var child = this.getChildAt( index ); - if(this.stage) - child.removeStageReference(); - - child.parent = undefined; - this.children.splice( index, 1 ); - return child; -}; - -/** -* Removes all child instances from the child list of the container. -* -* @method removeChildren -* @param beginIndex {Number} The beginning position. Predefined value is 0. -* @param endIndex {Number} The ending position. Predefined value is children's array length. -*/ -PIXI.DisplayObjectContainer.prototype.removeChildren = function(beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - - if (range > 0 && range <= end) - { - var removed = this.children.splice(begin, range); - for (var i = 0; i < removed.length; i++) { - var child = removed[i]; - if(this.stage) - child.removeStageReference(); - child.parent = undefined; - } - return removed; - } - else - { - throw new Error( 'Range Error, numeric values are outside the acceptable range' ); - } -}; - -/* - * 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 ); - - if(this._cacheAsBitmap)return; - - 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; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if(this._filters) - { - spriteBatch.flush(); - renderSession.filterManager.pushFilter(this._filterBlock); - } - - if(this._mask) - { - spriteBatch.stop(); - renderSession.maskManager.pushMask(this.mask, renderSession); - spriteBatch.start(); - } - - // add this sprite to the batch - spriteBatch.render(this); - - // now loop through the children and make sure they get rendered - for(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; - - this.currentFrame = this.currentFrame % this.textures.length; - - 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(); - } - } -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @method fromFrames - * @param frames {Array} the array of frames ids the movieclip will use as its texture frames - */ -PIXI.MovieClip.fromFrames = function(frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; i++) - { - textures.push(new PIXI.Texture.fromFrame(frames[i])); - } - - return new PIXI.MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @method fromFrames - * @param frames {Array} the array of image ids the movieclip will use as its texture frames - */ -PIXI.MovieClip.fromImages = function(images) -{ - var textures = []; - - for (var i = 0; i < images.length; i++) - { - textures.push(new PIXI.Texture.fromImage(images[i])); - } - - return new PIXI.MovieClip(textures); -}; -/** - * @author Mat Groves http://matgroves.com/ @Doormat23 - */ - - -PIXI.FilterBlock = function() -{ - this.visible = true; - this.renderable = true; -}; - -/** - * @author Mat Groves http://matgroves.com/ @Doormat23 - * - Modified by Tom Slezakowski http://www.tomslezakowski.com @TomSlezakowski (24/03/2014) - Added dropShadowColor. - */ - -/** - * 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 - * @param [style.dropShadow=false] {Boolean} Set a drop shadow for the text - * @param [style.dropShadowColor='#000000'] {String} A fill style to be used on the dropshadow e.g 'red', '#00FF00' - * @param [style.dropShadowAngle=Math.PI/4] {Number} Set a angle of the drop shadow - * @param [style.dropShadowDistance=5] {Number} Set a distance of the drop shadow - */ -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); -}; - -// constructor -PIXI.Text.prototype = Object.create(PIXI.Sprite.prototype); -PIXI.Text.prototype.constructor = PIXI.Text; - - -/** - * The width of the sprite, setting this will actually modify the scale to achieve the value set - * - * @property width - * @type Number - */ -Object.defineProperty(PIXI.Text.prototype, 'width', { - get: function() { - - if(this.dirty) - { - this.updateText(); - this.dirty = false; - } - - - return this.scale.x * this.texture.frame.width; - }, - set: function(value) { - this.scale.x = value / this.texture.frame.width; - this._width = value; - } -}); - -/** - * The height of the Text, setting this will actually modify the scale to achieve the value set - * - * @property height - * @type Number - */ -Object.defineProperty(PIXI.Text.prototype, 'height', { - get: function() { - - if(this.dirty) - { - this.updateText(); - this.dirty = false; - } - - - return this.scale.y * this.texture.frame.height; - }, - set: function(value) { - this.scale.y = value / this.texture.frame.height; - this._height = value; - } -}); - - -/** - * 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 - * @param [style.dropShadow=false] {Boolean} Set a drop shadow for the text - * @param [style.dropShadowColor='#000000'] {String} A fill style to be used on the dropshadow e.g 'red', '#00FF00' - * @param [style.dropShadowAngle=Math.PI/4] {Number} Set a angle of the drop shadow - * @param [style.dropShadowDistance=5] {Number} Set a distance of the drop shadow - */ -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; - style.wordWrapWidth = style.wordWrapWidth || 100; - - style.dropShadow = style.dropShadow || false; - style.dropShadowAngle = style.dropShadowAngle || Math.PI / 6; - style.dropShadowDistance = style.dropShadowDistance || 4; - style.dropShadowColor = style.dropShadowColor || 'black'; - - 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); - } - - var width = maxLineWidth + this.style.strokeThickness; - if(this.style.dropShadow)width += this.style.dropShadowDistance; - - this.canvas.width = width + this.context.lineWidth; - //calculate text height - var lineHeight = this.determineFontHeight('font: ' + this.style.font + ';') + this.style.strokeThickness; - - var height = lineHeight * lines.length; - if(this.style.dropShadow)height += this.style.dropShadowDistance; - - this.canvas.height = height; - - if(navigator.isCocoonJS) this.context.clearRect(0,0,this.canvas.width,this.canvas.height); - - this.context.font = this.style.font; - this.context.strokeStyle = this.style.stroke; - this.context.lineWidth = this.style.strokeThickness; - this.context.textBaseline = 'top'; - - var linePositionX; - var linePositionY; - - if(this.style.dropShadow) - { - this.context.fillStyle = this.style.dropShadowColor; - - var xShadowOffset = Math.sin(this.style.dropShadowAngle) * this.style.dropShadowDistance; - var yShadowOffset = Math.cos(this.style.dropShadowAngle) * this.style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = this.style.strokeThickness / 2; - linePositionY = this.style.strokeThickness / 2 + i * lineHeight; - - if(this.style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if(this.style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if(this.style.fill) - { - this.context.fillText(lines[i], linePositionX + xShadowOffset, linePositionY + yShadowOffset); - } - - // if(dropShadow) - } - } - - //set canvas text styles - this.context.fillStyle = this.style.fill; - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = this.style.strokeThickness / 2; - linePositionY = this.style.strokeThickness / 2 + i * lineHeight; - - if(this.style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if(this.style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if(this.style.stroke && this.style.strokeThickness) - { - this.context.strokeText(lines[i], linePositionX, linePositionY); - } - - if(this.style.fill) - { - this.context.fillText(lines[i], linePositionX, linePositionY); - } - - // if(dropShadow) - } - - - 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.crop.width = this.texture.frame.width = this.canvas.width; - this.texture.crop.height = 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(j === 0 || 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 destroyBaseTexture {Boolean} whether to destroy the base texture as well - */ -PIXI.Text.prototype.destroy = function(destroyBaseTexture) -{ - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this.texture.destroy(destroyBaseTexture === undefined ? true : destroyBaseTexture); -}; - -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(); - - - /** - * 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); - - window.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; - - window.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.rebuildInteractiveGraph(); - } - - // 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; - } -}; - -PIXI.InteractionManager.prototype.rebuildInteractiveGraph = function() -{ - this.dirty = false; - - var len = this.interactiveItems.length; - - for (var 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); -}; - -/** - * 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) -{ - if(this.dirty) - { - this.rebuildInteractiveGraph(); - } - - 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) -{ - if(this.dirty) - { - this.rebuildInteractiveGraph(); - } - - 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() -{ - if(this.dirty) - { - this.rebuildInteractiveGraph(); - } - - 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) -{ - if(this.dirty) - { - this.rebuildInteractiveGraph(); - } - - 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) -{ - if(this.dirty) - { - this.rebuildInteractiveGraph(); - } - - 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; - } - - for (var j = 0; j < this.interactiveItems.length; j++) - { - var item = this.interactiveItems[j]; - if(item.touchmove && item.__touchData && item.__touchData[touchEvent.identifier]) 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) -{ - if(this.dirty) - { - this.rebuildInteractiveGraph(); - } - - 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 = item.__touchData || {}; - item.__touchData[touchEvent.identifier] = 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) -{ - if(this.dirty) - { - this.rebuildInteractiveGraph(); - } - - //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]; - - if(item.__touchData && item.__touchData[touchEvent.identifier]) { - - item.__hit = this.hitTest(item, item.__touchData[touchEvent.identifier]); - - // 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[touchEvent.identifier] = null; - } - } - // 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 interactionManager - * @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 listeners - * @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 ].unshift( 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 = listeners[ event.type ].length-1; i >= 0; i--) { -// 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 ) { - - if ( listeners[ type ] === undefined ) return; - - 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); -}; - -/** - * This helper function will automatically detect which renderer you should be using. - * This function is very similar to the autoDetectRenderer function except that is will return a canvas renderer for android. - * Even thought both android chrome suports webGL the canvas implementation perform better at the time of writing. - * This function will likely change and update as webGL performance imporoves on thease devices. - * @class getRecommendedRenderer - * @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.autoDetectRecommendedRenderer = 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; - } - } )(); - - var isAndroid = /Android/i.test(navigator.userAgent); - - if( webgl && !isAndroid) - { - 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) -{ - this._UID = PIXI._UID++; - - /** - * @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._glTextures[gl.id]); - - // Extended texture data - if (uniform.textureData) - { - var data = uniform.textureData; - - // GLTexture = mag linear, min linear_mipmap_linear, wrap repeat + gl.generateMipmap(gl.TEXTURE_2D); - // GLTextureLinear = mag/min linear, wrap clamp - // GLTextureNearestRepeat = mag/min NEAREST, wrap repeat - // GLTextureNearest = mag/min nearest, wrap clamp - // AudioTexture = whatever + luminance + width 512, height 2, border 0 - // KeyTexture = whatever + luminance + width 256, height 2, border 0 - - // magFilter can be: gl.LINEAR, gl.LINEAR_MIPMAP_LINEAR or gl.NEAREST - // wrapS/T can be: gl.CLAMP_TO_EDGE or gl.REPEAT - - var magFilter = (data.magFilter) ? data.magFilter : gl.LINEAR; - var minFilter = (data.minFilter) ? data.minFilter : gl.LINEAR; - var wrapS = (data.wrapS) ? data.wrapS : gl.CLAMP_TO_EDGE; - var wrapT = (data.wrapT) ? data.wrapT : gl.CLAMP_TO_EDGE; - var format = (data.luminance) ? gl.LUMINANCE : gl.RGBA; - - if (data.repeat) - { - wrapS = gl.REPEAT; - wrapT = gl.REPEAT; - } - - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, !!data.flipY); - - if (data.width) - { - var width = (data.width) ? data.width : 512; - var height = (data.height) ? data.height : 2; - var border = (data.border) ? data.border : 0; - - // void texImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, ArrayBufferView? pixels); - gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, border, format, gl.UNSIGNED_BYTE, null); - } - else - { - // void texImage2D(GLenum target, GLint level, GLenum internalformat, GLenum format, GLenum type, ImageData? pixels); - gl.texImage2D(gl.TEXTURE_2D, 0, format, gl.RGBA, gl.UNSIGNED_BYTE, uniform.value.baseTexture.source); - } - - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, magFilter); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, minFilter); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrapS); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrapT); - } - - gl.uniform1i(uniform.uniformLocation, this.textureCount); - - uniform._init = true; - - this.textureCount++; - -}; - -/** -* Updates the shader uniform values. -* -* @method syncUniforms -*/ -PIXI.PixiShader.prototype.syncUniforms = function() -{ - this.textureCount = 1; - var uniform; - var gl = this.gl; - - // This would probably be faster in an array and it would guarantee key order - for (var key in this.uniforms) - { - uniform = this.uniforms[key]; - - if (uniform.glValueLength === 1) - { - if (uniform.glMatrix === true) - { - uniform.glFunc.call(gl, uniform.uniformLocation, uniform.transpose, uniform.value); - } - else - { - uniform.glFunc.call(gl, uniform.uniformLocation, uniform.value); - } - } - else if (uniform.glValueLength === 2) - { - uniform.glFunc.call(gl, uniform.uniformLocation, uniform.value.x, uniform.value.y); - } - else if (uniform.glValueLength === 3) - { - uniform.glFunc.call(gl, uniform.uniformLocation, uniform.value.x, uniform.value.y, uniform.value.z); - } - else if (uniform.glValueLength === 4) - { - uniform.glFunc.call(gl, uniform.uniformLocation, uniform.value.x, uniform.value.y, uniform.value.z, uniform.value.w); - } - else if (uniform.type === 'sampler2D') - { - if (uniform._init) - { - gl.activeTexture(gl['TEXTURE' + this.textureCount]); - gl.bindTexture(gl.TEXTURE_2D, uniform.value.baseTexture._glTextures[gl.id] || PIXI.createWebGLTexture( uniform.value.baseTexture, gl)); - gl.uniform1i(uniform.uniformLocation, this.textureCount); - this.textureCount++; - } - else - { - this.initSampler2D(uniform); - } - } - } - -}; - -/** -* Destroys the shader -* @method destroy -*/ -PIXI.PixiShader.prototype.destroy = function() -{ - this.gl.deleteProgram( this.program ); - this.uniforms = null; - this.gl = null; - - this.attributes = null; -}; - -/** -* The Default Vertex shader source -* @property defaultVertexSrc -* @type String -*/ -PIXI.PixiShader.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - 'attribute 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) -{ - this._UID = PIXI._UID++; - - /** - * @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(gl) -{ - this._UID = PIXI._UID++; - - this.gl = gl; - - /** - * @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 = vec4(1.0, 0.0, 0.0, 1.0);',//gl_FragColor * alpha;', - '}' - ]; - - /** - * @property {array} fragmentSrc - The fragment shader. - */ - this.vertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - 'uniform mat3 translationMatrix;', - 'uniform vec2 projectionVector;', - 'uniform vec2 offsetVector;', - // 'uniform float alpha;', - // 'uniform vec3 tint;', - 'varying vec2 vTextureCoord;', - // 'varying vec4 vColor;', - - 'void main(void) {', - ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', - ' v -= offsetVector.xyx;', - ' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - // ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ]; - - this.init(); -}; - -/** -* Initialises the shader -* @method init -* -*/ -PIXI.StripShader.prototype.init = function() -{ - var gl = this.gl; - - var program = PIXI.compileProgram(gl, this.vertexSrc, this.fragmentSrc); - gl.useProgram(program); - - // get and store the uniforms for the shader - this.uSampler = gl.getUniformLocation(program, 'uSampler'); - this.projectionVector = gl.getUniformLocation(program, 'projectionVector'); - this.offsetVector = gl.getUniformLocation(program, 'offsetVector'); - this.colorAttribute = gl.getAttribLocation(program, 'aColor'); - //this.dimensions = gl.getUniformLocation(this.program, 'dimensions'); - - // get and store the attributes - this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition'); - this.aTextureCoord = gl.getAttribLocation(program, 'aTextureCoord'); - - this.attributes = [this.aVertexPosition, this.aTextureCoord]; - - this.translationMatrix = gl.getUniformLocation(program, 'translationMatrix'); - this.alpha = gl.getUniformLocation(program, 'alpha'); - - this.program = program; -}; - -/** - * @author Mat Groves http://matgroves.com/ @Doormat23 - */ - -/** -* @class PrimitiveShader -* @constructor -* @param gl {WebGLContext} the current WebGL drawing context -*/ -PIXI.PrimitiveShader = function(gl) -{ - this._UID = PIXI._UID++; - - /** - * @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 - */ - -/** -* @class ComplexPrimitiveShader -* @constructor -* @param gl {WebGLContext} the current WebGL drawing context -*/ -PIXI.ComplexPrimitiveShader = function(gl) -{ - this._UID = PIXI._UID++; - /** - * @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 vec3 tint;', - 'uniform float alpha;', - 'uniform vec3 color;', - - '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 = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', - '}' - ]; - - this.init(); -}; - -/** -* Initialises the shader -* @method init -* -*/ -PIXI.ComplexPrimitiveShader.prototype.init = function() -{ - - var gl = this.gl; - - var program = PIXI.compileProgram(gl, this.vertexSrc, this.fragmentSrc); - gl.useProgram(program); - - // get and store the uniforms for the shader - this.projectionVector = gl.getUniformLocation(program, 'projectionVector'); - this.offsetVector = gl.getUniformLocation(program, 'offsetVector'); - this.tintColor = gl.getUniformLocation(program, 'tint'); - this.color = gl.getUniformLocation(program, 'color'); - - - // get and store the attributes - this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition'); - // this.colorAttribute = gl.getAttribLocation(program, 'aColor'); - - this.attributes = [this.aVertexPosition, this.colorAttribute]; - - this.translationMatrix = gl.getUniformLocation(program, 'translationMatrix'); - this.alpha = gl.getUniformLocation(program, 'alpha'); - - this.program = program; -}; - -/** -* Destroys the shader -* @method destroy -* -*/ -PIXI.ComplexPrimitiveShader.prototype.destroy = function() -{ - this.gl.deleteProgram( this.program ); - this.uniforms = null; - this.gl = null; - - this.attribute = null; -}; - -/** - * @author Mat Groves http://matgroves.com/ @Doormat23 - */ - -/** - * 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, - webGLData; - - if(graphics.dirty) - { - PIXI.WebGLGraphics.updateGraphics(graphics, gl); - } - - var webGL = graphics._webGL[gl.id]; - - // This could be speeded up for sure! - - for (var i = 0; i < webGL.data.length; i++) - { - if(webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderSession.stencilManager.pushStencil(graphics, webGLData, renderSession); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderSession.stencilManager.popStencil(graphics, webGLData, renderSession); - - this.last = webGLData.mode; - } - else - { - webGLData = webGL.data[i]; - - - renderSession.shaderManager.setShader( shader );//activatePrimitiveShader(); - shader = renderSession.shaderManager.primitiveShader; - 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, webGLData.buffer); - - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); - gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); - - // set the index buffer! - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); - gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); - } - } -}; - -/** - * 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) -{ - // get the contexts graphics object - var webGL = graphics._webGL[gl.id]; - // if the graphics object does not exist in the webGL context time to create it! - if(!webGL)webGL = graphics._webGL[gl.id] = {lastIndex:0, data:[], gl:gl}; - - // flag the graphics as not dirty as we are about to update it... - graphics.dirty = false; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if(graphics.clearDirty) - { - graphics.clearDirty = false; - - // lop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - graphicsData.reset(); - PIXI.WebGLGraphics.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - if(data.type === PIXI.Graphics.POLY) - { - // MAKE SURE WE HAVE THE CORRECT TYPE.. - if(data.fill) - { - if(data.points.length > 6) - { - if(data.points.length > 5 * 2) - { - webGLData = PIXI.WebGLGraphics.switchMode(webGL, 1); - PIXI.WebGLGraphics.buildComplexPoly(data, webGLData); - } - else - { - webGLData = PIXI.WebGLGraphics.switchMode(webGL, 0); - PIXI.WebGLGraphics.buildPoly(data, webGLData); - } - } - } - - if(data.lineWidth > 0) - { - webGLData = PIXI.WebGLGraphics.switchMode(webGL, 0); - PIXI.WebGLGraphics.buildLine(data, webGLData); - - } - } - else - { - webGLData = PIXI.WebGLGraphics.switchMode(webGL, 0); - - if(data.type === PIXI.Graphics.RECT) - { - PIXI.WebGLGraphics.buildRectangle(data, webGLData); - } - else if(data.type === PIXI.Graphics.CIRC || data.type === PIXI.Graphics.ELIP) - { - PIXI.WebGLGraphics.buildCircle(data, webGLData); - } - else if(data.type === PIXI.Graphics.RREC) - { - PIXI.WebGLGraphics.buildRoundedRectangle(data, webGLData); - } - } - - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - if(webGLData.dirty)webGLData.upload(); - } -}; - - -PIXI.WebGLGraphics.switchMode = function(webGL, type) -{ - var webGLData; - - if(!webGL.data.length) - { - webGLData = PIXI.WebGLGraphics.graphicsDataPool.pop() || new PIXI.WebGLGraphicsData(webGL.gl); - webGLData.mode = type; - webGL.data.push(webGLData); - } - else - { - webGLData = webGL.data[webGL.data.length-1]; - - if(webGLData.mode !== type || type === 1) - { - webGLData = PIXI.WebGLGraphics.graphicsDataPool.pop() || new PIXI.WebGLGraphicsData(webGL.gl); - webGLData.mode = type; - webGL.data.push(webGLData); - } - } - - webGLData.dirty = true; - - return webGLData; -}; - -/** - * 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 rounded rectangle to draw - * - * @static - * @private - * @method buildRoundedRectangle - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {Object} - */ -PIXI.WebGLGraphics.buildRoundedRectangle = function(graphicsData, webGLData) -{ - - var points = graphicsData.points; - var x = points[0]; - var y = points[1]; - var width = points[2]; - var height = points[3]; - var radius = points[4]; - - - var recPoints = []; - recPoints.push(x, y + radius); - recPoints = recPoints.concat(PIXI.WebGLGraphics.quadraticBezierCurve(x, y + height - radius, x, y + height, x + radius, y + height)); - recPoints = recPoints.concat(PIXI.WebGLGraphics.quadraticBezierCurve(x + width - radius, y + height, x + width, y + height, x + width, y + height - radius)); - recPoints = recPoints.concat(PIXI.WebGLGraphics.quadraticBezierCurve(x + width, y + radius, x + width, y, x + width - radius, y)); - recPoints = recPoints.concat(PIXI.WebGLGraphics.quadraticBezierCurve(x + radius, y, x, y, x, y + radius)); - - - 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; - - var triangles = PIXI.PolyK.Triangulate(recPoints); - - var i = 0; - for (i = 0; i < triangles.length; i+=3) - { - indices.push(triangles[i] + vecPos); - indices.push(triangles[i] + vecPos); - indices.push(triangles[i+1] + vecPos); - indices.push(triangles[i+2] + vecPos); - indices.push(triangles[i+2] + vecPos); - } - - for (i = 0; i < recPoints.length; i++) - { - verts.push(recPoints[i], recPoints[++i], r, g, b, alpha); - } - } - - if (graphicsData.lineWidth) { - var tempPoints = graphicsData.points; - - graphicsData.points = recPoints; - - PIXI.WebGLGraphics.buildLine(graphicsData, webGLData); - - graphicsData.points = tempPoints; - } -}; - -/** - * Calcul the points for a quadratic bezier curve. (helper function..) - * Based on : https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @param {number} fromX Origin point x - * @param {number} fromY Origin point x - * @param {number} cpX Control point x - * @param {number} cpY Control point y - * @param {number} toX Destination point x - * @param {number} toY Destination point y - * @return {number[]} - */ -PIXI.WebGLGraphics.quadraticBezierCurve = function(fromX, fromY, cpX, cpY, toX, toY) { - var xa, - ya, - xb, - yb, - x, - y, - n = 20, - points = []; - - function getPt(n1 , n2, perc) { - var diff = n2 - n1; - - return n1 + ( diff * perc ); - } - - var j = 0; - for (var i = 0; i <= n; i++ ) - { - j = i / n; - - // The Green Line - xa = getPt( fromX , cpX , j ); - ya = getPt( fromY , cpY , j ); - xb = getPt( cpX , toX , j ); - yb = getPt( cpY , toY , j ); - - // The Black Dot - x = getPt( xa , xb , j ); - y = getPt( ya , yb , j ); - - points.push(x, y); - } - return points; -}; - -/** - * 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) - { - // need to clone as we are going to slightly modify the shape.. - points = points.slice(); - - 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 complex polygon to draw - * - * @static - * @private - * @method buildPoly - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {Object} - */ -PIXI.WebGLGraphics.buildComplexPoly = function(graphicsData, webGLData) -{ - - //TODO - no need to copy this as it gets turned into a FLoat32Array anyways.. - var points = graphicsData.points.slice(); - if(points.length < 6)return; - - // get first and last point.. figure out the middle! - var indices = webGLData.indices; - webGLData.points = points; - webGLData.alpha = graphicsData.fillAlpha; - webGLData.color = PIXI.hex2rgb(graphicsData.fillColor); - - /* - calclate the bounds.. - */ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - var x,y; - - // get size.. - for (var i = 0; i < points.length; i+=2) - { - x = points[i]; - y = points[i+1]; - - minX = x < minX ? x : minX; - maxX = x > maxX ? x : maxX; - - minY = y < minY ? y : minY; - maxY = y > maxY ? y : maxY; - } - - // add a quad to the end cos there is no point making another buffer! - points.push(minX, minY, - maxX, minY, - maxX, maxY, - minX, maxY); - - // push a quad onto the end.. - - //TODO - this aint needed! - var length = points.length / 2; - for (i = 0; i < length; i++) - { - indices.push( i ); - } - -}; - -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); - } - -}; - -PIXI.WebGLGraphics.graphicsDataPool = []; - -PIXI.WebGLGraphicsData = function(gl) -{ - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.lastIndex = 0; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -}; - -PIXI.WebGLGraphicsData.prototype.reset = function() -{ - this.points = []; - this.indices = []; - this.lastIndex = 0; -}; - -PIXI.WebGLGraphicsData.prototype.upload = function() -{ - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; - -/** - * @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) - * @param preserveDrawingBuffer=false {Boolean} enables drawing buffer preservation, enable this if you need to call toDataUrl on the webgl context - * - */ -PIXI.WebGLRenderer = function(width, height, view, transparent, antialias, preserveDrawingBuffer) -{ - if(!PIXI.defaultRenderer) - { - PIXI.sayHello('webGL'); - 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 value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @property preserveDrawingBuffer - * @type Boolean - */ - this.preserveDrawingBuffer = preserveDrawingBuffer; - - /** - * 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, - preserveDrawingBuffer: preserveDrawingBuffer - }; - - var gl = null; - - ['experimental-webgl', 'webgl'].forEach(function(name) { - try { - gl = gl || this.view.getContext(name, this.options); - } catch(e) {} - }, this); - - if (!gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer' + this); - } - - this.gl = 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.primitiveBatch = new PIXI.WebGLPrimitiveBatch(gl); // primitive batch renderer - 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.stencilManager = new PIXI.WebGLStencilManager(gl); - this.blendModeManager = new PIXI.WebGLBlendModeManager(gl); - - 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.blendModeManager = this.blendModeManager; - // this.renderSession.primitiveBatch = this.primitiveBatch; - this.renderSession.spriteBatch = this.spriteBatch; - this.renderSession.stencilManager = this.stencilManager; - this.renderSession.renderer = this; - - 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(); - - - // interaction - if(stage._interactive) - { - //need to add some events! - if(!stage._interactiveEventsAdded) - { - stage._interactiveEventsAdded = true; - stage.interactionManager.setTarget(this); - } - } - - 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) -{ - this.renderSession.blendModeManager.setBlendMode(PIXI.blendModes.NORMAL); - // 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); - -// this.primitiveBatch.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(); - -// this.primitiveBatch.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..updateWebGLTexture(PIXI.texturesToUpdate[i], this.gl); - - - 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, texture.premultipliedAlpha); - - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.source); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); - 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); - - texture._dirty[gl.id] = false; - } - - 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, texture.premultipliedAlpha); - - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.source); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); - 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); - } - - texture._dirty[gl.id] = false; - } - -}; - -/** - * 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.primitiveBatch.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.primitiveBatch.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.WebGLBlendModeManager = function(gl) -{ - this.gl = gl; - this.currentBlendMode = 99999; -}; - -/** -* Sets-up the given blendMode from WebGL's point of view -* @method setBlendMode -* -* @param blendMode {Number} the blendMode, should be a Pixi const, such as PIXI.BlendModes.ADD -*/ -PIXI.WebGLBlendModeManager.prototype.setBlendMode = function(blendMode) -{ - if(this.currentBlendMode === blendMode)return false; - // console.log("SWAP!") - this.currentBlendMode = blendMode; - - var blendModeWebGL = PIXI.blendModesWebGL[this.currentBlendMode]; - this.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); - - return true; -}; - -PIXI.WebGLBlendModeManager.prototype.destroy = function() -{ - this.gl = null; -}; -/** - * @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); - - this.reverse = false; - this.count = 0; -}; - -/** -* 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 = renderSession.gl; - - if(maskData.dirty) - { - PIXI.WebGLGraphics.updateGraphics(maskData, gl); - } - - if(!maskData._webGL[gl.id].data.length)return; - - renderSession.stencilManager.pushStencil(maskData, maskData._webGL[gl.id].data[0], renderSession); -}; - -/** -* Removes the last filter from the filter stack and doesn't return it -* @method popMask -* -* @param renderSession {RenderSession} an object containing all the useful parameters -*/ -PIXI.WebGLMaskManager.prototype.popMask = function(maskData, renderSession) -{ - var gl = this.gl; - renderSession.stencilManager.popStencil(maskData, maskData._webGL[gl.id].data[0], renderSession); -}; - - -/** -* Destroys the mask stack -* @method destroy -*/ -PIXI.WebGLMaskManager.prototype.destroy = function() -{ - this.maskStack = null; - this.gl = null; -}; -/** - * @author Mat Groves http://matgroves.com/ @Doormat23 - */ - -//BA0285 -//Intercontinental Hotel, 888 Howard Street -//San Francisco - -/** -* @class WebGLStencilManager -* @constructor -* @param gl {WebGLContext} the current WebGL drawing context -* @private -*/ -PIXI.WebGLStencilManager = function(gl) -{ - - this.stencilStack = []; - this.setContext(gl); - this.reverse = true; - this.count = 0; - -}; - -/** -* Sets the drawing context to the one given in parameter -* @method setContext -* @param gl {WebGLContext} the current WebGL drawing context -*/ -PIXI.WebGLStencilManager.prototype.setContext = function(gl) -{ - this.gl = gl; -}; - -/** -* Applies the Mask and adds it to the current filter stack -* @method pushMask -* @param maskData {Array} -* @param renderSession {RenderSession} -*/ -PIXI.WebGLStencilManager.prototype.pushStencil = function(graphics, webGLData, renderSession) -{ - var gl = this.gl; - this.bindGraphics(graphics, webGLData, renderSession); - - if(this.stencilStack.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - this.reverse = true; - this.count = 0; - } - - this.stencilStack.push(webGLData); - - var level = this.count; - - gl.colorMask(false, false, false, false); - - gl.stencilFunc(gl.ALWAYS,0,0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); - - // draw the triangle strip! - - if(webGLData.mode === 1) - { - - gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); - - if(this.reverse) - { - gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - else - { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - - // draw a quad to increment.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - if(this.reverse) - { - gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); - } - else - { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - } - - this.reverse = !this.reverse; - } - else - { - if(!this.reverse) - { - gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - else - { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - - gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); - - if(!this.reverse) - { - gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); - } - else - { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - } - } - - gl.colorMask(true, true, true, true); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); - - this.count++; -}; - -//TODO this does not belong here! -PIXI.WebGLStencilManager.prototype.bindGraphics = function(graphics, webGLData, renderSession) -{ - //if(this._currentGraphics === graphics)return; - this._currentGraphics = graphics; - - var gl = this.gl; - - // bind the graphics object.. - var projection = renderSession.projection, - offset = renderSession.offset, - shader;// = renderSession.shaderManager.primitiveShader; - - if(webGLData.mode === 1) - { - shader = renderSession.shaderManager.complexPrimativeShader; - - renderSession.shaderManager.setShader( shader ); - - gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); - - gl.uniform2f(shader.projectionVector, projection.x, -projection.y); - gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); - - gl.uniform3fv(shader.tintColor, PIXI.hex2rgb(graphics.tint)); - gl.uniform3fv(shader.color, webGLData.color); - - gl.uniform1f(shader.alpha, graphics.worldAlpha * webGLData.alpha); - - gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); - - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0); - - - // now do the rest.. - // set the index buffer! - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); - } - else - { - //renderSession.shaderManager.activatePrimitiveShader(); - shader = renderSession.shaderManager.primitiveShader; - renderSession.shaderManager.setShader( shader ); - - gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); - - gl.uniform2f(shader.projectionVector, projection.x, -projection.y); - gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); - - gl.uniform3fv(shader.tintColor, PIXI.hex2rgb(graphics.tint)); - - gl.uniform1f(shader.alpha, graphics.worldAlpha); - - gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); - - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); - gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); - - // set the index buffer! - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); - } -}; - -PIXI.WebGLStencilManager.prototype.popStencil = function(graphics, webGLData, renderSession) -{ - var gl = this.gl; - this.stencilStack.pop(); - - this.count--; - - if(this.stencilStack.length === 0) - { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - - } - else - { - - var level = this.count; - - this.bindGraphics(graphics, webGLData, renderSession); - - gl.colorMask(false, false, false, false); - - if(webGLData.mode === 1) - { - this.reverse = !this.reverse; - - if(this.reverse) - { - gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - else - { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - - // draw a quad to increment.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - gl.stencilFunc(gl.ALWAYS,0,0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); - - // draw the triangle strip! - gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); - - if(!this.reverse) - { - gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); - } - else - { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - } - - } - else - { - // console.log("<<>>") - if(!this.reverse) - { - gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - else - { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - - gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); - - if(!this.reverse) - { - gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); - } - else - { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - } - } - - gl.colorMask(true, true, true, true); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); - - - } - - //renderSession.shaderManager.deactivatePrimitiveShader(); -}; - -/** -* Destroys the mask stack -* @method destroy -*/ -PIXI.WebGLStencilManager.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 = []; - this.shaderMap = []; - - for (var i = 0; i < this.maxAttibs; i++) { - this.attribState[i] = false; - } - - this.setContext(gl); - // the final one is used for the rendering strips -}; - - -/** -* 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); - - // the next one is used for rendering triangle strips - this.complexPrimativeShader = new PIXI.ComplexPrimitiveShader(gl); - - // this shader is used for the default sprite rendering - this.defaultShader = new PIXI.PixiShader(gl); - - // this shader is used for the fast sprite rendering - this.fastShader = new PIXI.PixiFastShader(gl); - - // the next one is used for rendering triangle strips - this.stripShader = new PIXI.StripShader(gl); - this.setShader(this.defaultShader); -}; - - -/** -* Takes the attributes given in parameters -* @method setAttribs -* @param attribs {Array} attribs -*/ -PIXI.WebGLShaderManager.prototype.setAttribs = function(attribs) -{ - // reset temp state - - var i; - - for (i = 0; i < this.tempAttribState.length; i++) - { - this.tempAttribState[i] = false; - } - - // set the new attribs - for (i = 0; i < attribs.length; i++) - { - var attribId = attribs[i]; - this.tempAttribState[attribId] = true; - } - - var gl = this.gl; - - for (i = 0; i < this.attribState.length; i++) - { - if(this.attribState[i] !== this.tempAttribState[i]) - { - this.attribState[i] = this.tempAttribState[i]; - - if(this.tempAttribState[i]) - { - gl.enableVertexAttribArray(i); - } - else - { - gl.disableVertexAttribArray(i); - } - } - } -}; - -PIXI.WebGLShaderManager.prototype.setShader = function(shader) -{ - if(this._currentId === shader._UID)return false; - - this._currentId = shader._UID; - - this.currentShader = shader; - - this.gl.useProgram(shader.program); - this.setAttribs(shader.attributes); - - return true; -}; - -/** -* Destroys -* @method destroy -*/ -PIXI.WebGLShaderManager.prototype.destroy = function() -{ - this.attribState = null; - - this.tempAttribState = null; - - this.primitiveShader.destroy(); - - this.defaultShader.destroy(); - - this.fastShader.destroy(); - - this.stripShader.destroy(); - - this.gl = null; -}; - - -/** - * @author Mat Groves - * - * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ - * for creating the original pixi version! - * - * 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 = 2000;//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); - - this.dirty = true; - - this.textures = []; - this.blendModes = []; -}; - -/** -* -* @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) -{ - var texture = sprite.texture; - - //TODO set blend modes.. - // check texture.. - if(this.currentBatchSize >= this.size) - { - //return; - this.flush(); - this.currentBaseTexture = texture.baseTexture; - } - - // get the uvs for the texture - var uvs = texture._uvs; - // if the uvs have not updated then no point rendering just yet! - if(!uvs)return; - - // get the sprites current alpha - var alpha = sprite.worldAlpha; - var tint = sprite.tint; - - var verticies = this.vertices; - - - // TODO trim?? - var aX = sprite.anchor.x; - var aY = sprite.anchor.y; - - var w0, w1, h0, h1; - - if (texture.trim) - { - // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. - var trim = texture.trim; - - w1 = trim.x - aX * trim.width; - w0 = w1 + texture.crop.width; - - h1 = trim.y - aY * trim.height; - h0 = h1 + texture.crop.height; - - } - else - { - w0 = (texture.frame.width ) * (1-aX); - w1 = (texture.frame.width ) * -aX; - - h0 = texture.frame.height * (1-aY); - h1 = texture.frame.height * -aY; - } - - var index = this.currentBatchSize * 4 * this.vertSize; - - var worldTransform = sprite.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 batchsize - this.textures[this.currentBatchSize] = sprite.texture.baseTexture; - this.blendModes[this.currentBatchSize] = sprite.blendMode; - - 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; - - - // check texture.. - if(this.currentBatchSize >= this.size) - { - //return; - this.flush(); - this.currentBaseTexture = texture.baseTexture; - } - - // 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.tileScaleOffset.x; - tilingSprite.tilePosition.y %= texture.baseTexture.height * tilingSprite.tileScaleOffset.y; - - var offsetX = tilingSprite.tilePosition.x/(texture.baseTexture.width*tilingSprite.tileScaleOffset.x); - var offsetY = tilingSprite.tilePosition.y/(texture.baseTexture.height*tilingSprite.tileScaleOffset.y); - - 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; - var aY = tilingSprite.anchor.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.textures[this.currentBatchSize] = texture.baseTexture; - this.blendModes[this.currentBatchSize] = tilingSprite.blendMode; - 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; - - this.renderSession.shaderManager.setShader(this.renderSession.shaderManager.defaultShader); - - if(this.dirty) - { - this.dirty = false; - // bind the main texture - gl.activeTexture(gl.TEXTURE0); - - // bind the buffers - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - - // 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); - - } - - // 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 nextTexture, nextBlendMode; - var batchSize = 0; - var start = 0; - - var currentBaseTexture = null; - var currentBlendMode = this.renderSession.blendModeManager.currentBlendMode; - - for (var i = 0, j = this.currentBatchSize; i < j; i++) { - - nextTexture = this.textures[i]; - nextBlendMode = this.blendModes[i]; - - if(currentBaseTexture !== nextTexture || currentBlendMode !== nextBlendMode) - { - this.renderBatch(currentBaseTexture, batchSize, start); - - start = i; - batchSize = 0; - currentBaseTexture = nextTexture; - currentBlendMode = nextBlendMode; - - this.renderSession.blendModeManager.setBlendMode( currentBlendMode ); - } - - batchSize++; - } - - this.renderBatch(currentBaseTexture, batchSize, start); - - // then reset the batch! - this.currentBatchSize = 0; -}; - -PIXI.WebGLSpriteBatch.prototype.renderBatch = function(texture, size, startIndex) -{ - if(size === 0)return; - - var gl = this.gl; - // bind the current texture - gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id] || PIXI.createWebGLTexture(texture, gl)); - - // check if a texture is dirty.. - if(texture._dirty[gl.id]) - { - PIXI.updateWebGLTexture(this.currentBaseTexture, gl); - } - - // now draw those suckas! - gl.drawElements(gl.TRIANGLES, size * 6, gl.UNSIGNED_SHORT, startIndex * 6 * 2); - - // increment the draw count - this.renderSession.drawCount++; -}; - -/** -* -* @method stop -* -*/ -PIXI.WebGLSpriteBatch.prototype.stop = function() -{ - this.flush(); -}; - -/** -* -* @method start -* -*/ -PIXI.WebGLSpriteBatch.prototype.start = function() -{ - this.dirty = true; -}; - -/** -* Destroys the SpriteBatch -* @method destroy -*/ -PIXI.WebGLSpriteBatch.prototype.destroy = function() -{ - - this.vertices = null; - this.indices = null; - - this.gl.deleteBuffer( this.vertexBuffer ); - this.gl.deleteBuffer( this.indexBuffer ); - - this.currentBaseTexture = null; - - this.gl = null; -}; - - -/** - * @author Mat Groves - * - * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ - * for creating the original pixi version! - * - * Heavily inspired by LibGDX's WebGLSpriteBatch: - * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/WebGLSpriteBatch.java - */ - -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); -}; - -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.renderSession.blendModeManager.currentBlendMode) - { - this.flush(); - this.renderSession.blendModeManager.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]); - - // 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); - - -}; - - - -/** - * @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; - // console.log(this.width) - this.width = projection.x * 2; - this.height = -projection.y * 2; - this.buffer = buffer; -}; - -/** -* Applies the filter and adds it to the current filter stack -* @method pushFilter -* @param filterBlock {Object} the filter that will be pushed to the current filter stack -*/ -PIXI.WebGLFilterManager.prototype.pushFilter = function(filterBlock) -{ - var gl = this.gl; - - var projection = this.renderSession.projection; - var offset = this.renderSession.offset; - - filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); - - - // filter program - // OPTIMISATION - the first filter is free if its a simple color change? - this.filterStack.push(filterBlock); - - var filter = filterBlock.filterPasses[0]; - - this.offsetX += filterBlock._filterArea.x; - this.offsetY += filterBlock._filterArea.y; - - var texture = this.texturePool.pop(); - if(!texture) - { - texture = new PIXI.FilterTexture(this.gl, this.width, this.height); - } - else - { - texture.resize(this.width, this.height); - } - - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; - - var padding = filter.padding; - filterArea.x -= padding; - filterArea.y -= padding; - filterArea.width += padding * 2; - filterArea.height += padding * 2; - - // cap filter to screen size.. - if(filterArea.x < 0)filterArea.x = 0; - if(filterArea.width > this.width)filterArea.width = this.width; - if(filterArea.y < 0)filterArea.y = 0; - if(filterArea.height > this.height)filterArea.height = this.height; - - //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); - - // set view port - gl.viewport(0, 0, filterArea.width, filterArea.height); - - projection.x = filterArea.width/2; - projection.y = -filterArea.height/2; - - offset.x = -filterArea.x; - offset.y = -filterArea.y; - - // update projection - // now restore the regular shader.. - this.renderSession.shaderManager.setShader(this.defaultShader); - gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); - gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); - - gl.colorMask(true, true, true, true); - gl.clearColor(0,0,0, 0); - gl.clear(gl.COLOR_BUFFER_BIT); - - filterBlock._glFilterTexture = texture; - -}; - - -/** -* Removes the last filter from the filter stack and doesn't return it -* @method popFilter -*/ -PIXI.WebGLFilterManager.prototype.popFilter = function() -{ - var gl = this.gl; - var filterBlock = this.filterStack.pop(); - var filterArea = filterBlock._filterArea; - var texture = filterBlock._glFilterTexture; - var projection = this.renderSession.projection; - var offset = this.renderSession.offset; - - if(filterBlock.filterPasses.length > 1) - { - gl.viewport(0, 0, filterArea.width, 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); - outputTexture.resize(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, true);//this.transparent); - } - else - { - var currentFilter = this.filterStack[this.filterStack.length-1]; - filterArea = currentFilter._filterArea; - - sizeX = filterArea.width; - sizeY = filterArea.height; - - offsetX = filterArea.x; - offsetY = filterArea.y; - - buffer = currentFilter._glFilterTexture.frameBuffer; - } - - - - // TODO need toremove thease global elements.. - projection.x = sizeX/2; - projection.y = -sizeY/2; - - offset.x = offsetX; - offset.y = offsetY; - - filterArea = filterBlock._filterArea; - - var x = filterArea.x-offsetX; - var y = filterArea.y-offsetY; - - // update the buffers.. - // make sure to flip the y! - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = x; - this.vertexArray[1] = y + filterArea.height; - - this.vertexArray[2] = x + filterArea.width; - this.vertexArray[3] = y + filterArea.height; - - this.vertexArray[4] = x; - this.vertexArray[5] = y; - - this.vertexArray[6] = x + filterArea.width; - this.vertexArray[7] = y; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - - //console.log(this.vertexArray) - //console.log(this.uvArray) - //console.log(sizeX + " : " + sizeY) - - 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.. - this.renderSession.shaderManager.setShader(this.defaultShader); - gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); - gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); - - // return the texture to the pool - this.texturePool.push(texture); - filterBlock._glFilterTexture = null; -}; - - -/** -* Applies the filter to the specified area -* @method applyFilterPass -* @param filter {AbstractFilter} the filter that needs to be applied -* @param filterArea {texture} TODO - might need an update -* @param width {Number} the horizontal range of the filter -* @param height {Number} the vertical range of the filter -*/ -PIXI.WebGLFilterManager.prototype.applyFilterPass = function(filter, filterArea, width, height) -{ - // use program - var gl = this.gl; - var shader = filter.shaders[gl.id]; - - if(!shader) - { - shader = new PIXI.PixiShader(gl); - - shader.fragmentSrc = filter.fragmentSrc; - shader.uniforms = filter.uniforms; - shader.init(); - - filter.shaders[gl.id] = shader; - } - - // set the shader - this.renderSession.shaderManager.setShader(shader); - -// gl.useProgram(shader.program); - - gl.uniform2f(shader.projectionVector, width/2, -height/2); - gl.uniform2f(shader.offsetVector, 0,0); - - if(filter.uniforms.dimensions) - { - filter.uniforms.dimensions.value[0] = this.width;//width; - filter.uniforms.dimensions.value[1] = this.height;//height; - filter.uniforms.dimensions.value[2] = this.vertexArray[0]; - filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; - } - - // console.log(this.uvArray ) - 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[i].destroy(); - } - - this.texturePool = null; - - //destroy buffers.. - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.uvBuffer); - gl.deleteBuffer(this.colorBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - -/** - * @author Mat Groves http://matgroves.com/ @Doormat23 - */ - -/** -* @class FilterTexture -* @constructor -* @param gl {WebGLContext} the current WebGL drawing context -* @param width {Number} the horizontal range of the filter -* @param height {Number} the vertical range of the filter -* @param scaleMode {Number} Should be one of the PIXI.scaleMode consts -* @private -*/ -PIXI.FilterTexture = function(gl, width, height, scaleMode) -{ - /** - * @property gl - * @type WebGLContext - */ - this.gl = gl; - - // next time to create a frame buffer and texture - this.frameBuffer = gl.createFramebuffer(); - this.texture = gl.createTexture(); - - scaleMode = scaleMode || PIXI.scaleModes.DEFAULT; - - gl.bindTexture(gl.TEXTURE_2D, this.texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer ); - - gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer ); - gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0); - - // required for masking a mask?? - this.renderBuffer = gl.createRenderbuffer(); - gl.bindRenderbuffer(gl.RENDERBUFFER, this.renderBuffer); - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, this.renderBuffer); - - this.resize(width, height); -}; - - -/** -* Clears the filter texture -* @method clear -*/ -PIXI.FilterTexture.prototype.clear = function() -{ - var gl = this.gl; - - gl.clearColor(0,0,0, 0); - gl.clear(gl.COLOR_BUFFER_BIT); -}; - -/** - * Resizes the texture to the specified width and height - * - * @method resize - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -PIXI.FilterTexture.prototype.resize = function(width, height) -{ - if(this.width === width && this.height === height) return; - - this.width = width; - this.height = height; - - var gl = this.gl; - - gl.bindTexture(gl.TEXTURE_2D, this.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - - // update the stencil buffer width and height - gl.bindRenderbuffer(gl.RENDERBUFFER, this.renderBuffer); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, width, height); -}; - -/** -* Destroys the filter texture -* @method destroy -*/ -PIXI.FilterTexture.prototype.destroy = function() -{ - var gl = this.gl; - gl.deleteFramebuffer( this.frameBuffer ); - gl.deleteTexture( this.texture ); - - this.frameBuffer = null; - this.texture = null; -}; - -/** - * @author Mat Groves - * - * - */ -/** - * 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) -{ - if(!PIXI.defaultRenderer) - { - PIXI.sayHello("Canvas"); - 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; - - /** - * 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 true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - */ - roundPixels: false - }; - - 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 (navigator.isCocoonJS && this.view.screencanvas) { - this.context.fillStyle = "black"; - this.context.clear(); - } - - 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(); - } - } - else if (data.type === PIXI.Graphics.RREC) - { - var rx = points[0]; - var ry = points[1]; - var width = points[2]; - var height = points[3]; - var radius = points[4]; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if(data.fillColor || data.fillColor === 0) - { - 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(); - } - else if (data.type === PIXI.Graphics.RREC) - { - var rx = points[0]; - var ry = points[1]; - var width = points[2]; - var height = points[3]; - var radius = points[4]; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - 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 boundsPadding - * @type Number - */ - this.boundsPadding = 10; - - /** - * Used to detect if the graphics object has changed if this is set to true then the graphics object will be recalculated - * - * @type {Boolean} - */ - this.dirty = true; -}; - -// 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; -}; - -/** - * Calculate the points for a quadratic bezier curve. - * Based on : https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @method quadraticCurveTo - * @param {number} cpX Control point x - * @param {number} cpY Control point y - * @param {number} toX Destination point x - * @param {number} toY Destination point y - * @return {PIXI.Graphics} - */ -PIXI.Graphics.prototype.quadraticCurveTo = function(cpX, cpY, toX, toY) -{ - if( this.currentPath.points.length === 0)this.moveTo(0,0); - - var xa, - ya, - n = 20, - points = this.currentPath.points; - if(points.length === 0)this.moveTo(0, 0); - - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; i++ ) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve. - * - * @method bezierCurveTo - * @param {number} cpX Control point x - * @param {number} cpY Control point y - * @param {number} cpX2 Second Control point x - * @param {number} cpY2 Second Control point y - * @param {number} toX Destination point x - * @param {number} toY Destination point y - * @return {PIXI.Graphics} - */ -PIXI.Graphics.prototype.bezierCurveTo = function(cpX, cpY, cpX2, cpY2, toX, toY) -{ - if( this.currentPath.points.length === 0)this.moveTo(0,0); - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i=1; i b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc() method creates an arc/curve (used to create circles, or parts of circles). - * - * @method arc - * @param {number} cx The x-coordinate of the center of the circle - * @param {number} cy The y-coordinate of the center of the circle - * @param {number} radius The radius of the circle - * @param {number} startAngle The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param {number} endAngle The ending angle, in radians - * @param {number} anticlockwise Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} - */ -PIXI.Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - var points = this.currentPath.points; - - if(points.length !== 0 && points[points.length-2] !== startX || points[points.length-1] !== startY) - { - this.moveTo(startX, startY); - points = this.currentPath.points; - } - - if (startAngle === endAngle)return this; - - if( !anticlockwise && endAngle <= startAngle ) - { - endAngle += Math.PI * 2; - } - else if( anticlockwise && startAngle <= endAngle ) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = ( Math.abs(sweep)/ (Math.PI * 2) ) * 40; - - if( sweep === 0 ) return this; - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for(var i=0; i<=segMinus; i++) - { - var real = i + remainder * i; - - - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - 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.drawPath = function(path) -{ - 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.graphicsData.push(this.currentPath); - - this.currentPath.points = this.currentPath.points.concat(path); - 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; -}; - -/** - * @method drawRoundedRect - * - * @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 - * @param radius {Number} Radius of the rectangle corners - */ -PIXI.Graphics.prototype.drawRoundedRect = function( x, y, width, height, 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, width, height, radius], type:PIXI.Graphics.RREC}; - - 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 center of the ellipse - * @param y {Number} The Y coordinate of the center of the ellipse - * @param width {Number} The half width of the ellipse - * @param height {Number} The half 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; - } - - this._cachedSprite.alpha = this.alpha; - PIXI.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); - - return; - } - else - { - renderSession.spriteBatch.stop(); - renderSession.blendModeManager.setBlendMode(this.blendMode); - - 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]); - } - - // for (var i = this.graphicsData.length - 1; i >= 0; i--) { - // this.graphicsData[i] - -// }; - - 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 ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - 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.alpha = this.alpha; - - // 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; -PIXI.Graphics.RREC = 4; - - -/** - * @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) -{ - PIXI.DisplayObjectContainer.call( this ); - - this.texture = texture; - - // set up the main bits.. - this.uvs = new PIXI.Float32Array([0, 1, - 1, 1, - 1, 0, - 0,1]); - - this.verticies = new PIXI.Float32Array([0, 0, - 100,0, - 100,100, - 0, 100]); - - this.colors = new PIXI.Float32Array([1, 1, 1, 1]); - - this.indices = new PIXI.Uint16Array([0, 1, 2, 3]); - - - this.dirty = true; -}; - -// constructor -PIXI.Strip.prototype = Object.create(PIXI.DisplayObjectContainer.prototype); -PIXI.Strip.prototype.constructor = PIXI.Strip; - -PIXI.Strip.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; - // render triangle strip.. - - renderSession.spriteBatch.stop(); - - // init! init! - if(!this._vertexBuffer)this._initWebGL(renderSession); - - renderSession.shaderManager.setShader(renderSession.shaderManager.stripShader); - - this._renderStrip(renderSession); - - ///renderSession.shaderManager.activateDefaultShader(); - - renderSession.spriteBatch.start(); - - //TODO check culling -}; - -PIXI.Strip.prototype._initWebGL = function(renderSession) -{ - // build the strip! - var gl = renderSession.gl; - - this._vertexBuffer = gl.createBuffer(); - this._indexBuffer = gl.createBuffer(); - this._uvBuffer = gl.createBuffer(); - this._colorBuffer = gl.createBuffer(); - - gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.verticies, gl.DYNAMIC_DRAW); - - gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.STATIC_DRAW); - - gl.bindBuffer(gl.ARRAY_BUFFER, this._colorBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.colors, gl.STATIC_DRAW); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); -}; - -PIXI.Strip.prototype._renderStrip = function(renderSession) -{ - var gl = renderSession.gl; - var projection = renderSession.projection, - offset = renderSession.offset, - shader = renderSession.shaderManager.stripShader; - - - // gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mat4Real); - - gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); - - // set uniforms - gl.uniformMatrix3fv(shader.translationMatrix, false, this.worldTransform.toArray(true)); - gl.uniform2f(shader.projectionVector, projection.x, -projection.y); - gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); - gl.uniform1f(shader.alpha, 1); - - if(!this.dirty) - { - - gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer); - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.verticies); - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); - - // update the uvs - gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer); - gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); - - gl.activeTexture(gl.TEXTURE0); - // bind the current texture - gl.bindTexture(gl.TEXTURE_2D, this.texture.baseTexture._glTextures[gl.id] || PIXI.createWebGLTexture(this.texture.baseTexture, gl)); - - // dont need to upload! - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer); - - - } - else - { - - this.dirty = false; - gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.verticies, gl.STATIC_DRAW); - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); - - // update the uvs - gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.STATIC_DRAW); - gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); - - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, this.texture.baseTexture._glTextures[gl.id] || PIXI.createWebGLTexture(this.texture.baseTexture, gl)); - - // dont need to upload! - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); - - } - //console.log(gl.TRIANGLE_STRIP) - // - // - gl.drawElements(gl.TRIANGLE_STRIP, this.indices.length, gl.UNSIGNED_SHORT, 0); - - -}; - -PIXI.Strip.prototype._renderCanvas = function(renderSession) -{ - var context = renderSession.context; - - var transform = this.worldTransform; - - if (renderSession.roundPixels) - { - context.setTransform(transform.a, transform.c, transform.b, transform.d, transform.tx | 0, transform.ty | 0); - } - else - { - context.setTransform(transform.a, transform.c, transform.b, transform.d, transform.tx, transform.ty); - } - - var strip = this; - // draw triangles!! - var verticies = strip.verticies; - var uvs = strip.uvs; - - var length = verticies.length/2; - this.count++; - - for (var i = 0; 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]; - - if(true) - { - - //expand(); - var centerX = (x0 + x1 + x2)/3; - var centerY = (y0 + y1 + y2)/3; - - var normX = x0 - centerX; - var normY = y0 - centerY; - - var dist = Math.sqrt( normX * normX + normY * normY ); - x0 = centerX + (normX / dist) * (dist + 3); - y0 = centerY + (normY / dist) * (dist + 3); - - // - - normX = x1 - centerX; - normY = y1 - centerY; - - dist = Math.sqrt( normX * normX + normY * normY ); - x1 = centerX + (normX / dist) * (dist + 3); - y1 = centerY + (normY / dist) * (dist + 3); - - normX = x2 - centerX; - normY = y2 - centerY; - - dist = Math.sqrt( normX * normX + normY * normY ); - x2 = centerX + (normX / dist) * (dist + 3); - y2 = centerY + (normY / dist) * (dist + 3); - - } - - 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(); - } -}; - -/* - * 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; - - this.verticies = new PIXI.Float32Array(points.length * 4); - this.uvs = new PIXI.Float32Array(points.length * 4); - this.colors = new PIXI.Float32Array(points.length * 2); - this.indices = new PIXI.Uint16Array(points.length * 2); - - - this.refresh(); -}; - - -// constructor -PIXI.Rope.prototype = Object.create( PIXI.Strip.prototype ); -PIXI.Rope.prototype.constructor = PIXI.Rope; - -/* - * Refreshes - * - * @method refresh - */ -PIXI.Rope.prototype.refresh = function() -{ - var points = this.points; - if(points.length < 1) return; - - var uvs = this.uvs; - - var lastPoint = points[0]; - var indices = this.indices; - var colors = this.colors; - - this.count-=0.2; - - uvs[0] = 0; - uvs[1] = 0; - uvs[2] = 0; - uvs[3] = 1; - - colors[0] = 1; - colors[1] = 1; - - indices[0] = 0; - indices[1] = 1; - - var total = points.length, - point, index, amount; - - for (var i = 1; i < total; i++) - { - point = points[i]; - index = i * 4; - // time to do some smart drawing! - amount = i / (total-1); - - if(i%2) - { - uvs[index] = amount; - uvs[index+1] = 0; - - uvs[index+2] = amount; - uvs[index+3] = 1; - } - else - { - uvs[index] = amount; - uvs[index+1] = 0; - - uvs[index+2] = amount; - uvs[index+3] = 1; - } - - index = i * 2; - colors[index] = 1; - colors[index+1] = 1; - - index = i * 2; - indices[index] = index; - indices[index + 1] = index + 1; - - lastPoint = point; - } -}; - -/* - * Updates the object transform for rendering - * - * @method updateTransform - * @private - */ -PIXI.Rope.prototype.updateTransform = function() -{ - - var points = this.points; - if(points.length < 1)return; - - var lastPoint = points[0]; - var nextPoint; - var perp = {x:0, y:0}; - - this.count-=0.2; - - var verticies = this.verticies; - var total = points.length, - point, index, ratio, perpLength, num; - - for (var i = 0; i < total; i++) - { - point = points[i]; - index = i * 4; - - if(i < points.length-1) - { - nextPoint = points[i+1]; - } - else - { - nextPoint = point; - } - - perp.y = -(nextPoint.x - lastPoint.x); - perp.x = nextPoint.y - lastPoint.y; - - ratio = (1 - (i / (total-1))) * 10; - - if(ratio > 1) ratio = 1; - - perpLength = Math.sqrt(perp.x * perp.x + perp.y * perp.y); - num = this.texture.height / 2; //(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio; - perp.x /= perpLength; - perp.y /= perpLength; - - perp.x *= num; - perp.y *= num; - - 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 Sprite - * @constructor - * @param texture {Texture} the texture of the tiling sprite - * @param width {Number} the width of the tiling sprite - * @param height {Number} the height of the tiling sprite - */ -PIXI.TilingSprite = function(texture, width, height) -{ - PIXI.Sprite.call( this, texture); - - /** - * The 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; - } -}); - -PIXI.TilingSprite.prototype.setTexture = function(texture) -{ - if (this.texture === texture) return; - - this.texture = texture; - - this.refreshTexture = true; - - this.cachedTint = 0xFFFFFF; -}; - -/** -* Renders the object using the WebGL renderer -* -* @method _renderWebGL -* @param renderSession {RenderSession} -* @private -*/ -PIXI.TilingSprite.prototype._renderWebGL = function(renderSession) -{ - if (this.visible === false || this.alpha === 0) return; - var i,j; - - 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.refreshTexture) - { - this.generateTilingTexture(true); - - if (this.tilingTexture && this.tilingTexture.needsUpdate) - { - //TODO - tweaking - PIXI.updateWebGLTexture(this.tilingTexture.baseTexture, renderSession.gl); - this.tilingTexture.needsUpdate = false; - // this.tilingTexture._uvs = null; - } - } - 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; -}; - - - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @method onTextureUpdate - * @param event - * @private - */ -PIXI.TilingSprite.prototype.onTextureUpdate = function() -{ - // overriding the sprite version of this! -}; - - -/** -* -* @method generateTilingTexture -* -* @param forcePowerOfTwo {Boolean} Whether we want to force the texture to be a power of two -*/ -PIXI.TilingSprite.prototype.generateTilingTexture = function(forcePowerOfTwo) -{ - if (!this.texture.baseTexture.hasLoaded) return; - - var texture = this.texture; - var frame = texture.frame; - var targetWidth, targetHeight; - - // Check that the frame is the same size as the base texture. - var isFrame = frame.width !== texture.baseTexture.width || frame.height !== texture.baseTexture.height; - - var newTextureRequired = false; - - if (!forcePowerOfTwo) - { - if (isFrame) - { - targetWidth = frame.width; - targetHeight = frame.height; - - newTextureRequired = true; - } - } - else - { - targetWidth = PIXI.getNextPowerOfTwo(frame.width); - targetHeight = PIXI.getNextPowerOfTwo(frame.height); - - if (frame.width !== targetWidth || frame.height !== targetHeight) newTextureRequired = true; - } - - if (newTextureRequired) - { - var canvasBuffer; - - if (this.tilingTexture && this.tilingTexture.isTiling) - { - canvasBuffer = this.tilingTexture.canvasBuffer; - canvasBuffer.resize(targetWidth, targetHeight); - this.tilingTexture.baseTexture.width = targetWidth; - this.tilingTexture.baseTexture.height = targetHeight; - this.tilingTexture.needsUpdate = true; - } - else - { - canvasBuffer = new PIXI.CanvasBuffer(targetWidth, targetHeight); - - this.tilingTexture = PIXI.Texture.fromCanvas(canvasBuffer.canvas); - this.tilingTexture.canvasBuffer = canvasBuffer; - this.tilingTexture.isTiling = true; - } - - canvasBuffer.context.drawImage(texture.baseTexture.source, - texture.crop.x, - texture.crop.y, - texture.crop.width, - texture.crop.height, - 0, - 0, - targetWidth, - targetHeight); - - this.tileScaleOffset.x = frame.width / targetWidth; - this.tileScaleOffset.y = frame.height / targetHeight; - } - else - { - // TODO - switching? - if (this.tilingTexture && this.tilingTexture.isTiling) - { - // destroy the tiling texture! - // TODO could store this somewhere? - this.tilingTexture.destroy(true); - } - - this.tileScaleOffset.x = 1; - this.tileScaleOffset.y = 1; - this.tilingTexture = texture; - } - - this.refreshTexture = false; - 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 / 5; - }, - setFrame: function (frameIndex, time, r, g, b, a) { - 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 = { - animationSpeed: 1, - current: null, - previous: null, - currentTime: 0, - previousTime: 0, - currentLoop: false, - previousLoop: false, - mixTime: 0, - mixDuration: 0, - update: function (delta) { - this.currentTime += (delta * this.animationSpeed); //timeScale: Multiply delta by the speed of animation required. - 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.substr(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); - - slotContainer.alpha = slot.a; - slot.currentSprite.tint = PIXI.rgb2hex([slot.r,slot.g,slot.b]); - } - - 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; - - //TODO will be used for futer pixi 1.5... - this.id = PIXI.BaseTextureCacheIdGenerator++; - - /** - * Controls if RGB channels should be premultiplied by Alpha (WebGL only) - * - * @property - * @type Boolean - * @default TRUE - */ - this.premultipliedAlpha = true; - - // used for webGL - this._glTextures = []; - - // used for webGL teture updateing... - this._dirty = []; - - if(!source)return; - - if((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - 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; - - for (var i = 0; i < scope._glTextures.length; i++) - { - scope._dirty[i] = true; - } - - // add it to somewhere... - scope.dispatchEvent( { type: 'loaded', content: scope } ); - }; - this.source.onerror = function() { - scope.dispatchEvent( { type: 'error', content: scope } ); - }; - } - - this.imageUrl = null; - this._powerOf2 = false; - - - -}; - -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]; - delete PIXI.TextureCache[this.imageUrl]; - this.imageUrl = null; - this.source.src = null; - } - else if (this.source && this.source._pixiId) - { - delete PIXI.BaseTextureCache[this.source._pixiId]; - } - 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]; - - if(crossorigin === undefined && imageUrl.indexOf('data:') === -1) crossorigin = true; - - if(!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//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; -}; - -/** - * Helper function that returns a base texture based on a canvas element - * If the image is not in the base texture cache it will be created and loaded - * - * @static - * @method fromCanvas - * @param canvas {Canvas} The canvas element source of the texture - * @param scaleMode {Number} Should be one of the PIXI.scaleMode consts - * @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 ); - - /** - * Does this Texture have any frame data assigned to it? - * - * @property noFrame - * @type Boolean - */ - this.noFrame = false; - - if (!frame) - { - this.noFrame = true; - frame = new PIXI.Rectangle(0,0,1,1); - } - - if (baseTexture instanceof PIXI.Texture) - { - baseTexture = baseTexture.baseTexture; - } - - /** - * The base texture that this texture uses. - * - * @property baseTexture - * @type BaseTexture - */ - this.baseTexture = baseTexture; - - /** - * The frame specifies the region of the base texture that this texture uses - * - * @property frame - * @type Rectangle - */ - this.frame = frame; - - /** - * The trim point - * - * @property trim - * @type Rectangle - */ - this.trim = null; - - /** - * This will let the renderer know if the texture is valid. If its not then it cannot be rendered. - * - * @property valid - * @type Boolean - */ - this.valid = false; - - /** - * The context scope under which events are run. - * - * @property scope - * @type Object - */ - this.scope = this; - - /** - * The WebGL UV data cache. - * - * @private - * @property _uvs - * @type Object - */ - this._uvs = null; - - /** - * The width of the Texture in pixels. - * - * @property width - * @type Number - */ - this.width = 0; - - /** - * The height of the Texture in pixels. - * - * @property height - * @type Number - */ - this.height = 0; - - /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) - * - * @property crop - * @type Rectangle - */ - this.crop = new PIXI.Rectangle(0, 0, 1, 1); - - if (baseTexture.hasLoaded) - { - if (this.noFrame) frame = new PIXI.Rectangle(0, 0, baseTexture.width, baseTexture.height); - this.setFrame(frame); - } - 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(); - - this.valid = false; -}; - -/** - * Specifies the region of the baseTexture that this texture will use. - * - * @method setFrame - * @param frame {Rectangle} The frame of the texture to set it to - */ -PIXI.Texture.prototype.setFrame = function(frame) -{ - this.noFrame = false; - - this.frame = frame; - this.width = frame.width; - this.height = frame.height; - - this.crop.x = frame.x; - this.crop.y = frame.y; - this.crop.width = frame.width; - this.crop.height = frame.height; - - if (!this.trim && (frame.x + frame.width > this.baseTexture.width || frame.y + frame.height > this.baseTexture.height)) - { - throw new Error('Texture Error: frame does not fit inside the base Texture dimensions ' + this); - } - - this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; - - if (this.trim) - { - this.width = this.trim.width; - this.height = this.trim.height; - this.frame.width = this.trim.width; - this.frame.height = this.trim.height; - } - - if (this.valid) PIXI.Texture.frameUpdates.push(this); - -}; - -/** - * Updates the internal WebGL UV cache. - * - * @method _updateWebGLuvs - * @private - */ -PIXI.Texture.prototype._updateWebGLuvs = function() -{ - if(!this._uvs)this._uvs = new PIXI.TextureUvs(); - - var frame = this.crop; - var tw = this.baseTexture.width; - var th = this.baseTexture.height; - - this._uvs.x0 = frame.x / tw; - this._uvs.y0 = frame.y / th; - - this._uvs.x1 = (frame.x + frame.width) / tw; - this._uvs.y1 = frame.y / th; - - this._uvs.x2 = (frame.x + frame.width) / tw; - this._uvs.y2 = (frame.y + frame.height) / th; - - this._uvs.x3 = frame.x / tw; - this._uvs.y3 = (frame.y + frame.height) / th; - -}; - -/** - * Helper function that 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 - * @param scaleMode {Number} Should be one of the PIXI.scaleMode consts - * @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 - * @param scaleMode {Number} Should be one of the PIXI.scaleMode consts - * @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]; - delete PIXI.TextureCache[id]; - delete PIXI.BaseTextureCache[id]; - 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.y3 = 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 - * @param scaleMode {Number} Should be one of the PIXI.scaleMode consts - */ -PIXI.RenderTexture = function(width, height, renderer, scaleMode) -{ - 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); - - /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) - * - * @property crop - * @type Rectangle - */ - this.crop = new PIXI.Rectangle(0, 0, this.width, this.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.scaleMode = scaleMode || PIXI.scaleModes.DEFAULT; - - 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.scaleMode); - 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; - } - - this.valid = true; - PIXI.Texture.frameUpdates.push(this); - - -}; - -PIXI.RenderTexture.prototype = Object.create(PIXI.Texture.prototype); -PIXI.RenderTexture.prototype.constructor = PIXI.RenderTexture; - -/** - * Resize the RenderTexture. - * - * @method resize - * @param width {Number} The width to resize to. - * @param height {Number} The height to resize to. - * @param updateBase {Boolean} Should the baseTexture.width and height values be resized as well? - */ -PIXI.RenderTexture.prototype.resize = function(width, height, updateBase) -{ - if (width === this.width && height === this.height) - { - return; - } - - this.width = this.frame.width = this.crop.width = width; - this.height = this.frame.height = this.crop.height = height; - - if (updateBase) - { - this.baseTexture.width = this.width; - this.baseTexture.height = this.height; - } - - if (this.renderer.type === PIXI.WEBGL_RENDERER) - { - this.projection.x = this.width / 2; - this.projection.y = -this.height / 2; - } - - this.textureBuffer.resize(this.width, this.height); -}; - -/** - * Clears the RenderTexture. - * - * @method clear - */ -PIXI.RenderTexture.prototype.clear = function() -{ - if (this.renderer.type === PIXI.WEBGL_RENDERER) - { - this.renderer.gl.bindFramebuffer(this.renderer.gl.FRAMEBUFFER, this.textureBuffer.frameBuffer); - } - - this.textureBuffer.clear(); -}; - -/** - * This function will draw the display object to the texture. - * - * @method renderWebGL - * @param displayObject {DisplayObject} The display object to render this texture on - * @param 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, - 'webp': 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.content); - } - - 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 () { - - var scope = this; - - if(window.XDomainRequest && scope.crossorigin) - { - this.ajaxRequest = new window.XDomainRequest(); - - // XDomainRequest has a few querks. Occasionally it will abort requests - // A way to avoid this is to make sure ALL callbacks are set even if not used - // More info here: http://stackoverflow.com/questions/15786966/xdomainrequest-aborts-post-on-ie-9 - this.ajaxRequest.timeout = 3000; - - this.ajaxRequest.onerror = function () { - scope.onError(); - }; - - this.ajaxRequest.ontimeout = function () { - scope.onError(); - }; - - this.ajaxRequest.onprogress = function() {}; - - } - else if (window.XMLHttpRequest) - { - this.ajaxRequest = new window.XMLHttpRequest(); - } - else - { - this.ajaxRequest = new window.ActiveXObject('Microsoft.XMLHTTP'); - } - - - - this.ajaxRequest.onload = function(){ - - scope.onJSONLoaded(); - }; - - this.ajaxRequest.open('GET',this.url,true); - - this.ajaxRequest.send(); -}; - -/** - * Invoke when JSON file is loaded - * - * @method onJSONLoaded - * @private - */ -PIXI.JsonLoader.prototype.onJSONLoaded = function () { - - if(!this.ajaxRequest.responseText ) - { - this.onError(); - return; - } - - 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 - }); - - PIXI.TextureCache[i].crop = new PIXI.Rectangle(rect.x, rect.y, rect.w, rect.h); - - // Check to see if the sprite is trimmed - if (frameData[i].trimmed) - { - var actualSize = frameData[i].sourceSize; - var realSize = frameData[i].spriteSourceSize; - PIXI.TextureCache[i].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(); - } -}; - -/** - * 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-1){var c=["%c %c %c Pixi.js "+b.VERSION+" - "+a+" %c %c http://www.pixijs.com/ %c %c ♥%c♥%c♥ ","background: #ff66a5","background: #ff66a5","color: #ff66a5; background: #030307;","background: #ff66a5","background: #ffc3dc","background: #ff66a5","color: #ff2424; background: #fff","color: #ff2424; background: #fff","color: #ff2424; background: #fff"];console.log.apply(console,c)}else window.console&&console.log("Pixi.js "+b.VERSION+" - http://www.pixijs.com/");b.dontSayHello=!0}},b.Point=function(a,b){this.x=a||0,this.y=b||0},b.Point.prototype.clone=function(){return new b.Point(this.x,this.y)},b.Point.prototype.set=function(a,b){this.x=a||0,this.y=b||(0!==b?this.x:0)},b.Point.prototype.constructor=b.Point,b.Rectangle=function(a,b,c,d){this.x=a||0,this.y=b||0,this.width=c||0,this.height=d||0},b.Rectangle.prototype.clone=function(){return new b.Rectangle(this.x,this.y,this.width,this.height)},b.Rectangle.prototype.contains=function(a,b){if(this.width<=0||this.height<=0)return!1;var c=this.x;if(a>=c&&a<=c+this.width){var d=this.y;if(b>=d&&b<=d+this.height)return!0}return!1},b.Rectangle.prototype.constructor=b.Rectangle,b.EmptyRectangle=new b.Rectangle(0,0,0,0),b.Polygon=function(a){if(a instanceof Array||(a=Array.prototype.slice.call(arguments)),a[0]instanceof b.Point){for(var c=[],d=0,e=a.length;e>d;d++)c.push(a[d].x,a[d].y);a=c}this.closed=!0,this.points=a},b.Polygon.prototype.clone=function(){var a=this.points.slice();return new b.Polygon(a)},b.Polygon.prototype.contains=function(a,b){for(var c=!1,d=this.points.length/2,e=0,f=d-1;d>e;f=e++){var g=this.points[2*e],h=this.points[2*e+1],i=this.points[2*f],j=this.points[2*f+1],k=h>b!=j>b&&(i-g)*(b-h)/(j-h)+g>a;k&&(c=!c)}return c},b.Polygon.prototype.constructor=b.Polygon,b.Circle=function(a,b,c){this.x=a||0,this.y=b||0,this.radius=c||0},b.Circle.prototype.clone=function(){return new b.Circle(this.x,this.y,this.radius)},b.Circle.prototype.contains=function(a,b){if(this.radius<=0)return!1;var c=this.x-a,d=this.y-b,e=this.radius*this.radius;return c*=c,d*=d,e>=c+d},b.Circle.prototype.getBounds=function(){return new b.Rectangle(this.x-this.radius,this.y-this.radius,2*this.radius,2*this.radius)},b.Circle.prototype.constructor=b.Circle,b.Ellipse=function(a,b,c,d){this.x=a||0,this.y=b||0,this.width=c||0,this.height=d||0},b.Ellipse.prototype.clone=function(){return new b.Ellipse(this.x,this.y,this.width,this.height)},b.Ellipse.prototype.contains=function(a,b){if(this.width<=0||this.height<=0)return!1;var c=(a-this.x)/this.width,d=(b-this.y)/this.height;return c*=c,d*=d,1>=c+d},b.Ellipse.prototype.getBounds=function(){return new b.Rectangle(this.x-this.width,this.y-this.height,this.width,this.height)},b.Ellipse.prototype.constructor=b.Ellipse,b.RoundedRectangle=function(a,b,c,d,e){this.x=a||0,this.y=b||0,this.width=c||0,this.height=d||0,this.radius=e||20},b.RoundedRectangle.prototype.clone=function(){return new b.RoundedRectangle(this.x,this.y,this.width,this.height,this.radius)},b.RoundedRectangle.prototype.contains=function(a,b){if(this.width<=0||this.height<=0)return!1;var c=this.x;if(a>=c&&a<=c+this.width){var d=this.y;if(b>=d&&b<=d+this.height)return!0}return!1},b.RoundedRectangle.prototype.constructor=b.RoundedRectangle,b.Matrix=function(){this.a=1,this.b=0,this.c=0,this.d=1,this.tx=0,this.ty=0},b.Matrix.prototype.fromArray=function(a){this.a=a[0],this.b=a[1],this.c=a[3],this.d=a[4],this.tx=a[2],this.ty=a[5]},b.Matrix.prototype.toArray=function(a){this.array||(this.array=new b.Float32Array(9));var c=this.array;return a?(c[0]=this.a,c[1]=this.b,c[2]=0,c[3]=this.c,c[4]=this.d,c[5]=0,c[6]=this.tx,c[7]=this.ty,c[8]=1):(c[0]=this.a,c[1]=this.c,c[2]=this.tx,c[3]=this.b,c[4]=this.d,c[5]=this.ty,c[6]=0,c[7]=0,c[8]=1),c},b.Matrix.prototype.apply=function(a,c){return c=c||new b.Point,c.x=this.a*a.x+this.c*a.y+this.tx,c.y=this.b*a.x+this.d*a.y+this.ty,c},b.Matrix.prototype.applyInverse=function(a,c){c=c||new b.Point;var d=1/(this.a*this.d+this.c*-this.b);return c.x=this.d*d*a.x+-this.c*d*a.y+(this.ty*this.c-this.tx*this.d)*d,c.y=this.a*d*a.y+-this.b*d*a.x+(-this.ty*this.a+this.tx*this.b)*d,c},b.Matrix.prototype.translate=function(a,b){return this.tx+=a,this.ty+=b,this},b.Matrix.prototype.scale=function(a,b){return this.a*=a,this.d*=b,this.c*=a,this.b*=b,this.tx*=a,this.ty*=b,this},b.Matrix.prototype.rotate=function(a){var b=Math.cos(a),c=Math.sin(a),d=this.a,e=this.c,f=this.tx;return this.a=d*b-this.b*c,this.b=d*c+this.b*b,this.c=e*b-this.d*c,this.d=e*c+this.d*b,this.tx=f*b-this.ty*c,this.ty=f*c+this.ty*b,this},b.Matrix.prototype.append=function(a){var b=this.a,c=this.b,d=this.c,e=this.d;return this.a=a.a*b+a.b*d,this.b=a.a*c+a.b*e,this.c=a.c*b+a.d*d,this.d=a.c*c+a.d*e,this.tx=a.tx*b+a.ty*d+this.tx,this.ty=a.tx*c+a.ty*e+this.ty,this},b.Matrix.prototype.identity=function(){return this.a=1,this.b=0,this.c=0,this.d=1,this.tx=0,this.ty=0,this},b.identityMatrix=new b.Matrix,b.DisplayObject=function(){this.position=new b.Point,this.scale=new b.Point(1,1),this.pivot=new b.Point(0,0),this.rotation=0,this.alpha=1,this.visible=!0,this.hitArea=null,this.buttonMode=!1,this.renderable=!1,this.parent=null,this.stage=null,this.worldAlpha=1,this._interactive=!1,this.defaultCursor="pointer",this.worldTransform=new b.Matrix,this._sr=0,this._cr=1,this.filterArea=null,this._bounds=new b.Rectangle(0,0,1,1),this._currentBounds=null,this._mask=null,this._cacheAsBitmap=!1,this._cacheIsDirty=!1},b.DisplayObject.prototype.constructor=b.DisplayObject,Object.defineProperty(b.DisplayObject.prototype,"interactive",{get:function(){return this._interactive},set:function(a){this._interactive=a,this.stage&&(this.stage.dirty=!0)}}),Object.defineProperty(b.DisplayObject.prototype,"worldVisible",{get:function(){var a=this;do{if(!a.visible)return!1;a=a.parent}while(a);return!0}}),Object.defineProperty(b.DisplayObject.prototype,"mask",{get:function(){return this._mask},set:function(a){this._mask&&(this._mask.isMask=!1),this._mask=a,this._mask&&(this._mask.isMask=!0)}}),Object.defineProperty(b.DisplayObject.prototype,"filters",{get:function(){return this._filters},set:function(a){if(a){for(var b=[],c=0;c=0&&b<=this.children.length)return a.parent&&a.parent.removeChild(a),a.parent=this,this.children.splice(b,0,a),this.stage&&a.setStageReference(this.stage),a;throw new Error(a+"addChildAt: The index "+b+" supplied is out of bounds "+this.children.length)},b.DisplayObjectContainer.prototype.swapChildren=function(a,b){if(a!==b){var c=this.getChildIndex(a),d=this.getChildIndex(b);if(0>c||0>d)throw new Error("swapChildren: Both the supplied DisplayObjects must be a child of the caller.");this.children[c]=b,this.children[d]=a}},b.DisplayObjectContainer.prototype.getChildIndex=function(a){var b=this.children.indexOf(a);if(-1===b)throw new Error("The supplied DisplayObject must be a child of the caller");return b},b.DisplayObjectContainer.prototype.setChildIndex=function(a,b){if(0>b||b>=this.children.length)throw new Error("The supplied index is out of bounds");var c=this.getChildIndex(a);this.children.splice(c,1),this.children.splice(b,0,a)},b.DisplayObjectContainer.prototype.getChildAt=function(a){if(0>a||a>=this.children.length)throw new Error("getChildAt: Supplied index "+a+" does not exist in the child list, or the supplied DisplayObject must be a child of the caller");return this.children[a]},b.DisplayObjectContainer.prototype.removeChild=function(a){var b=this.children.indexOf(a);if(-1!==b)return this.removeChildAt(b)},b.DisplayObjectContainer.prototype.removeChildAt=function(a){var b=this.getChildAt(a);return this.stage&&b.removeStageReference(),b.parent=void 0,this.children.splice(a,1),b},b.DisplayObjectContainer.prototype.removeChildren=function(a,b){var c=a||0,d="number"==typeof b?b:this.children.length,e=d-c;if(e>0&&d>=e){for(var f=this.children.splice(c,e),g=0;ga;a++)this.children[a].updateTransform()},b.DisplayObjectContainer.prototype.displayObjectContainerUpdateTransform=b.DisplayObjectContainer.prototype.updateTransform,b.DisplayObjectContainer.prototype.getBounds=function(){if(0===this.children.length)return b.EmptyRectangle;for(var a,c,d,e=1/0,f=1/0,g=-1/0,h=-1/0,i=!1,j=0,k=this.children.length;k>j;j++){var l=this.children[j];l.visible&&(i=!0,a=this.children[j].getBounds(),e=ec?g:c,h=h>d?h:d)}if(!i)return b.EmptyRectangle;var m=this._bounds;return m.x=e,m.y=f,m.width=g-e,m.height=h-f,m},b.DisplayObjectContainer.prototype.getLocalBounds=function(){var a=this.worldTransform;this.worldTransform=b.identityMatrix;for(var c=0,d=this.children.length;d>c;c++)this.children[c].updateTransform();var e=this.getBounds();return this.worldTransform=a,e},b.DisplayObjectContainer.prototype.setStageReference=function(a){this.stage=a,this._interactive&&(this.stage.dirty=!0);for(var b=0,c=this.children.length;c>b;b++){var d=this.children[b];d.setStageReference(a)}},b.DisplayObjectContainer.prototype.removeStageReference=function(){for(var a=0,b=this.children.length;b>a;a++){var c=this.children[a];c.removeStageReference()}this._interactive&&(this.stage.dirty=!0),this.stage=null},b.DisplayObjectContainer.prototype._renderWebGL=function(a){if(this.visible&&!(this.alpha<=0)){if(this._cacheAsBitmap)return this._renderCachedSprite(a),void 0;var b,c;if(this._mask||this._filters){for(this._filters&&(a.spriteBatch.flush(),a.filterManager.pushFilter(this._filterBlock)),this._mask&&(a.spriteBatch.stop(),a.maskManager.pushMask(this.mask,a),a.spriteBatch.start()),b=0,c=this.children.length;c>b;b++)this.children[b]._renderWebGL(a);a.spriteBatch.stop(),this._mask&&a.maskManager.popMask(this._mask,a),this._filters&&a.filterManager.popFilter(),a.spriteBatch.start()}else for(b=0,c=this.children.length;c>b;b++)this.children[b]._renderWebGL(a)}},b.DisplayObjectContainer.prototype._renderCanvas=function(a){if(this.visible!==!1&&0!==this.alpha){if(this._cacheAsBitmap)return this._renderCachedSprite(a),void 0;this._mask&&a.maskManager.pushMask(this._mask,a);for(var b=0,c=this.children.length;c>b;b++){var d=this.children[b];d._renderCanvas(a)}this._mask&&a.maskManager.popMask(a)}},b.Sprite=function(a){b.DisplayObjectContainer.call(this),this.anchor=new b.Point,this.texture=a,this._width=0,this._height=0,this.tint=16777215,this.blendMode=b.blendModes.NORMAL,this.shader=null,a.baseTexture.hasLoaded?this.onTextureUpdate():this.texture.on("update",this.onTextureUpdate.bind(this)),this.renderable=!0},b.Sprite.prototype=Object.create(b.DisplayObjectContainer.prototype),b.Sprite.prototype.constructor=b.Sprite,Object.defineProperty(b.Sprite.prototype,"width",{get:function(){return this.scale.x*this.texture.frame.width},set:function(a){this.scale.x=a/this.texture.frame.width,this._width=a}}),Object.defineProperty(b.Sprite.prototype,"height",{get:function(){return this.scale.y*this.texture.frame.height},set:function(a){this.scale.y=a/this.texture.frame.height,this._height=a}}),b.Sprite.prototype.setTexture=function(a){this.texture=a,this.cachedTint=16777215},b.Sprite.prototype.onTextureUpdate=function(){this._width&&(this.scale.x=this._width/this.texture.frame.width),this._height&&(this.scale.y=this._height/this.texture.frame.height)},b.Sprite.prototype.getBounds=function(a){var b=this.texture.frame.width,c=this.texture.frame.height,d=b*(1-this.anchor.x),e=b*-this.anchor.x,f=c*(1-this.anchor.y),g=c*-this.anchor.y,h=a||this.worldTransform,i=h.a,j=h.b,k=h.c,l=h.d,m=h.tx,n=h.ty,o=i*e+k*g+m,p=l*g+j*e+n,q=i*d+k*g+m,r=l*g+j*d+n,s=i*d+k*f+m,t=l*f+j*d+n,u=i*e+k*f+m,v=l*f+j*e+n,w=-1/0,x=-1/0,y=1/0,z=1/0;y=y>o?o:y,y=y>q?q:y,y=y>s?s:y,y=y>u?u:y,z=z>p?p:z,z=z>r?r:z,z=z>t?t:z,z=z>v?v:z,w=o>w?o:w,w=q>w?q:w,w=s>w?s:w,w=u>w?u:w,x=p>x?p:x,x=r>x?r:x,x=t>x?t:x,x=v>x?v:x;var A=this._bounds;return A.x=y,A.width=w-y,A.y=z,A.height=x-z,this._currentBounds=A,A},b.Sprite.prototype._renderWebGL=function(a){if(this.visible&&!(this.alpha<=0)){var b,c;if(this._mask||this._filters){var d=a.spriteBatch;for(this._filters&&(d.flush(),a.filterManager.pushFilter(this._filterBlock)),this._mask&&(d.stop(),a.maskManager.pushMask(this.mask,a),d.start()),d.render(this),b=0,c=this.children.length;c>b;b++)this.children[b]._renderWebGL(a);d.stop(),this._mask&&a.maskManager.popMask(this._mask,a),this._filters&&a.filterManager.popFilter(),d.start()}else for(a.spriteBatch.render(this),b=0,c=this.children.length;c>b;b++)this.children[b]._renderWebGL(a)}},b.Sprite.prototype._renderCanvas=function(a){if(!(this.visible===!1||0===this.alpha||this.texture.crop.width<=0||this.texture.crop.height<=0)){if(this.blendMode!==a.currentBlendMode&&(a.currentBlendMode=this.blendMode,a.context.globalCompositeOperation=b.blendModesCanvas[a.currentBlendMode]),this._mask&&a.maskManager.pushMask(this._mask,a),this.texture.valid){var c=this.texture.baseTexture.resolution/a.resolution;a.context.globalAlpha=this.worldAlpha,a.roundPixels?a.context.setTransform(this.worldTransform.a,this.worldTransform.b,this.worldTransform.c,this.worldTransform.d,this.worldTransform.tx*a.resolution|0,this.worldTransform.ty*a.resolution|0):a.context.setTransform(this.worldTransform.a,this.worldTransform.b,this.worldTransform.c,this.worldTransform.d,this.worldTransform.tx*a.resolution,this.worldTransform.ty*a.resolution),a.smoothProperty&&a.scaleMode!==this.texture.baseTexture.scaleMode&&(a.scaleMode=this.texture.baseTexture.scaleMode,a.context[a.smoothProperty]=a.scaleMode===b.scaleModes.LINEAR);var d=this.texture.trim?this.texture.trim.x-this.anchor.x*this.texture.trim.width:this.anchor.x*-this.texture.frame.width,e=this.texture.trim?this.texture.trim.y-this.anchor.y*this.texture.trim.height:this.anchor.y*-this.texture.frame.height;16777215!==this.tint?(this.cachedTint!==this.tint&&(this.cachedTint=this.tint,this.tintedTexture=b.CanvasTinter.getTintedTexture(this,this.tint)),a.context.drawImage(this.tintedTexture,0,0,this.texture.crop.width,this.texture.crop.height,d/c,e/c,this.texture.crop.width/c,this.texture.crop.height/c)):a.context.drawImage(this.texture.baseTexture.source,this.texture.crop.x,this.texture.crop.y,this.texture.crop.width,this.texture.crop.height,d/c,e/c,this.texture.crop.width/c,this.texture.crop.height/c)}for(var f=0,g=this.children.length;g>f;f++)this.children[f]._renderCanvas(a);this._mask&&a.maskManager.popMask(a)}},b.Sprite.fromFrame=function(a){var c=b.TextureCache[a];if(!c)throw new Error('The frameId "'+a+'" does not exist in the texture cache'+this);return new b.Sprite(c)},b.Sprite.fromImage=function(a,c,d){var e=b.Texture.fromImage(a,c,d);return new b.Sprite(e)},b.SpriteBatch=function(a){b.DisplayObjectContainer.call(this),this.textureThing=a,this.ready=!1},b.SpriteBatch.prototype=Object.create(b.DisplayObjectContainer.prototype),b.SpriteBatch.prototype.constructor=b.SpriteBatch,b.SpriteBatch.prototype.initWebGL=function(a){this.fastSpriteBatch=new b.WebGLFastSpriteBatch(a),this.ready=!0},b.SpriteBatch.prototype.updateTransform=function(){b.DisplayObject.prototype.updateTransform.call(this)},b.SpriteBatch.prototype._renderWebGL=function(a){!this.visible||this.alpha<=0||!this.children.length||(this.ready||this.initWebGL(a.gl),a.spriteBatch.stop(),a.shaderManager.setShader(a.shaderManager.fastShader),this.fastSpriteBatch.begin(this,a),this.fastSpriteBatch.render(this),a.spriteBatch.start())},b.SpriteBatch.prototype._renderCanvas=function(a){if(this.visible&&!(this.alpha<=0)&&this.children.length){var c=a.context;c.globalAlpha=this.worldAlpha,b.DisplayObject.prototype.updateTransform.call(this);for(var d=this.worldTransform,e=!0,f=0;f=this.textures.length&&(this.gotoAndStop(this.textures.length-1),this.onComplete&&this.onComplete())}},b.MovieClip.fromFrames=function(a){for(var c=[],d=0;di;i++){for(j=0;m>j;j+=4)if(255!==k[n+j]){o=!0;break}if(o)break;n+=m}for(c.ascent=g-i,n=l-m,o=!1,i=h;i>g;i--){for(j=0;m>j;j+=4)if(255!==k[n+j]){o=!0;break}if(o)break;n-=m}c.descent=i-g,c.fontSize=c.ascent+c.descent,b.Text.fontPropertiesCache[a]=c}return c},b.Text.prototype.wordWrap=function(a){for(var b="",c=a.split("\n"),d=0;de?(g>0&&(b+="\n"),b+=f[g],e=this.style.wordWrapWidth-h):(e-=i,b+=" "+f[g])}d=2?parseInt(c[c.length-2],10):b.BitmapText.fonts[this.fontName].size,this.dirty=!0,this.tint=a.tint},b.BitmapText.prototype.updateText=function(){for(var a=b.BitmapText.fonts[this.fontName],c=new b.Point,d=null,e=[],f=0,g=[],h=0,i=this.fontSize/a.size,j=0;j=j;j++){var n=0;"right"===this.style.align?n=f-g[j]:"center"===this.style.align&&(n=(f-g[j])/2),m.push(n)}var o=this.children.length,p=e.length,q=this.tint||16777215;for(j=0;p>j;j++){var r=o>j?this.children[j]:this._pool.pop();r?r.setTexture(e[j].texture):r=new b.Sprite(e[j].texture),r.position.x=(e[j].position.x+m[e[j].line])*i,r.position.y=e[j].position.y*i,r.scale.x=r.scale.y=i,r.tint=q,r.parent||this.addChild(r)}for(;this.children.length>p;){var s=this.getChildAt(this.children.length-1);this._pool.push(s),this.removeChild(s)}this.textWidth=f*i,this.textHeight=(c.y+a.lineHeight)*i},b.BitmapText.prototype.updateTransform=function(){this.dirty&&(this.updateText(),this.dirty=!1),b.DisplayObjectContainer.prototype.updateTransform.call(this)},b.BitmapText.fonts={},b.InteractionData=function(){this.global=new b.Point,this.target=null,this.originalEvent=null},b.InteractionData.prototype.getLocalPosition=function(a,c){var d=a.worldTransform,e=this.global,f=d.a,g=d.c,h=d.tx,i=d.b,j=d.d,k=d.ty,l=1/(f*j+g*-i);return c=c||new b.Point,c.x=j*l*e.x+-g*l*e.y+(k*g-h*j)*l,c.y=f*l*e.y+-i*l*e.x+(-k*f+h*i)*l,c},b.InteractionData.prototype.constructor=b.InteractionData,b.InteractionManager=function(a){this.stage=a,this.mouse=new b.InteractionData,this.touches={},this.tempPoint=new b.Point,this.mouseoverEnabled=!0,this.pool=[],this.interactiveItems=[],this.interactionDOMElement=null,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,this.currentCursorStyle="inherit",this.mouseOut=!1,this.resolution=1},b.InteractionManager.prototype.constructor=b.InteractionManager,b.InteractionManager.prototype.collectInteractiveSprite=function(a,b){for(var c=a.children,d=c.length,e=d-1;e>=0;e--){var f=c[e];f._interactive?(b.interactiveChildren=!0,this.interactiveItems.push(f),f.children.length>0&&this.collectInteractiveSprite(f,f)):(f.__iParent=null,f.children.length>0&&this.collectInteractiveSprite(f,b)) +}},b.InteractionManager.prototype.setTarget=function(a){this.target=a,this.resolution=a.resolution,null===this.interactionDOMElement&&this.setTargetDomElement(a.view)},b.InteractionManager.prototype.setTargetDomElement=function(a){this.removeEvents(),window.navigator.msPointerEnabled&&(a.style["-ms-content-zooming"]="none",a.style["-ms-touch-action"]="none"),this.interactionDOMElement=a,a.addEventListener("mousemove",this.onMouseMove,!0),a.addEventListener("mousedown",this.onMouseDown,!0),a.addEventListener("mouseout",this.onMouseOut,!0),a.addEventListener("touchstart",this.onTouchStart,!0),a.addEventListener("touchend",this.onTouchEnd,!0),a.addEventListener("touchmove",this.onTouchMove,!0),window.addEventListener("mouseup",this.onMouseUp,!0)},b.InteractionManager.prototype.removeEvents=function(){this.interactionDOMElement&&(this.interactionDOMElement.style["-ms-content-zooming"]="",this.interactionDOMElement.style["-ms-touch-action"]="",this.interactionDOMElement.removeEventListener("mousemove",this.onMouseMove,!0),this.interactionDOMElement.removeEventListener("mousedown",this.onMouseDown,!0),this.interactionDOMElement.removeEventListener("mouseout",this.onMouseOut,!0),this.interactionDOMElement.removeEventListener("touchstart",this.onTouchStart,!0),this.interactionDOMElement.removeEventListener("touchend",this.onTouchEnd,!0),this.interactionDOMElement.removeEventListener("touchmove",this.onTouchMove,!0),this.interactionDOMElement=null,window.removeEventListener("mouseup",this.onMouseUp,!0))},b.InteractionManager.prototype.update=function(){if(this.target){var a=Date.now(),c=a-this.last;if(c=c*b.INTERACTION_FREQUENCY/1e3,!(1>c)){this.last=a;var d=0;this.dirty&&this.rebuildInteractiveGraph();var e=this.interactiveItems.length,f="inherit",g=!1;for(d=0;e>d;d++){var h=this.interactiveItems[d];h.__hit=this.hitTest(h,this.mouse),this.mouse.target=h,h.__hit&&!g?(h.buttonMode&&(f=h.defaultCursor),h.interactiveChildren||(g=!0),h.__isOver||(h.mouseover&&h.mouseover(this.mouse),h.__isOver=!0)):h.__isOver&&(h.mouseout&&h.mouseout(this.mouse),h.__isOver=!1)}this.currentCursorStyle!==f&&(this.currentCursorStyle=f,this.interactionDOMElement.style.cursor=f)}}},b.InteractionManager.prototype.rebuildInteractiveGraph=function(){this.dirty=!1;for(var a=this.interactiveItems.length,b=0;a>b;b++)this.interactiveItems[b].interactiveChildren=!1;this.interactiveItems=[],this.stage.interactive&&this.interactiveItems.push(this.stage),this.collectInteractiveSprite(this.stage,this.stage)},b.InteractionManager.prototype.onMouseMove=function(a){this.dirty&&this.rebuildInteractiveGraph(),this.mouse.originalEvent=a;var b=this.interactionDOMElement.getBoundingClientRect();this.mouse.global.x=(a.clientX-b.left)*(this.target.width/b.width)/this.resolution,this.mouse.global.y=(a.clientY-b.top)*(this.target.height/b.height)/this.resolution;for(var c=this.interactiveItems.length,d=0;c>d;d++){var e=this.interactiveItems[d];e.mousemove&&e.mousemove(this.mouse)}},b.InteractionManager.prototype.onMouseDown=function(a){this.dirty&&this.rebuildInteractiveGraph(),this.mouse.originalEvent=a,b.AUTO_PREVENT_DEFAULT&&this.mouse.originalEvent.preventDefault();for(var c=this.interactiveItems.length,d=this.mouse.originalEvent,e=2===d.button||3===d.which,f=e?"rightdown":"mousedown",g=e?"rightclick":"click",h=e?"__rightIsDown":"__mouseIsDown",i=e?"__isRightDown":"__isDown",j=0;c>j;j++){var k=this.interactiveItems[j];if((k[f]||k[g])&&(k[h]=!0,k.__hit=this.hitTest(k,this.mouse),k.__hit&&(k[f]&&k[f](this.mouse),k[i]=!0,!k.interactiveChildren)))break}},b.InteractionManager.prototype.onMouseOut=function(a){this.dirty&&this.rebuildInteractiveGraph(),this.mouse.originalEvent=a;var b=this.interactiveItems.length;this.interactionDOMElement.style.cursor="inherit";for(var c=0;b>c;c++){var d=this.interactiveItems[c];d.__isOver&&(this.mouse.target=d,d.mouseout&&d.mouseout(this.mouse),d.__isOver=!1)}this.mouseOut=!0,this.mouse.global.x=-1e4,this.mouse.global.y=-1e4},b.InteractionManager.prototype.onMouseUp=function(a){this.dirty&&this.rebuildInteractiveGraph(),this.mouse.originalEvent=a;for(var b=this.interactiveItems.length,c=!1,d=this.mouse.originalEvent,e=2===d.button||3===d.which,f=e?"rightup":"mouseup",g=e?"rightclick":"click",h=e?"rightupoutside":"mouseupoutside",i=e?"__isRightDown":"__isDown",j=0;b>j;j++){var k=this.interactiveItems[j];(k[g]||k[f]||k[h])&&(k.__hit=this.hitTest(k,this.mouse),k.__hit&&!c?(k[f]&&k[f](this.mouse),k[i]&&k[g]&&k[g](this.mouse),k.interactiveChildren||(c=!0)):k[i]&&k[h]&&k[h](this.mouse),k[i]=!1)}},b.InteractionManager.prototype.hitTest=function(a,c){var d=c.global;if(!a.worldVisible)return!1;var e,f=a.worldTransform,g=f.a,h=f.b,i=f.c,j=f.tx,k=f.d,l=f.ty,m=1/(g*k+i*-h),n=k*m*d.x+-i*m*d.y+(l*i-j*k)*m,o=g*m*d.y+-h*m*d.x+(-l*g+j*h)*m;if(c.target=a,a.hitArea&&a.hitArea.contains)return a.hitArea.contains(n,o)?(c.target=a,!0):!1;if(a instanceof b.Sprite){var p,q=a.texture.frame.width,r=a.texture.frame.height,s=-q*a.anchor.x;if(n>s&&s+q>n&&(p=-r*a.anchor.y,o>p&&p+r>o))return c.target=a,!0}else if(a instanceof b.Graphics){var t=a.graphicsData;for(e=0;ee;e++){var w=a.children[e],x=this.hitTest(w,c);if(x)return c.target=a,!0}return!1},b.InteractionManager.prototype.onTouchMove=function(a){this.dirty&&this.rebuildInteractiveGraph();var b,c=this.interactionDOMElement.getBoundingClientRect(),d=a.changedTouches,e=0;for(e=0;ei;i++){var j=this.interactiveItems[i];if((j.touchstart||j.tap)&&(j.__hit=this.hitTest(j,g),j.__hit&&(j.touchstart&&j.touchstart(g),j.__isDown=!0,j.__touchData=j.__touchData||{},j.__touchData[f.identifier]=g,!j.interactiveChildren)))break}}},b.InteractionManager.prototype.onTouchEnd=function(a){this.dirty&&this.rebuildInteractiveGraph();for(var b=this.interactionDOMElement.getBoundingClientRect(),c=a.changedTouches,d=0;di;i++){var j=this.interactiveItems[i];j.__touchData&&j.__touchData[e.identifier]&&(j.__hit=this.hitTest(j,j.__touchData[e.identifier]),f.originalEvent=a,(j.touchend||j.tap)&&(j.__hit&&!g?(j.touchend&&j.touchend(f),j.__isDown&&j.tap&&j.tap(f),j.interactiveChildren||(g=!0)):j.__isDown&&j.touchendoutside&&j.touchendoutside(f),j.__isDown=!1),j.__touchData[e.identifier]=null)}this.pool.push(f),this.touches[e.identifier]=null}},b.Stage=function(a){b.DisplayObjectContainer.call(this),this.worldTransform=new b.Matrix,this.interactive=!0,this.interactionManager=new b.InteractionManager(this),this.dirty=!0,this.stage=this,this.stage.hitArea=new b.Rectangle(0,0,1e5,1e5),this.setBackgroundColor(a)},b.Stage.prototype=Object.create(b.DisplayObjectContainer.prototype),b.Stage.prototype.constructor=b.Stage,b.Stage.prototype.setInteractionDelegate=function(a){this.interactionManager.setTargetDomElement(a)},b.Stage.prototype.updateTransform=function(){this.worldAlpha=1;for(var a=0,b=this.children.length;b>a;a++)this.children[a].updateTransform();this.dirty&&(this.dirty=!1,this.interactionManager.dirty=!0),this.interactive&&this.interactionManager.update()},b.Stage.prototype.setBackgroundColor=function(a){this.backgroundColor=a||0,this.backgroundColorSplit=b.hex2rgb(this.backgroundColor);var c=this.backgroundColor.toString(16);c="000000".substr(0,6-c.length)+c,this.backgroundColorString="#"+c},b.Stage.prototype.getMousePosition=function(){return this.interactionManager.mouse.global},function(a){for(var b=0,c=["ms","moz","webkit","o"],d=0;d>16&255)/255,(a>>8&255)/255,(255&a)/255]},b.rgb2hex=function(a){return(255*a[0]<<16)+(255*a[1]<<8)+255*a[2]},"function"!=typeof Function.prototype.bind&&(Function.prototype.bind=function(){return function(a){function b(){for(var d=arguments.length,f=new Array(d);d--;)f[d]=arguments[d];return f=e.concat(f),c.apply(this instanceof b?this:a,f)}var c=this,d=arguments.length-1,e=[];if(d>0)for(e.length=d;d--;)e[d]=arguments[d+1];if("function"!=typeof c)throw new TypeError;return b.prototype=function f(a){return a&&(f.prototype=a),this instanceof f?void 0:new f}(c.prototype),b}}()),b.AjaxRequest=function(){var a=["Msxml2.XMLHTTP.6.0","Msxml2.XMLHTTP.3.0","Microsoft.XMLHTTP"];if(!window.ActiveXObject)return window.XMLHttpRequest?new window.XMLHttpRequest:!1;for(var b=0;b0&&0===(a&a-1))return a;for(var b=1;a>b;)b<<=1;return b},b.EventTarget={call:function(a){a&&(a=a.prototype||a,b.EventTarget.mixin(a))},mixin:function(a){a.listeners=function(a){return this._listeners=this._listeners||{},this._listeners[a]?this._listeners[a].slice():[]},a.emit=a.dispatchEvent=function(a,c){if(this._listeners=this._listeners||{},"object"==typeof a&&(c=a,a=a.type),c&&c.__isEventObject===!0||(c=new b.Event(this,a,c)),this._listeners&&this._listeners[a]){var d,e=this._listeners[a].slice(0),f=e.length,g=e[0];for(d=0;f>d;g=e[++d])if(g.call(this,c),c.stoppedImmediate)return this;if(c.stopped)return this}return this.parent&&this.parent.emit&&this.parent.emit.call(this.parent,a,c),this},a.on=a.addEventListener=function(a,b){return this._listeners=this._listeners||{},(this._listeners[a]=this._listeners[a]||[]).push(b),this},a.once=function(a,b){function c(){b.apply(d.off(a,c),arguments)}this._listeners=this._listeners||{};var d=this;return c._originalHandler=b,this.on(a,c)},a.off=a.removeEventListener=function(a,b){if(this._listeners=this._listeners||{},!this._listeners[a])return this;for(var c=this._listeners[a],d=b?c.length:0;d-->0;)(c[d]===b||c[d]._originalHandler===b)&&c.splice(d,1);return 0===c.length&&delete this._listeners[a],this},a.removeAllListeners=function(a){return this._listeners=this._listeners||{},this._listeners[a]?(delete this._listeners[a],this):this}}},b.Event=function(a,b,c){this.__isEventObject=!0,this.stopped=!1,this.stoppedImmediate=!1,this.target=a,this.type=b,this.data=c,this.content=c,this.timeStamp=Date.now()},b.Event.prototype.stopPropagation=function(){this.stopped=!0},b.Event.prototype.stopImmediatePropagation=function(){this.stoppedImmediate=!0},b.autoDetectRenderer=function(a,c,d){a||(a=800),c||(c=600);var e=function(){try{var a=document.createElement("canvas");return!!window.WebGLRenderingContext&&(a.getContext("webgl")||a.getContext("experimental-webgl"))}catch(b){return!1}}();return e?new b.WebGLRenderer(a,c,d):new b.CanvasRenderer(a,c,d)},b.autoDetectRecommendedRenderer=function(a,c,d){a||(a=800),c||(c=600);var e=function(){try{var a=document.createElement("canvas");return!!window.WebGLRenderingContext&&(a.getContext("webgl")||a.getContext("experimental-webgl"))}catch(b){return!1}}(),f=/Android/i.test(navigator.userAgent);return e&&!f?new b.WebGLRenderer(a,c,d):new b.CanvasRenderer(a,c,d)},b.PolyK={},b.PolyK.Triangulate=function(a){var c=!0,d=a.length>>1;if(3>d)return[];for(var e=[],f=[],g=0;d>g;g++)f.push(g);g=0;for(var h=d;h>3;){var i=f[(g+0)%h],j=f[(g+1)%h],k=f[(g+2)%h],l=a[2*i],m=a[2*i+1],n=a[2*j],o=a[2*j+1],p=a[2*k],q=a[2*k+1],r=!1;if(b.PolyK._convex(l,m,n,o,p,q,c)){r=!0;for(var s=0;h>s;s++){var t=f[s];if(t!==i&&t!==j&&t!==k&&b.PolyK._PointInTriangle(a[2*t],a[2*t+1],l,m,n,o,p,q)){r=!1;break}}}if(r)e.push(i,j,k),f.splice((g+1)%h,1),h--,g=0;else if(g++>3*h){if(!c)return null;for(e=[],f=[],g=0;d>g;g++)f.push(g);g=0,h=d,c=!1}}return e.push(f[0],f[1],f[2]),e},b.PolyK._PointInTriangle=function(a,b,c,d,e,f,g,h){var i=g-c,j=h-d,k=e-c,l=f-d,m=a-c,n=b-d,o=i*i+j*j,p=i*k+j*l,q=i*m+j*n,r=k*k+l*l,s=k*m+l*n,t=1/(o*r-p*p),u=(r*q-p*s)*t,v=(o*s-p*q)*t;return u>=0&&v>=0&&1>u+v},b.PolyK._convex=function(a,b,c,d,e,f,g){return(b-d)*(e-c)+(c-a)*(f-d)>=0===g},b.initDefaultShaders=function(){},b.CompileVertexShader=function(a,c){return b._CompileShader(a,c,a.VERTEX_SHADER)},b.CompileFragmentShader=function(a,c){return b._CompileShader(a,c,a.FRAGMENT_SHADER)},b._CompileShader=function(a,b,c){var d=b.join("\n"),e=a.createShader(c);return a.shaderSource(e,d),a.compileShader(e),a.getShaderParameter(e,a.COMPILE_STATUS)?e:(window.console.log(a.getShaderInfoLog(e)),null)},b.compileProgram=function(a,c,d){var e=b.CompileFragmentShader(a,d),f=b.CompileVertexShader(a,c),g=a.createProgram();return a.attachShader(g,f),a.attachShader(g,e),a.linkProgram(g),a.getProgramParameter(g,a.LINK_STATUS)||window.console.log("Could not initialise shaders"),g},b.PixiShader=function(a){this._UID=b._UID++,this.gl=a,this.program=null,this.fragmentSrc=["precision lowp float;","varying vec2 vTextureCoord;","varying vec4 vColor;","uniform sampler2D uSampler;","void main(void) {"," gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;","}"],this.textureCount=0,this.firstRun=!0,this.dirty=!0,this.attributes=[],this.init()},b.PixiShader.prototype.constructor=b.PixiShader,b.PixiShader.prototype.init=function(){var a=this.gl,c=b.compileProgram(a,this.vertexSrc||b.PixiShader.defaultVertexSrc,this.fragmentSrc);a.useProgram(c),this.uSampler=a.getUniformLocation(c,"uSampler"),this.projectionVector=a.getUniformLocation(c,"projectionVector"),this.offsetVector=a.getUniformLocation(c,"offsetVector"),this.dimensions=a.getUniformLocation(c,"dimensions"),this.aVertexPosition=a.getAttribLocation(c,"aVertexPosition"),this.aTextureCoord=a.getAttribLocation(c,"aTextureCoord"),this.colorAttribute=a.getAttribLocation(c,"aColor"),-1===this.colorAttribute&&(this.colorAttribute=2),this.attributes=[this.aVertexPosition,this.aTextureCoord,this.colorAttribute];for(var d in this.uniforms)this.uniforms[d].uniformLocation=a.getUniformLocation(c,d);this.initUniforms(),this.program=c},b.PixiShader.prototype.initUniforms=function(){this.textureCount=1;var a,b=this.gl;for(var c in this.uniforms){a=this.uniforms[c];var d=a.type;"sampler2D"===d?(a._init=!1,null!==a.value&&this.initSampler2D(a)):"mat2"===d||"mat3"===d||"mat4"===d?(a.glMatrix=!0,a.glValueLength=1,"mat2"===d?a.glFunc=b.uniformMatrix2fv:"mat3"===d?a.glFunc=b.uniformMatrix3fv:"mat4"===d&&(a.glFunc=b.uniformMatrix4fv)):(a.glFunc=b["uniform"+d],a.glValueLength="2f"===d||"2i"===d?2:"3f"===d||"3i"===d?3:"4f"===d||"4i"===d?4:1)}},b.PixiShader.prototype.initSampler2D=function(a){if(a.value&&a.value.baseTexture&&a.value.baseTexture.hasLoaded){var b=this.gl;if(b.activeTexture(b["TEXTURE"+this.textureCount]),b.bindTexture(b.TEXTURE_2D,a.value.baseTexture._glTextures[b.id]),a.textureData){var c=a.textureData,d=c.magFilter?c.magFilter:b.LINEAR,e=c.minFilter?c.minFilter:b.LINEAR,f=c.wrapS?c.wrapS:b.CLAMP_TO_EDGE,g=c.wrapT?c.wrapT:b.CLAMP_TO_EDGE,h=c.luminance?b.LUMINANCE:b.RGBA;if(c.repeat&&(f=b.REPEAT,g=b.REPEAT),b.pixelStorei(b.UNPACK_FLIP_Y_WEBGL,!!c.flipY),c.width){var i=c.width?c.width:512,j=c.height?c.height:2,k=c.border?c.border:0;b.texImage2D(b.TEXTURE_2D,0,h,i,j,k,h,b.UNSIGNED_BYTE,null)}else b.texImage2D(b.TEXTURE_2D,0,h,b.RGBA,b.UNSIGNED_BYTE,a.value.baseTexture.source);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_MAG_FILTER,d),b.texParameteri(b.TEXTURE_2D,b.TEXTURE_MIN_FILTER,e),b.texParameteri(b.TEXTURE_2D,b.TEXTURE_WRAP_S,f),b.texParameteri(b.TEXTURE_2D,b.TEXTURE_WRAP_T,g)}b.uniform1i(a.uniformLocation,this.textureCount),a._init=!0,this.textureCount++}},b.PixiShader.prototype.syncUniforms=function(){this.textureCount=1;var a,c=this.gl;for(var d in this.uniforms)a=this.uniforms[d],1===a.glValueLength?a.glMatrix===!0?a.glFunc.call(c,a.uniformLocation,a.transpose,a.value):a.glFunc.call(c,a.uniformLocation,a.value):2===a.glValueLength?a.glFunc.call(c,a.uniformLocation,a.value.x,a.value.y):3===a.glValueLength?a.glFunc.call(c,a.uniformLocation,a.value.x,a.value.y,a.value.z):4===a.glValueLength?a.glFunc.call(c,a.uniformLocation,a.value.x,a.value.y,a.value.z,a.value.w):"sampler2D"===a.type&&(a._init?(c.activeTexture(c["TEXTURE"+this.textureCount]),a.value.baseTexture._dirty[c.id]?b.instances[c.id].updateTexture(a.value.baseTexture):c.bindTexture(c.TEXTURE_2D,a.value.baseTexture._glTextures[c.id]),c.uniform1i(a.uniformLocation,this.textureCount),this.textureCount++):this.initSampler2D(a))},b.PixiShader.prototype.destroy=function(){this.gl.deleteProgram(this.program),this.uniforms=null,this.gl=null,this.attributes=null},b.PixiShader.defaultVertexSrc=["attribute vec2 aVertexPosition;","attribute vec2 aTextureCoord;","attribute vec4 aColor;","uniform vec2 projectionVector;","uniform vec2 offsetVector;","varying vec2 vTextureCoord;","varying vec4 vColor;","const vec2 center = vec2(-1.0, 1.0);","void main(void) {"," gl_Position = vec4( ((aVertexPosition + offsetVector) / projectionVector) + center , 0.0, 1.0);"," vTextureCoord = aTextureCoord;"," 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);","}"],b.PixiFastShader=function(a){this._UID=b._UID++,this.gl=a,this.program=null,this.fragmentSrc=["precision lowp float;","varying vec2 vTextureCoord;","varying float vColor;","uniform sampler2D uSampler;","void main(void) {"," gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;","}"],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;"," vColor = aColor;","}"],this.textureCount=0,this.init()},b.PixiFastShader.prototype.constructor=b.PixiFastShader,b.PixiFastShader.prototype.init=function(){var a=this.gl,c=b.compileProgram(a,this.vertexSrc,this.fragmentSrc);a.useProgram(c),this.uSampler=a.getUniformLocation(c,"uSampler"),this.projectionVector=a.getUniformLocation(c,"projectionVector"),this.offsetVector=a.getUniformLocation(c,"offsetVector"),this.dimensions=a.getUniformLocation(c,"dimensions"),this.uMatrix=a.getUniformLocation(c,"uMatrix"),this.aVertexPosition=a.getAttribLocation(c,"aVertexPosition"),this.aPositionCoord=a.getAttribLocation(c,"aPositionCoord"),this.aScale=a.getAttribLocation(c,"aScale"),this.aRotation=a.getAttribLocation(c,"aRotation"),this.aTextureCoord=a.getAttribLocation(c,"aTextureCoord"),this.colorAttribute=a.getAttribLocation(c,"aColor"),-1===this.colorAttribute&&(this.colorAttribute=2),this.attributes=[this.aVertexPosition,this.aPositionCoord,this.aScale,this.aRotation,this.aTextureCoord,this.colorAttribute],this.program=c},b.PixiFastShader.prototype.destroy=function(){this.gl.deleteProgram(this.program),this.uniforms=null,this.gl=null,this.attributes=null},b.StripShader=function(a){this._UID=b._UID++,this.gl=a,this.program=null,this.fragmentSrc=["precision mediump float;","varying vec2 vTextureCoord;","uniform float alpha;","uniform sampler2D uSampler;","void main(void) {"," gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * alpha;","}"],this.vertexSrc=["attribute vec2 aVertexPosition;","attribute vec2 aTextureCoord;","uniform mat3 translationMatrix;","uniform vec2 projectionVector;","uniform vec2 offsetVector;","varying vec2 vTextureCoord;","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;","}"],this.init()},b.StripShader.prototype.constructor=b.StripShader,b.StripShader.prototype.init=function(){var a=this.gl,c=b.compileProgram(a,this.vertexSrc,this.fragmentSrc);a.useProgram(c),this.uSampler=a.getUniformLocation(c,"uSampler"),this.projectionVector=a.getUniformLocation(c,"projectionVector"),this.offsetVector=a.getUniformLocation(c,"offsetVector"),this.colorAttribute=a.getAttribLocation(c,"aColor"),this.aVertexPosition=a.getAttribLocation(c,"aVertexPosition"),this.aTextureCoord=a.getAttribLocation(c,"aTextureCoord"),this.attributes=[this.aVertexPosition,this.aTextureCoord],this.translationMatrix=a.getUniformLocation(c,"translationMatrix"),this.alpha=a.getUniformLocation(c,"alpha"),this.program=c},b.StripShader.prototype.destroy=function(){this.gl.deleteProgram(this.program),this.uniforms=null,this.gl=null,this.attribute=null},b.PrimitiveShader=function(a){this._UID=b._UID++,this.gl=a,this.program=null,this.fragmentSrc=["precision mediump float;","varying vec4 vColor;","void main(void) {"," gl_FragColor = vColor;","}"],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()},b.PrimitiveShader.prototype.constructor=b.PrimitiveShader,b.PrimitiveShader.prototype.init=function(){var a=this.gl,c=b.compileProgram(a,this.vertexSrc,this.fragmentSrc);a.useProgram(c),this.projectionVector=a.getUniformLocation(c,"projectionVector"),this.offsetVector=a.getUniformLocation(c,"offsetVector"),this.tintColor=a.getUniformLocation(c,"tint"),this.aVertexPosition=a.getAttribLocation(c,"aVertexPosition"),this.colorAttribute=a.getAttribLocation(c,"aColor"),this.attributes=[this.aVertexPosition,this.colorAttribute],this.translationMatrix=a.getUniformLocation(c,"translationMatrix"),this.alpha=a.getUniformLocation(c,"alpha"),this.program=c},b.PrimitiveShader.prototype.destroy=function(){this.gl.deleteProgram(this.program),this.uniforms=null,this.gl=null,this.attributes=null},b.ComplexPrimitiveShader=function(a){this._UID=b._UID++,this.gl=a,this.program=null,this.fragmentSrc=["precision mediump float;","varying vec4 vColor;","void main(void) {"," gl_FragColor = vColor;","}"],this.vertexSrc=["attribute vec2 aVertexPosition;","uniform mat3 translationMatrix;","uniform vec2 projectionVector;","uniform vec2 offsetVector;","uniform vec3 tint;","uniform float alpha;","uniform vec3 color;","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 = vec4(color * alpha * tint, alpha);","}"],this.init()},b.ComplexPrimitiveShader.prototype.constructor=b.ComplexPrimitiveShader,b.ComplexPrimitiveShader.prototype.init=function(){var a=this.gl,c=b.compileProgram(a,this.vertexSrc,this.fragmentSrc);a.useProgram(c),this.projectionVector=a.getUniformLocation(c,"projectionVector"),this.offsetVector=a.getUniformLocation(c,"offsetVector"),this.tintColor=a.getUniformLocation(c,"tint"),this.color=a.getUniformLocation(c,"color"),this.aVertexPosition=a.getAttribLocation(c,"aVertexPosition"),this.attributes=[this.aVertexPosition,this.colorAttribute],this.translationMatrix=a.getUniformLocation(c,"translationMatrix"),this.alpha=a.getUniformLocation(c,"alpha"),this.program=c},b.ComplexPrimitiveShader.prototype.destroy=function(){this.gl.deleteProgram(this.program),this.uniforms=null,this.gl=null,this.attribute=null},b.WebGLGraphics=function(){},b.WebGLGraphics.renderGraphics=function(a,c){var d,e=c.gl,f=c.projection,g=c.offset,h=c.shaderManager.primitiveShader;a.dirty&&b.WebGLGraphics.updateGraphics(a,e);for(var i=a._webGL[e.id],j=0;j=6)if(h.points.length<12){g=b.WebGLGraphics.switchMode(d,0);var i=b.WebGLGraphics.buildPoly(h,g);i||(g=b.WebGLGraphics.switchMode(d,1),b.WebGLGraphics.buildComplexPoly(h,g))}else g=b.WebGLGraphics.switchMode(d,1),b.WebGLGraphics.buildComplexPoly(h,g);h.lineWidth>0&&(g=b.WebGLGraphics.switchMode(d,0),b.WebGLGraphics.buildLine(h,g))}else g=b.WebGLGraphics.switchMode(d,0),h.type===b.Graphics.RECT?b.WebGLGraphics.buildRectangle(h,g):h.type===b.Graphics.CIRC||h.type===b.Graphics.ELIP?b.WebGLGraphics.buildCircle(h,g):h.type===b.Graphics.RREC&&b.WebGLGraphics.buildRoundedRectangle(h,g);d.lastIndex++}for(e=0;e=q;q++)p=q/n,h=g(a,c,p),i=g(b,d,p),j=g(c,e,p),k=g(d,f,p),l=g(h,j,p),m=g(i,k,p),o.push(l,m);return o},b.WebGLGraphics.buildCircle=function(a,c){var d,e,f=a.shape,g=f.x,h=f.y;a.type===b.Graphics.CIRC?(d=f.radius,e=f.radius):(d=f.width,e=f.height);var i=40,j=2*Math.PI/i,k=0;if(a.fill){var l=b.hex2rgb(a.fillColor),m=a.fillAlpha,n=l[0]*m,o=l[1]*m,p=l[2]*m,q=c.points,r=c.indices,s=q.length/6;for(r.push(s),k=0;i+1>k;k++)q.push(g,h,n,o,p,m),q.push(g+Math.sin(j*k)*d,h+Math.cos(j*k)*e,n,o,p,m),r.push(s++,s++);r.push(s-1)}if(a.lineWidth){var t=a.points;for(a.points=[],k=0;i+1>k;k++)a.points.push(g+Math.sin(j*k)*d,h+Math.cos(j*k)*e);b.WebGLGraphics.buildLine(a,c),a.points=t}},b.WebGLGraphics.buildLine=function(a,c){var d=0,e=a.points;if(0!==e.length){if(a.lineWidth%2)for(d=0;dd;d++)l=e[2*(d-1)],m=e[2*(d-1)+1],n=e[2*d],o=e[2*d+1],p=e[2*(d+1)],q=e[2*(d+1)+1],r=-(m-o),s=l-n,F=Math.sqrt(r*r+s*s),r/=F,s/=F,r*=L,s*=L,t=-(o-q),u=n-p,F=Math.sqrt(t*t+u*u),t/=F,u/=F,t*=L,u*=L,x=-s+m-(-s+o),y=-r+n-(-r+l),z=(-r+l)*(-s+o)-(-r+n)*(-s+m),A=-u+q-(-u+o),B=-t+n-(-t+p),C=(-t+p)*(-u+o)-(-t+n)*(-u+q),D=x*B-A*y,Math.abs(D)<.1?(D+=10.1,G.push(n-r,o-s,O,P,Q,N),G.push(n+r,o+s,O,P,Q,N)):(j=(y*C-B*z)/D,k=(A*z-x*C)/D,E=(j-n)*(j-n)+(k-o)+(k-o),E>19600?(v=r-t,w=s-u,F=Math.sqrt(v*v+w*w),v/=F,w/=F,v*=L,w*=L,G.push(n-v,o-w),G.push(O,P,Q,N),G.push(n+v,o+w),G.push(O,P,Q,N),G.push(n-v,o-w),G.push(O,P,Q,N),J++):(G.push(j,k),G.push(O,P,Q,N),G.push(n-(j-n),o-(k-o)),G.push(O,P,Q,N)));for(l=e[2*(I-2)],m=e[2*(I-2)+1],n=e[2*(I-1)],o=e[2*(I-1)+1],r=-(m-o),s=l-n,F=Math.sqrt(r*r+s*s),r/=F,s/=F,r*=L,s*=L,G.push(n-r,o-s),G.push(O,P,Q,N),G.push(n+r,o+s),G.push(O,P,Q,N),H.push(K),d=0;J>d;d++)H.push(K++); +H.push(K-1)}},b.WebGLGraphics.buildComplexPoly=function(a,c){var d=a.points.slice();if(!(d.length<6)){var e=c.indices;c.points=d,c.alpha=a.fillAlpha,c.color=b.hex2rgb(a.fillColor);for(var f,g,h=1/0,i=-1/0,j=1/0,k=-1/0,l=0;lf?f:h,i=f>i?f:i,j=j>g?g:j,k=g>k?g:k;d.push(h,j,i,j,i,k,h,k);var m=d.length/2;for(l=0;m>l;l++)e.push(l)}},b.WebGLGraphics.buildPoly=function(a,c){var d=a.points;if(!(d.length<6)){var e=c.points,f=c.indices,g=d.length/2,h=b.hex2rgb(a.fillColor),i=a.fillAlpha,j=h[0]*i,k=h[1]*i,l=h[2]*i,m=b.PolyK.Triangulate(d);if(!m)return!1;var n=e.length/6,o=0;for(o=0;oo;o++)e.push(d[2*o],d[2*o+1],j,k,l,i);return!0}},b.WebGLGraphics.graphicsDataPool=[],b.WebGLGraphicsData=function(a){this.gl=a,this.color=[0,0,0],this.points=[],this.indices=[],this.lastIndex=0,this.buffer=a.createBuffer(),this.indexBuffer=a.createBuffer(),this.mode=1,this.alpha=1,this.dirty=!0},b.WebGLGraphicsData.prototype.reset=function(){this.points=[],this.indices=[],this.lastIndex=0},b.WebGLGraphicsData.prototype.upload=function(){var a=this.gl;this.glPoints=new b.Float32Array(this.points),a.bindBuffer(a.ARRAY_BUFFER,this.buffer),a.bufferData(a.ARRAY_BUFFER,this.glPoints,a.STATIC_DRAW),this.glIndicies=new b.Uint16Array(this.indices),a.bindBuffer(a.ELEMENT_ARRAY_BUFFER,this.indexBuffer),a.bufferData(a.ELEMENT_ARRAY_BUFFER,this.glIndicies,a.STATIC_DRAW),this.dirty=!1},b.glContexts=[],b.instances=[],b.WebGLRenderer=function(a,c,d){if(d)for(var e in b.defaultRenderOptions)"undefined"==typeof d[e]&&(d[e]=b.defaultRenderOptions[e]);else d=b.defaultRenderOptions;b.defaultRenderer||(b.sayHello("webGL"),b.defaultRenderer=this),this.type=b.WEBGL_RENDERER,this.resolution=d.resolution,this.transparent=d.transparent,this.autoResize=d.autoResize||!1,this.preserveDrawingBuffer=d.preserveDrawingBuffer,this.clearBeforeRender=d.clearBeforeRender,this.width=a||800,this.height=c||600,this.view=d.view||document.createElement("canvas"),this.contextLostBound=this.handleContextLost.bind(this),this.contextRestoredBound=this.handleContextRestored.bind(this),this.view.addEventListener("webglcontextlost",this.contextLostBound,!1),this.view.addEventListener("webglcontextrestored",this.contextRestoredBound,!1),this._contextOptions={alpha:this.transparent,antialias:d.antialias,premultipliedAlpha:this.transparent&&"notMultiplied"!==this.transparent,stencil:!0,preserveDrawingBuffer:d.preserveDrawingBuffer},this.projection=new b.Point,this.offset=new b.Point(0,0),this.shaderManager=new b.WebGLShaderManager,this.spriteBatch=new b.WebGLSpriteBatch,this.maskManager=new b.WebGLMaskManager,this.filterManager=new b.WebGLFilterManager,this.stencilManager=new b.WebGLStencilManager,this.blendModeManager=new b.WebGLBlendModeManager,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.blendModeManager=this.blendModeManager,this.renderSession.spriteBatch=this.spriteBatch,this.renderSession.stencilManager=this.stencilManager,this.renderSession.renderer=this,this.renderSession.resolution=this.resolution,this.initContext(),this.mapBlendModes()},b.WebGLRenderer.prototype.constructor=b.WebGLRenderer,b.WebGLRenderer.prototype.initContext=function(){var a=this.view.getContext("webgl",this._contextOptions)||this.view.getContext("experimental-webgl",this._contextOptions);if(this.gl=a,!a)throw new Error("This browser does not support webGL. Try using the canvas renderer");this.glContextId=a.id=b.WebGLRenderer.glContextId++,b.glContexts[this.glContextId]=a,b.instances[this.glContextId]=this,a.disable(a.DEPTH_TEST),a.disable(a.CULL_FACE),a.enable(a.BLEND),this.shaderManager.setContext(a),this.spriteBatch.setContext(a),this.maskManager.setContext(a),this.filterManager.setContext(a),this.blendModeManager.setContext(a),this.stencilManager.setContext(a),this.renderSession.gl=this.gl,this.resize(this.width,this.height)},b.WebGLRenderer.prototype.render=function(a){if(!this.contextLost){this.__stage!==a&&(a.interactive&&a.interactionManager.removeEvents(),this.__stage=a),a.updateTransform();var b=this.gl;a._interactive?a._interactiveEventsAdded||(a._interactiveEventsAdded=!0,a.interactionManager.setTarget(this)):a._interactiveEventsAdded&&(a._interactiveEventsAdded=!1,a.interactionManager.setTarget(this)),b.viewport(0,0,this.width,this.height),b.bindFramebuffer(b.FRAMEBUFFER,null),this.clearBeforeRender&&(this.transparent?b.clearColor(0,0,0,0):b.clearColor(a.backgroundColorSplit[0],a.backgroundColorSplit[1],a.backgroundColorSplit[2],1),b.clear(b.COLOR_BUFFER_BIT)),this.renderDisplayObject(a,this.projection)}},b.WebGLRenderer.prototype.renderDisplayObject=function(a,c,d){this.renderSession.blendModeManager.setBlendMode(b.blendModes.NORMAL),this.renderSession.drawCount=0,this.renderSession.projection=c,this.renderSession.offset=this.offset,this.spriteBatch.begin(this.renderSession),this.filterManager.begin(this.renderSession,d),a._renderWebGL(this.renderSession),this.spriteBatch.end()},b.WebGLRenderer.prototype.resize=function(a,b){this.width=a*this.resolution,this.height=b*this.resolution,this.view.width=this.width,this.view.height=this.height,this.autoResize&&(this.view.style.width=this.width/this.resolution+"px",this.view.style.height=this.height/this.resolution+"px"),this.gl.viewport(0,0,this.width,this.height),this.projection.x=this.width/2/this.resolution,this.projection.y=-this.height/2/this.resolution},b.WebGLRenderer.prototype.updateTexture=function(a){if(a.hasLoaded){var c=this.gl;return a._glTextures[c.id]||(a._glTextures[c.id]=c.createTexture()),c.bindTexture(c.TEXTURE_2D,a._glTextures[c.id]),c.pixelStorei(c.UNPACK_PREMULTIPLY_ALPHA_WEBGL,a.premultipliedAlpha),c.texImage2D(c.TEXTURE_2D,0,c.RGBA,c.RGBA,c.UNSIGNED_BYTE,a.source),c.texParameteri(c.TEXTURE_2D,c.TEXTURE_MAG_FILTER,a.scaleMode===b.scaleModes.LINEAR?c.LINEAR:c.NEAREST),c.texParameteri(c.TEXTURE_2D,c.TEXTURE_MIN_FILTER,a.scaleMode===b.scaleModes.LINEAR?c.LINEAR:c.NEAREST),a._powerOf2?(c.texParameteri(c.TEXTURE_2D,c.TEXTURE_WRAP_S,c.REPEAT),c.texParameteri(c.TEXTURE_2D,c.TEXTURE_WRAP_T,c.REPEAT)):(c.texParameteri(c.TEXTURE_2D,c.TEXTURE_WRAP_S,c.CLAMP_TO_EDGE),c.texParameteri(c.TEXTURE_2D,c.TEXTURE_WRAP_T,c.CLAMP_TO_EDGE)),a._dirty[c.id]=!1,a._glTextures[c.id]}},b.WebGLRenderer.prototype.handleContextLost=function(a){a.preventDefault(),this.contextLost=!0},b.WebGLRenderer.prototype.handleContextRestored=function(){this.initContext();for(var a in b.TextureCache){var c=b.TextureCache[a].baseTexture;c._glTextures=[]}this.contextLost=!1},b.WebGLRenderer.prototype.destroy=function(){this.view.removeEventListener("webglcontextlost",this.contextLostBound),this.view.removeEventListener("webglcontextrestored",this.contextRestoredBound),b.glContexts[this.glContextId]=null,this.projection=null,this.offset=null,this.shaderManager.destroy(),this.spriteBatch.destroy(),this.maskManager.destroy(),this.filterManager.destroy(),this.shaderManager=null,this.spriteBatch=null,this.maskManager=null,this.filterManager=null,this.gl=null,this.renderSession=null},b.WebGLRenderer.prototype.mapBlendModes=function(){var a=this.gl;b.blendModesWebGL||(b.blendModesWebGL=[],b.blendModesWebGL[b.blendModes.NORMAL]=[a.ONE,a.ONE_MINUS_SRC_ALPHA],b.blendModesWebGL[b.blendModes.ADD]=[a.SRC_ALPHA,a.DST_ALPHA],b.blendModesWebGL[b.blendModes.MULTIPLY]=[a.DST_COLOR,a.ONE_MINUS_SRC_ALPHA],b.blendModesWebGL[b.blendModes.SCREEN]=[a.SRC_ALPHA,a.ONE],b.blendModesWebGL[b.blendModes.OVERLAY]=[a.ONE,a.ONE_MINUS_SRC_ALPHA],b.blendModesWebGL[b.blendModes.DARKEN]=[a.ONE,a.ONE_MINUS_SRC_ALPHA],b.blendModesWebGL[b.blendModes.LIGHTEN]=[a.ONE,a.ONE_MINUS_SRC_ALPHA],b.blendModesWebGL[b.blendModes.COLOR_DODGE]=[a.ONE,a.ONE_MINUS_SRC_ALPHA],b.blendModesWebGL[b.blendModes.COLOR_BURN]=[a.ONE,a.ONE_MINUS_SRC_ALPHA],b.blendModesWebGL[b.blendModes.HARD_LIGHT]=[a.ONE,a.ONE_MINUS_SRC_ALPHA],b.blendModesWebGL[b.blendModes.SOFT_LIGHT]=[a.ONE,a.ONE_MINUS_SRC_ALPHA],b.blendModesWebGL[b.blendModes.DIFFERENCE]=[a.ONE,a.ONE_MINUS_SRC_ALPHA],b.blendModesWebGL[b.blendModes.EXCLUSION]=[a.ONE,a.ONE_MINUS_SRC_ALPHA],b.blendModesWebGL[b.blendModes.HUE]=[a.ONE,a.ONE_MINUS_SRC_ALPHA],b.blendModesWebGL[b.blendModes.SATURATION]=[a.ONE,a.ONE_MINUS_SRC_ALPHA],b.blendModesWebGL[b.blendModes.COLOR]=[a.ONE,a.ONE_MINUS_SRC_ALPHA],b.blendModesWebGL[b.blendModes.LUMINOSITY]=[a.ONE,a.ONE_MINUS_SRC_ALPHA])},b.WebGLRenderer.glContextId=0,b.WebGLBlendModeManager=function(){this.currentBlendMode=99999},b.WebGLBlendModeManager.prototype.constructor=b.WebGLBlendModeManager,b.WebGLBlendModeManager.prototype.setContext=function(a){this.gl=a},b.WebGLBlendModeManager.prototype.setBlendMode=function(a){if(this.currentBlendMode===a)return!1;this.currentBlendMode=a;var c=b.blendModesWebGL[this.currentBlendMode];return this.gl.blendFunc(c[0],c[1]),!0},b.WebGLBlendModeManager.prototype.destroy=function(){this.gl=null},b.WebGLMaskManager=function(){},b.WebGLMaskManager.prototype.constructor=b.WebGLMaskManager,b.WebGLMaskManager.prototype.setContext=function(a){this.gl=a},b.WebGLMaskManager.prototype.pushMask=function(a,c){var d=c.gl;a.dirty&&b.WebGLGraphics.updateGraphics(a,d),a._webGL[d.id].data.length&&c.stencilManager.pushStencil(a,a._webGL[d.id].data[0],c)},b.WebGLMaskManager.prototype.popMask=function(a,b){var c=this.gl;b.stencilManager.popStencil(a,a._webGL[c.id].data[0],b)},b.WebGLMaskManager.prototype.destroy=function(){this.gl=null},b.WebGLStencilManager=function(){this.stencilStack=[],this.reverse=!0,this.count=0},b.WebGLStencilManager.prototype.setContext=function(a){this.gl=a},b.WebGLStencilManager.prototype.pushStencil=function(a,b,c){var d=this.gl;this.bindGraphics(a,b,c),0===this.stencilStack.length&&(d.enable(d.STENCIL_TEST),d.clear(d.STENCIL_BUFFER_BIT),this.reverse=!0,this.count=0),this.stencilStack.push(b);var e=this.count;d.colorMask(!1,!1,!1,!1),d.stencilFunc(d.ALWAYS,0,255),d.stencilOp(d.KEEP,d.KEEP,d.INVERT),1===b.mode?(d.drawElements(d.TRIANGLE_FAN,b.indices.length-4,d.UNSIGNED_SHORT,0),this.reverse?(d.stencilFunc(d.EQUAL,255-e,255),d.stencilOp(d.KEEP,d.KEEP,d.DECR)):(d.stencilFunc(d.EQUAL,e,255),d.stencilOp(d.KEEP,d.KEEP,d.INCR)),d.drawElements(d.TRIANGLE_FAN,4,d.UNSIGNED_SHORT,2*(b.indices.length-4)),this.reverse?d.stencilFunc(d.EQUAL,255-(e+1),255):d.stencilFunc(d.EQUAL,e+1,255),this.reverse=!this.reverse):(this.reverse?(d.stencilFunc(d.EQUAL,e,255),d.stencilOp(d.KEEP,d.KEEP,d.INCR)):(d.stencilFunc(d.EQUAL,255-e,255),d.stencilOp(d.KEEP,d.KEEP,d.DECR)),d.drawElements(d.TRIANGLE_STRIP,b.indices.length,d.UNSIGNED_SHORT,0),this.reverse?d.stencilFunc(d.EQUAL,e+1,255):d.stencilFunc(d.EQUAL,255-(e+1),255)),d.colorMask(!0,!0,!0,!0),d.stencilOp(d.KEEP,d.KEEP,d.KEEP),this.count++},b.WebGLStencilManager.prototype.bindGraphics=function(a,c,d){this._currentGraphics=a;var e,f=this.gl,g=d.projection,h=d.offset;1===c.mode?(e=d.shaderManager.complexPrimitiveShader,d.shaderManager.setShader(e),f.uniformMatrix3fv(e.translationMatrix,!1,a.worldTransform.toArray(!0)),f.uniform2f(e.projectionVector,g.x,-g.y),f.uniform2f(e.offsetVector,-h.x,-h.y),f.uniform3fv(e.tintColor,b.hex2rgb(a.tint)),f.uniform3fv(e.color,c.color),f.uniform1f(e.alpha,a.worldAlpha*c.alpha),f.bindBuffer(f.ARRAY_BUFFER,c.buffer),f.vertexAttribPointer(e.aVertexPosition,2,f.FLOAT,!1,8,0),f.bindBuffer(f.ELEMENT_ARRAY_BUFFER,c.indexBuffer)):(e=d.shaderManager.primitiveShader,d.shaderManager.setShader(e),f.uniformMatrix3fv(e.translationMatrix,!1,a.worldTransform.toArray(!0)),f.uniform2f(e.projectionVector,g.x,-g.y),f.uniform2f(e.offsetVector,-h.x,-h.y),f.uniform3fv(e.tintColor,b.hex2rgb(a.tint)),f.uniform1f(e.alpha,a.worldAlpha),f.bindBuffer(f.ARRAY_BUFFER,c.buffer),f.vertexAttribPointer(e.aVertexPosition,2,f.FLOAT,!1,24,0),f.vertexAttribPointer(e.colorAttribute,4,f.FLOAT,!1,24,8),f.bindBuffer(f.ELEMENT_ARRAY_BUFFER,c.indexBuffer))},b.WebGLStencilManager.prototype.popStencil=function(a,b,c){var d=this.gl;if(this.stencilStack.pop(),this.count--,0===this.stencilStack.length)d.disable(d.STENCIL_TEST);else{var e=this.count;this.bindGraphics(a,b,c),d.colorMask(!1,!1,!1,!1),1===b.mode?(this.reverse=!this.reverse,this.reverse?(d.stencilFunc(d.EQUAL,255-(e+1),255),d.stencilOp(d.KEEP,d.KEEP,d.INCR)):(d.stencilFunc(d.EQUAL,e+1,255),d.stencilOp(d.KEEP,d.KEEP,d.DECR)),d.drawElements(d.TRIANGLE_FAN,4,d.UNSIGNED_SHORT,2*(b.indices.length-4)),d.stencilFunc(d.ALWAYS,0,255),d.stencilOp(d.KEEP,d.KEEP,d.INVERT),d.drawElements(d.TRIANGLE_FAN,b.indices.length-4,d.UNSIGNED_SHORT,0),this.reverse?d.stencilFunc(d.EQUAL,e,255):d.stencilFunc(d.EQUAL,255-e,255)):(this.reverse?(d.stencilFunc(d.EQUAL,e+1,255),d.stencilOp(d.KEEP,d.KEEP,d.DECR)):(d.stencilFunc(d.EQUAL,255-(e+1),255),d.stencilOp(d.KEEP,d.KEEP,d.INCR)),d.drawElements(d.TRIANGLE_STRIP,b.indices.length,d.UNSIGNED_SHORT,0),this.reverse?d.stencilFunc(d.EQUAL,e,255):d.stencilFunc(d.EQUAL,255-e,255)),d.colorMask(!0,!0,!0,!0),d.stencilOp(d.KEEP,d.KEEP,d.KEEP)}},b.WebGLStencilManager.prototype.destroy=function(){this.stencilStack=null,this.gl=null},b.WebGLShaderManager=function(){this.maxAttibs=10,this.attribState=[],this.tempAttribState=[];for(var a=0;ad;d+=6,e+=4)this.indices[d+0]=e+0,this.indices[d+1]=e+1,this.indices[d+2]=e+2,this.indices[d+3]=e+0,this.indices[d+4]=e+2,this.indices[d+5]=e+3;this.drawing=!1,this.currentBatchSize=0,this.currentBaseTexture=null,this.dirty=!0,this.textures=[],this.blendModes=[],this.shaders=[],this.sprites=[],this.defaultShader=new b.AbstractFilter(["precision lowp float;","varying vec2 vTextureCoord;","varying vec4 vColor;","uniform sampler2D uSampler;","void main(void) {"," gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;","}"])},b.WebGLSpriteBatch.prototype.setContext=function(a){this.gl=a,this.vertexBuffer=a.createBuffer(),this.indexBuffer=a.createBuffer(),a.bindBuffer(a.ELEMENT_ARRAY_BUFFER,this.indexBuffer),a.bufferData(a.ELEMENT_ARRAY_BUFFER,this.indices,a.STATIC_DRAW),a.bindBuffer(a.ARRAY_BUFFER,this.vertexBuffer),a.bufferData(a.ARRAY_BUFFER,this.vertices,a.DYNAMIC_DRAW),this.currentBlendMode=99999;var c=new b.PixiShader(a);c.fragmentSrc=this.defaultShader.fragmentSrc,c.uniforms={},c.init(),this.defaultShader.shaders[a.id]=c},b.WebGLSpriteBatch.prototype.begin=function(a){this.renderSession=a,this.shader=this.renderSession.shaderManager.defaultShader,this.start()},b.WebGLSpriteBatch.prototype.end=function(){this.flush()},b.WebGLSpriteBatch.prototype.render=function(a){var b=a.texture;this.currentBatchSize>=this.size&&(this.flush(),this.currentBaseTexture=b.baseTexture);var c=b._uvs;if(c){var d,e,f,g,h=a.worldAlpha,i=a.tint,j=this.vertices,k=a.anchor.x,l=a.anchor.y;if(b.trim){var m=b.trim;e=m.x-k*m.width,d=e+b.crop.width,g=m.y-l*m.height,f=g+b.crop.height}else d=b.frame.width*(1-k),e=b.frame.width*-k,f=b.frame.height*(1-l),g=b.frame.height*-l;var n=4*this.currentBatchSize*this.vertSize,o=b.baseTexture.resolution,p=a.worldTransform,q=p.a/o,r=p.b/o,s=p.c/o,t=p.d/o,u=p.tx,v=p.ty;j[n++]=q*e+s*g+u,j[n++]=t*g+r*e+v,j[n++]=c.x0,j[n++]=c.y0,j[n++]=h,j[n++]=i,j[n++]=q*d+s*g+u,j[n++]=t*g+r*d+v,j[n++]=c.x1,j[n++]=c.y1,j[n++]=h,j[n++]=i,j[n++]=q*d+s*f+u,j[n++]=t*f+r*d+v,j[n++]=c.x2,j[n++]=c.y2,j[n++]=h,j[n++]=i,j[n++]=q*e+s*f+u,j[n++]=t*f+r*e+v,j[n++]=c.x3,j[n++]=c.y3,j[n++]=h,j[n++]=i,this.sprites[this.currentBatchSize++]=a}},b.WebGLSpriteBatch.prototype.renderTilingSprite=function(a){var c=a.tilingTexture;this.currentBatchSize>=this.size&&(this.flush(),this.currentBaseTexture=c.baseTexture),a._uvs||(a._uvs=new b.TextureUvs);var d=a._uvs;a.tilePosition.x%=c.baseTexture.width*a.tileScaleOffset.x,a.tilePosition.y%=c.baseTexture.height*a.tileScaleOffset.y;var e=a.tilePosition.x/(c.baseTexture.width*a.tileScaleOffset.x),f=a.tilePosition.y/(c.baseTexture.height*a.tileScaleOffset.y),g=a.width/c.baseTexture.width/(a.tileScale.x*a.tileScaleOffset.x),h=a.height/c.baseTexture.height/(a.tileScale.y*a.tileScaleOffset.y);d.x0=0-e,d.y0=0-f,d.x1=1*g-e,d.y1=0-f,d.x2=1*g-e,d.y2=1*h-f,d.x3=0-e,d.y3=1*h-f;var i=a.worldAlpha,j=a.tint,k=this.vertices,l=a.width,m=a.height,n=a.anchor.x,o=a.anchor.y,p=l*(1-n),q=l*-n,r=m*(1-o),s=m*-o,t=4*this.currentBatchSize*this.vertSize,u=c.baseTexture.resolution,v=a.worldTransform,w=v.a/u,x=v.b/u,y=v.c/u,z=v.d/u,A=v.tx,B=v.ty;k[t++]=w*q+y*s+A,k[t++]=z*s+x*q+B,k[t++]=d.x0,k[t++]=d.y0,k[t++]=i,k[t++]=j,k[t++]=w*p+y*s+A,k[t++]=z*s+x*p+B,k[t++]=d.x1,k[t++]=d.y1,k[t++]=i,k[t++]=j,k[t++]=w*p+y*r+A,k[t++]=z*r+x*p+B,k[t++]=d.x2,k[t++]=d.y2,k[t++]=i,k[t++]=j,k[t++]=w*q+y*r+A,k[t++]=z*r+x*q+B,k[t++]=d.x3,k[t++]=d.y3,k[t++]=i,k[t++]=j,this.sprites[this.currentBatchSize++]=a},b.WebGLSpriteBatch.prototype.flush=function(){if(0!==this.currentBatchSize){var a,c=this.gl;if(this.dirty){this.dirty=!1,c.activeTexture(c.TEXTURE0),c.bindBuffer(c.ARRAY_BUFFER,this.vertexBuffer),c.bindBuffer(c.ELEMENT_ARRAY_BUFFER,this.indexBuffer),a=this.defaultShader.shaders[c.id];var d=4*this.vertSize;c.vertexAttribPointer(a.aVertexPosition,2,c.FLOAT,!1,d,0),c.vertexAttribPointer(a.aTextureCoord,2,c.FLOAT,!1,d,8),c.vertexAttribPointer(a.colorAttribute,2,c.FLOAT,!1,d,16)}if(this.currentBatchSize>.5*this.size)c.bufferSubData(c.ARRAY_BUFFER,0,this.vertices);else{var e=this.vertices.subarray(0,4*this.currentBatchSize*this.vertSize);c.bufferSubData(c.ARRAY_BUFFER,0,e)}for(var f,g,h,i,j=0,k=0,l=null,m=this.renderSession.blendModeManager.currentBlendMode,n=null,o=!1,p=!1,q=0,r=this.currentBatchSize;r>q;q++){if(i=this.sprites[q],f=i.texture.baseTexture,g=i.blendMode,h=i.shader||this.defaultShader,o=m!==g,p=n!==h,(l!==f||o||p)&&(this.renderBatch(l,j,k),k=q,j=0,l=f,o&&(m=g,this.renderSession.blendModeManager.setBlendMode(m)),p)){n=h,a=n.shaders[c.id],a||(a=new b.PixiShader(c),a.fragmentSrc=n.fragmentSrc,a.uniforms=n.uniforms,a.init(),n.shaders[c.id]=a),this.renderSession.shaderManager.setShader(a),a.dirty&&a.syncUniforms();var s=this.renderSession.projection;c.uniform2f(a.projectionVector,s.x,s.y);var t=this.renderSession.offset;c.uniform2f(a.offsetVector,t.x,t.y)}j++}this.renderBatch(l,j,k),this.currentBatchSize=0}},b.WebGLSpriteBatch.prototype.renderBatch=function(a,b,c){if(0!==b){var d=this.gl;a._dirty[d.id]?this.renderSession.renderer.updateTexture(a):d.bindTexture(d.TEXTURE_2D,a._glTextures[d.id]),d.drawElements(d.TRIANGLES,6*b,d.UNSIGNED_SHORT,6*c*2),this.renderSession.drawCount++}},b.WebGLSpriteBatch.prototype.stop=function(){this.flush(),this.dirty=!0},b.WebGLSpriteBatch.prototype.start=function(){this.dirty=!0},b.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},b.WebGLFastSpriteBatch=function(a){this.vertSize=10,this.maxSize=6e3,this.size=this.maxSize;var c=4*this.size*this.vertSize,d=6*this.maxSize;this.vertices=new b.Float32Array(c),this.indices=new b.Uint16Array(d),this.vertexBuffer=null,this.indexBuffer=null,this.lastIndexCount=0;for(var e=0,f=0;d>e;e+=6,f+=4)this.indices[e+0]=f+0,this.indices[e+1]=f+1,this.indices[e+2]=f+2,this.indices[e+3]=f+0,this.indices[e+4]=f+2,this.indices[e+5]=f+3;this.drawing=!1,this.currentBatchSize=0,this.currentBaseTexture=null,this.currentBlendMode=0,this.renderSession=null,this.shader=null,this.matrix=null,this.setContext(a)},b.WebGLFastSpriteBatch.prototype.constructor=b.WebGLFastSpriteBatch,b.WebGLFastSpriteBatch.prototype.setContext=function(a){this.gl=a,this.vertexBuffer=a.createBuffer(),this.indexBuffer=a.createBuffer(),a.bindBuffer(a.ELEMENT_ARRAY_BUFFER,this.indexBuffer),a.bufferData(a.ELEMENT_ARRAY_BUFFER,this.indices,a.STATIC_DRAW),a.bindBuffer(a.ARRAY_BUFFER,this.vertexBuffer),a.bufferData(a.ARRAY_BUFFER,this.vertices,a.DYNAMIC_DRAW)},b.WebGLFastSpriteBatch.prototype.begin=function(a,b){this.renderSession=b,this.shader=this.renderSession.shaderManager.fastShader,this.matrix=a.worldTransform.toArray(!0),this.start()},b.WebGLFastSpriteBatch.prototype.end=function(){this.flush()},b.WebGLFastSpriteBatch.prototype.render=function(a){var b=a.children,c=b[0];if(c.texture._uvs){this.currentBaseTexture=c.texture.baseTexture,c.blendMode!==this.renderSession.blendModeManager.currentBlendMode&&(this.flush(),this.renderSession.blendModeManager.setBlendMode(c.blendMode));for(var d=0,e=b.length;e>d;d++)this.renderSprite(b[d]);this.flush()}},b.WebGLFastSpriteBatch.prototype.renderSprite=function(a){if(a.visible&&(a.texture.baseTexture===this.currentBaseTexture||(this.flush(),this.currentBaseTexture=a.texture.baseTexture,a.texture._uvs))){var b,c,d,e,f,g,h,i,j=this.vertices;if(b=a.texture._uvs,c=a.texture.frame.width,d=a.texture.frame.height,a.texture.trim){var k=a.texture.trim;f=k.x-a.anchor.x*k.width,e=f+a.texture.crop.width,h=k.y-a.anchor.y*k.height,g=h+a.texture.crop.height}else e=a.texture.frame.width*(1-a.anchor.x),f=a.texture.frame.width*-a.anchor.x,g=a.texture.frame.height*(1-a.anchor.y),h=a.texture.frame.height*-a.anchor.y;i=4*this.currentBatchSize*this.vertSize,j[i++]=f,j[i++]=h,j[i++]=a.position.x,j[i++]=a.position.y,j[i++]=a.scale.x,j[i++]=a.scale.y,j[i++]=a.rotation,j[i++]=b.x0,j[i++]=b.y1,j[i++]=a.alpha,j[i++]=e,j[i++]=h,j[i++]=a.position.x,j[i++]=a.position.y,j[i++]=a.scale.x,j[i++]=a.scale.y,j[i++]=a.rotation,j[i++]=b.x1,j[i++]=b.y1,j[i++]=a.alpha,j[i++]=e,j[i++]=g,j[i++]=a.position.x,j[i++]=a.position.y,j[i++]=a.scale.x,j[i++]=a.scale.y,j[i++]=a.rotation,j[i++]=b.x2,j[i++]=b.y2,j[i++]=a.alpha,j[i++]=f,j[i++]=g,j[i++]=a.position.x,j[i++]=a.position.y,j[i++]=a.scale.x,j[i++]=a.scale.y,j[i++]=a.rotation,j[i++]=b.x3,j[i++]=b.y3,j[i++]=a.alpha,this.currentBatchSize++,this.currentBatchSize>=this.size&&this.flush()}},b.WebGLFastSpriteBatch.prototype.flush=function(){if(0!==this.currentBatchSize){var a=this.gl;if(this.currentBaseTexture._glTextures[a.id]||this.renderSession.renderer.updateTexture(this.currentBaseTexture,a),a.bindTexture(a.TEXTURE_2D,this.currentBaseTexture._glTextures[a.id]),this.currentBatchSize>.5*this.size)a.bufferSubData(a.ARRAY_BUFFER,0,this.vertices);else{var b=this.vertices.subarray(0,4*this.currentBatchSize*this.vertSize);a.bufferSubData(a.ARRAY_BUFFER,0,b)}a.drawElements(a.TRIANGLES,6*this.currentBatchSize,a.UNSIGNED_SHORT,0),this.currentBatchSize=0,this.renderSession.drawCount++}},b.WebGLFastSpriteBatch.prototype.stop=function(){this.flush()},b.WebGLFastSpriteBatch.prototype.start=function(){var a=this.gl;a.activeTexture(a.TEXTURE0),a.bindBuffer(a.ARRAY_BUFFER,this.vertexBuffer),a.bindBuffer(a.ELEMENT_ARRAY_BUFFER,this.indexBuffer);var b=this.renderSession.projection;a.uniform2f(this.shader.projectionVector,b.x,b.y),a.uniformMatrix3fv(this.shader.uMatrix,!1,this.matrix);var c=4*this.vertSize;a.vertexAttribPointer(this.shader.aVertexPosition,2,a.FLOAT,!1,c,0),a.vertexAttribPointer(this.shader.aPositionCoord,2,a.FLOAT,!1,c,8),a.vertexAttribPointer(this.shader.aScale,2,a.FLOAT,!1,c,16),a.vertexAttribPointer(this.shader.aRotation,1,a.FLOAT,!1,c,24),a.vertexAttribPointer(this.shader.aTextureCoord,2,a.FLOAT,!1,c,28),a.vertexAttribPointer(this.shader.colorAttribute,1,a.FLOAT,!1,c,36)},b.WebGLFilterManager=function(){this.filterStack=[],this.offsetX=0,this.offsetY=0},b.WebGLFilterManager.prototype.constructor=b.WebGLFilterManager,b.WebGLFilterManager.prototype.setContext=function(a){this.gl=a,this.texturePool=[],this.initShaderBuffers()},b.WebGLFilterManager.prototype.begin=function(a,b){this.renderSession=a,this.defaultShader=a.shaderManager.defaultShader;var c=this.renderSession.projection;this.width=2*c.x,this.height=2*-c.y,this.buffer=b},b.WebGLFilterManager.prototype.pushFilter=function(a){var c=this.gl,d=this.renderSession.projection,e=this.renderSession.offset;a._filterArea=a.target.filterArea||a.target.getBounds(),this.filterStack.push(a);var f=a.filterPasses[0];this.offsetX+=a._filterArea.x,this.offsetY+=a._filterArea.y;var g=this.texturePool.pop();g?g.resize(this.width,this.height):g=new b.FilterTexture(this.gl,this.width,this.height),c.bindTexture(c.TEXTURE_2D,g.texture);var h=a._filterArea,i=f.padding;h.x-=i,h.y-=i,h.width+=2*i,h.height+=2*i,h.x<0&&(h.x=0),h.width>this.width&&(h.width=this.width),h.y<0&&(h.y=0),h.height>this.height&&(h.height=this.height),c.bindFramebuffer(c.FRAMEBUFFER,g.frameBuffer),c.viewport(0,0,h.width,h.height),d.x=h.width/2,d.y=-h.height/2,e.x=-h.x,e.y=-h.y,c.colorMask(!0,!0,!0,!0),c.clearColor(0,0,0,0),c.clear(c.COLOR_BUFFER_BIT),a._glFilterTexture=g},b.WebGLFilterManager.prototype.popFilter=function(){var a=this.gl,c=this.filterStack.pop(),d=c._filterArea,e=c._glFilterTexture,f=this.renderSession.projection,g=this.renderSession.offset;if(c.filterPasses.length>1){a.viewport(0,0,d.width,d.height),a.bindBuffer(a.ARRAY_BUFFER,this.vertexBuffer),this.vertexArray[0]=0,this.vertexArray[1]=d.height,this.vertexArray[2]=d.width,this.vertexArray[3]=d.height,this.vertexArray[4]=0,this.vertexArray[5]=0,this.vertexArray[6]=d.width,this.vertexArray[7]=0,a.bufferSubData(a.ARRAY_BUFFER,0,this.vertexArray),a.bindBuffer(a.ARRAY_BUFFER,this.uvBuffer),this.uvArray[2]=d.width/this.width,this.uvArray[5]=d.height/this.height,this.uvArray[6]=d.width/this.width,this.uvArray[7]=d.height/this.height,a.bufferSubData(a.ARRAY_BUFFER,0,this.uvArray);var h=e,i=this.texturePool.pop();i||(i=new b.FilterTexture(this.gl,this.width,this.height)),i.resize(this.width,this.height),a.bindFramebuffer(a.FRAMEBUFFER,i.frameBuffer),a.clear(a.COLOR_BUFFER_BIT),a.disable(a.BLEND);for(var j=0;jA?A:z,c.beginPath(),c.moveTo(v,w+z),c.lineTo(v,w+y-z),c.quadraticCurveTo(v,w+y,v+z,w+y),c.lineTo(v+x-z,w+y),c.quadraticCurveTo(v+x,w+y,v+x,w+y-z),c.lineTo(v+x,w+z),c.quadraticCurveTo(v+x,w,v+x-z,w),c.lineTo(v+z,w),c.quadraticCurveTo(v,w,v,w+z),c.closePath(),(g.fillColor||0===g.fillColor)&&(c.globalAlpha=g.fillAlpha*d,c.fillStyle=e="#"+("00000"+(0|g.fillColor).toString(16)).substr(-6),c.fill()),g.lineWidth&&(c.globalAlpha=g.lineAlpha*d,c.stroke())}}},b.CanvasGraphics.renderGraphicsMask=function(a,c){var d=a.graphicsData.length;if(0!==d){d>1&&(d=1,window.console.log("Pixi.js warning: masks in canvas can only mask using the first path in the graphics object"));for(var e=0;1>e;e++){var f=a.graphicsData[e],g=f.shape;if(f.type===b.Graphics.POLY){c.beginPath();var h=g.points;c.moveTo(h[0],h[1]);for(var i=1;iA?A:z,c.beginPath(),c.moveTo(v,w+z),c.lineTo(v,w+y-z),c.quadraticCurveTo(v,w+y,v+z,w+y),c.lineTo(v+x-z,w+y),c.quadraticCurveTo(v+x,w+y,v+x,w+y-z),c.lineTo(v+x,w+z),c.quadraticCurveTo(v+x,w,v+x-z,w),c.lineTo(v+z,w),c.quadraticCurveTo(v,w,v,w+z),c.closePath()}}}},b.Graphics=function(){b.DisplayObjectContainer.call(this),this.renderable=!0,this.fillAlpha=1,this.lineWidth=0,this.lineColor=0,this.graphicsData=[],this.tint=16777215,this.blendMode=b.blendModes.NORMAL,this.currentPath=null,this._webGL=[],this.isMask=!1,this.boundsPadding=0,this._localBounds=new b.Rectangle(0,0,1,1),this.dirty=!0,this.webGLDirty=!1,this.cachedSpriteDirty=!1},b.Graphics.prototype=Object.create(b.DisplayObjectContainer.prototype),b.Graphics.prototype.constructor=b.Graphics,Object.defineProperty(b.Graphics.prototype,"cacheAsBitmap",{get:function(){return this._cacheAsBitmap},set:function(a){this._cacheAsBitmap=a,this._cacheAsBitmap?this._generateCachedSprite():(this.destroyCachedSprite(),this.dirty=!0)}}),b.Graphics.prototype.lineStyle=function(a,c,d){if(this.lineWidth=a||0,this.lineColor=c||0,this.lineAlpha=arguments.length<3?1:d,this.currentPath){if(this.currentPath.shape.points.length)return this.drawShape(new b.Polygon(this.currentPath.shape.points.slice(-2))),this;this.currentPath.lineWidth=this.lineWidth,this.currentPath.lineColor=this.lineColor,this.currentPath.lineAlpha=this.lineAlpha}return this},b.Graphics.prototype.moveTo=function(a,c){return this.drawShape(new b.Polygon([a,c])),this},b.Graphics.prototype.lineTo=function(a,b){return this.currentPath.shape.points.push(a,b),this.dirty=!0,this},b.Graphics.prototype.quadraticCurveTo=function(a,b,c,d){this.currentPath?0===this.currentPath.shape.points.length&&(this.currentPath.shape.points=[0,0]):this.moveTo(0,0);var e,f,g=20,h=this.currentPath.shape.points;0===h.length&&this.moveTo(0,0);for(var i=h[h.length-2],j=h[h.length-1],k=0,l=1;g>=l;l++)k=l/g,e=i+(a-i)*k,f=j+(b-j)*k,h.push(e+(a+(c-a)*k-e)*k,f+(b+(d-b)*k-f)*k);return this.dirty=!0,this},b.Graphics.prototype.bezierCurveTo=function(a,b,c,d,e,f){this.currentPath?0===this.currentPath.shape.points.length&&(this.currentPath.shape.points=[0,0]):this.moveTo(0,0);for(var g,h,i,j,k,l=20,m=this.currentPath.shape.points,n=m[m.length-2],o=m[m.length-1],p=0,q=1;l>=q;q++)p=q/l,g=1-p,h=g*g,i=h*g,j=p*p,k=j*p,m.push(i*n+3*h*p*a+3*g*j*c+k*e,i*o+3*h*p*b+3*g*j*d+k*f);return this.dirty=!0,this},b.Graphics.prototype.arcTo=function(a,b,c,d,e){this.currentPath?0===this.currentPath.shape.points.length&&this.currentPath.shape.points.push(a,b):this.moveTo(a,b);var f=this.currentPath.shape.points,g=f[f.length-2],h=f[f.length-1],i=h-b,j=g-a,k=d-b,l=c-a,m=Math.abs(i*l-j*k);if(1e-8>m||0===e)(f[f.length-2]!==a||f[f.length-1]!==b)&&f.push(a,b);else{var n=i*i+j*j,o=k*k+l*l,p=i*k+j*l,q=e*Math.sqrt(n)/m,r=e*Math.sqrt(o)/m,s=q*p/n,t=r*p/o,u=q*l+r*j,v=q*k+r*i,w=j*(r+s),x=i*(r+s),y=l*(q+t),z=k*(q+t),A=Math.atan2(x-v,w-u),B=Math.atan2(z-v,y-u);this.arc(u+a,v+b,e,A,B,j*k>l*i)}return this.dirty=!0,this},b.Graphics.prototype.arc=function(a,b,c,d,e,f){var g=a+Math.cos(d)*c,h=b+Math.sin(d)*c,i=this.currentPath.shape.points;if(0===i.length?(this.moveTo(g,h),i=this.currentPath.shape.points):(i[i.length-2]!==g||i[i.length-1]!==h)&&i.push(g,h),d===e)return this;!f&&d>=e?e+=2*Math.PI:f&&e>=d&&(d+=2*Math.PI);var j=f?-1*(d-e):e-d,k=Math.abs(j)/(2*Math.PI)*40;if(0===j)return this;for(var l=j/(2*k),m=2*l,n=Math.cos(l),o=Math.sin(l),p=k-1,q=p%1/p,r=0;p>=r;r++){var s=r+q*r,t=l+d+m*s,u=Math.cos(t),v=-Math.sin(t);i.push((n*u+o*v)*c+a,(n*-v+o*u)*c+b)}return this.dirty=!0,this},b.Graphics.prototype.beginFill=function(a,b){return this.filling=!0,this.fillColor=a||0,this.fillAlpha=void 0===b?1:b,this.currentPath&&this.currentPath.shape.points.length<=2&&(this.currentPath.fill=this.filling,this.currentPath.fillColor=this.fillColor,this.currentPath.fillAlpha=this.fillAlpha),this},b.Graphics.prototype.endFill=function(){return this.filling=!1,this.fillColor=null,this.fillAlpha=1,this},b.Graphics.prototype.drawRect=function(a,c,d,e){return this.drawShape(new b.Rectangle(a,c,d,e)),this},b.Graphics.prototype.drawRoundedRect=function(a,c,d,e,f){return this.drawShape(new b.RoundedRectangle(a,c,d,e,f)),this},b.Graphics.prototype.drawCircle=function(a,c,d){return this.drawShape(new b.Circle(a,c,d)),this},b.Graphics.prototype.drawEllipse=function(a,c,d,e){return this.drawShape(new b.Ellipse(a,c,d,e)),this},b.Graphics.prototype.drawPolygon=function(a){return a instanceof Array||(a=Array.prototype.slice.call(arguments)),this.drawShape(new b.Polygon(a)),this},b.Graphics.prototype.clear=function(){return this.lineWidth=0,this.filling=!1,this.dirty=!0,this.clearDirty=!0,this.graphicsData=[],this},b.Graphics.prototype.generateTexture=function(a,c){a=a||1;var d=this.getBounds(),e=new b.CanvasBuffer(d.width*a,d.height*a),f=b.Texture.fromCanvas(e.canvas,c);return f.baseTexture.resolution=a,e.context.scale(a,a),e.context.translate(-d.x,-d.y),b.CanvasGraphics.renderGraphics(this,e.context),f},b.Graphics.prototype._renderWebGL=function(a){if(this.visible!==!1&&0!==this.alpha&&this.isMask!==!0){if(this._cacheAsBitmap)return(this.dirty||this.cachedSpriteDirty)&&(this._generateCachedSprite(),this.updateCachedSpriteTexture(),this.cachedSpriteDirty=!1,this.dirty=!1),this._cachedSprite.worldAlpha=this.worldAlpha,b.Sprite.prototype._renderWebGL.call(this._cachedSprite,a),void 0;if(a.spriteBatch.stop(),a.blendModeManager.setBlendMode(this.blendMode),this._mask&&a.maskManager.pushMask(this._mask,a),this._filters&&a.filterManager.pushFilter(this._filterBlock),this.blendMode!==a.spriteBatch.currentBlendMode){a.spriteBatch.currentBlendMode=this.blendMode;var c=b.blendModesWebGL[a.spriteBatch.currentBlendMode];a.spriteBatch.gl.blendFunc(c[0],c[1])}if(this.webGLDirty&&(this.dirty=!0,this.webGLDirty=!1),b.WebGLGraphics.renderGraphics(this,a),this.children.length){a.spriteBatch.start();for(var d=0,e=this.children.length;e>d;d++)this.children[d]._renderWebGL(a);a.spriteBatch.stop()}this._filters&&a.filterManager.popFilter(),this._mask&&a.maskManager.popMask(this.mask,a),a.drawCount++,a.spriteBatch.start()}},b.Graphics.prototype._renderCanvas=function(a){if(this.visible!==!1&&0!==this.alpha&&this.isMask!==!0){if(this._cacheAsBitmap)return(this.dirty||this.cachedSpriteDirty)&&(this._generateCachedSprite(),this.updateCachedSpriteTexture(),this.cachedSpriteDirty=!1,this.dirty=!1),this._cachedSprite.alpha=this.alpha,b.Sprite.prototype._renderCanvas.call(this._cachedSprite,a),void 0;var c=a.context,d=this.worldTransform;this.blendMode!==a.currentBlendMode&&(a.currentBlendMode=this.blendMode,c.globalCompositeOperation=b.blendModesCanvas[a.currentBlendMode]),this._mask&&a.maskManager.pushMask(this._mask,a);var e=a.resolution;c.setTransform(d.a*e,d.b*e,d.c*e,d.d*e,d.tx*e,d.ty*e),b.CanvasGraphics.renderGraphics(this,c);for(var f=0,g=this.children.length;g>f;f++)this.children[f]._renderCanvas(a);this._mask&&a.maskManager.popMask(a)}},b.Graphics.prototype.getBounds=function(a){if(this.isMask)return b.EmptyRectangle;this.dirty&&(this.updateLocalBounds(),this.webGLDirty=!0,this.cachedSpriteDirty=!0,this.dirty=!1);var c=this._localBounds,d=c.x,e=c.width+c.x,f=c.y,g=c.height+c.y,h=a||this.worldTransform,i=h.a,j=h.b,k=h.c,l=h.d,m=h.tx,n=h.ty,o=i*e+k*g+m,p=l*g+j*e+n,q=i*d+k*g+m,r=l*g+j*d+n,s=i*d+k*f+m,t=l*f+j*d+n,u=i*e+k*f+m,v=l*f+j*e+n,w=o,x=p,y=o,z=p;return y=y>q?q:y,y=y>s?s:y,y=y>u?u:y,z=z>r?r:z,z=z>t?t:z,z=z>v?v:z,w=q>w?q:w,w=s>w?s:w,w=u>w?u:w,x=r>x?r:x,x=t>x?t:x,x=v>x?v:x,this._bounds.x=y,this._bounds.width=w-y,this._bounds.y=z,this._bounds.height=x-z,this._bounds},b.Graphics.prototype.updateLocalBounds=function(){var a=1/0,c=-1/0,d=1/0,e=-1/0;if(this.graphicsData.length)for(var f,g,h,i,j,k,l=0;lh?h:a,c=h+j>c?h+j:c,d=d>i?i:d,e=i+k>e?i+k:e;else if(n===b.Graphics.CIRC)h=f.x,i=f.y,j=f.radius+o/2,k=f.radius+o/2,a=a>h-j?h-j:a,c=h+j>c?h+j:c,d=d>i-k?i-k:d,e=i+k>e?i+k:e;else if(n===b.Graphics.ELIP)h=f.x,i=f.y,j=f.width+o/2,k=f.height+o/2,a=a>h-j?h-j:a,c=h+j>c?h+j:c,d=d>i-k?i-k:d,e=i+k>e?i+k:e;else{g=f.points;for(var p=0;ph-o?h-o:a,c=h+o>c?h+o:c,d=d>i-o?i-o:d,e=i+o>e?i+o:e}}else a=0,c=0,d=0,e=0;var q=this.boundsPadding;this._localBounds.x=a-q,this._localBounds.width=c-a+2*q,this._localBounds.y=d-q,this._localBounds.height=e-d+2*q},b.Graphics.prototype._generateCachedSprite=function(){var a=this.getLocalBounds();if(this._cachedSprite)this._cachedSprite.buffer.resize(a.width,a.height);else{var c=new b.CanvasBuffer(a.width,a.height),d=b.Texture.fromCanvas(c.canvas);this._cachedSprite=new b.Sprite(d),this._cachedSprite.buffer=c,this._cachedSprite.worldTransform=this.worldTransform}this._cachedSprite.anchor.x=-(a.x/a.width),this._cachedSprite.anchor.y=-(a.y/a.height),this._cachedSprite.buffer.context.translate(-a.x,-a.y),this.worldAlpha=1,b.CanvasGraphics.renderGraphics(this,this._cachedSprite.buffer.context),this._cachedSprite.alpha=this.alpha},b.Graphics.prototype.updateCachedSpriteTexture=function(){var a=this._cachedSprite,b=a.texture,c=a.buffer.canvas;b.baseTexture.width=c.width,b.baseTexture.height=c.height,b.crop.width=b.frame.width=c.width,b.crop.height=b.frame.height=c.height,a._width=c.width,a._height=c.height,b.baseTexture.dirty()},b.Graphics.prototype.destroyCachedSprite=function(){this._cachedSprite.texture.destroy(!0),this._cachedSprite=null},b.Graphics.prototype.drawShape=function(a){this.currentPath&&this.currentPath.shape.points.length<=2&&this.graphicsData.pop(),this.currentPath=null;var c=new b.GraphicsData(this.lineWidth,this.lineColor,this.lineAlpha,this.fillColor,this.fillAlpha,this.filling,a);return this.graphicsData.push(c),c.type===b.Graphics.POLY&&(c.shape.closed=this.filling,this.currentPath=c),this.dirty=!0,c},b.GraphicsData=function(a,b,c,d,e,f,g){this.lineWidth=a,this.lineColor=b,this.lineAlpha=c,this.fillColor=d,this.fillAlpha=e,this.fill=f,this.shape=g,this.type=g.type},b.Graphics.POLY=0,b.Graphics.RECT=1,b.Graphics.CIRC=2,b.Graphics.ELIP=3,b.Graphics.RREC=4,b.Polygon.prototype.type=b.Graphics.POLY,b.Rectangle.prototype.type=b.Graphics.RECT,b.Circle.prototype.type=b.Graphics.CIRC,b.Ellipse.prototype.type=b.Graphics.ELIP,b.RoundedRectangle.prototype.type=b.Graphics.RREC,b.Strip=function(a){b.DisplayObjectContainer.call(this),this.texture=a,this.uvs=new b.Float32Array([0,1,1,1,1,0,0,1]),this.verticies=new b.Float32Array([0,0,100,0,100,100,0,100]),this.colors=new b.Float32Array([1,1,1,1]),this.indices=new b.Uint16Array([0,1,2,3]),this.dirty=!0,this.blendMode=b.blendModes.NORMAL,this.padding=0},b.Strip.prototype=Object.create(b.DisplayObjectContainer.prototype),b.Strip.prototype.constructor=b.Strip,b.Strip.prototype._renderWebGL=function(a){!this.visible||this.alpha<=0||(a.spriteBatch.stop(),this._vertexBuffer||this._initWebGL(a),a.shaderManager.setShader(a.shaderManager.stripShader),this._renderStrip(a),a.spriteBatch.start())},b.Strip.prototype._initWebGL=function(a){var b=a.gl;this._vertexBuffer=b.createBuffer(),this._indexBuffer=b.createBuffer(),this._uvBuffer=b.createBuffer(),this._colorBuffer=b.createBuffer(),b.bindBuffer(b.ARRAY_BUFFER,this._vertexBuffer),b.bufferData(b.ARRAY_BUFFER,this.verticies,b.DYNAMIC_DRAW),b.bindBuffer(b.ARRAY_BUFFER,this._uvBuffer),b.bufferData(b.ARRAY_BUFFER,this.uvs,b.STATIC_DRAW),b.bindBuffer(b.ARRAY_BUFFER,this._colorBuffer),b.bufferData(b.ARRAY_BUFFER,this.colors,b.STATIC_DRAW),b.bindBuffer(b.ELEMENT_ARRAY_BUFFER,this._indexBuffer),b.bufferData(b.ELEMENT_ARRAY_BUFFER,this.indices,b.STATIC_DRAW)},b.Strip.prototype._renderStrip=function(a){var b=a.gl,c=a.projection,d=a.offset,e=a.shaderManager.stripShader;a.blendModeManager.setBlendMode(this.blendMode),b.uniformMatrix3fv(e.translationMatrix,!1,this.worldTransform.toArray(!0)),b.uniform2f(e.projectionVector,c.x,-c.y),b.uniform2f(e.offsetVector,-d.x,-d.y),b.uniform1f(e.alpha,this.worldAlpha),this.dirty?(this.dirty=!1,b.bindBuffer(b.ARRAY_BUFFER,this._vertexBuffer),b.bufferData(b.ARRAY_BUFFER,this.verticies,b.STATIC_DRAW),b.vertexAttribPointer(e.aVertexPosition,2,b.FLOAT,!1,0,0),b.bindBuffer(b.ARRAY_BUFFER,this._uvBuffer),b.bufferData(b.ARRAY_BUFFER,this.uvs,b.STATIC_DRAW),b.vertexAttribPointer(e.aTextureCoord,2,b.FLOAT,!1,0,0),b.activeTexture(b.TEXTURE0),this.texture.baseTexture._dirty[b.id]?a.renderer.updateTexture(this.texture.baseTexture):b.bindTexture(b.TEXTURE_2D,this.texture.baseTexture._glTextures[b.id]),b.bindBuffer(b.ELEMENT_ARRAY_BUFFER,this._indexBuffer),b.bufferData(b.ELEMENT_ARRAY_BUFFER,this.indices,b.STATIC_DRAW)):(b.bindBuffer(b.ARRAY_BUFFER,this._vertexBuffer),b.bufferSubData(b.ARRAY_BUFFER,0,this.verticies),b.vertexAttribPointer(e.aVertexPosition,2,b.FLOAT,!1,0,0),b.bindBuffer(b.ARRAY_BUFFER,this._uvBuffer),b.vertexAttribPointer(e.aTextureCoord,2,b.FLOAT,!1,0,0),b.activeTexture(b.TEXTURE0),this.texture.baseTexture._dirty[b.id]?a.renderer.updateTexture(this.texture.baseTexture):b.bindTexture(b.TEXTURE_2D,this.texture.baseTexture._glTextures[b.id]),b.bindBuffer(b.ELEMENT_ARRAY_BUFFER,this._indexBuffer)),b.drawElements(b.TRIANGLE_STRIP,this.indices.length,b.UNSIGNED_SHORT,0)},b.Strip.prototype._renderCanvas=function(a){var b=a.context,c=this.worldTransform;a.roundPixels?b.setTransform(c.a,c.b,c.c,c.d,0|c.tx,0|c.ty):b.setTransform(c.a,c.b,c.c,c.d,c.tx,c.ty);var d=this,e=d.verticies,f=d.uvs,g=e.length/2;this.count++;for(var h=0;g-2>h;h++){var i=2*h,j=e[i],k=e[i+2],l=e[i+4],m=e[i+1],n=e[i+3],o=e[i+5];if(this.padding>0){var p=(j+k+l)/3,q=(m+n+o)/3,r=j-p,s=m-q,t=Math.sqrt(r*r+s*s);j=p+r/t*(t+3),m=q+s/t*(t+3),r=k-p,s=n-q,t=Math.sqrt(r*r+s*s),k=p+r/t*(t+3),n=q+s/t*(t+3),r=l-p,s=o-q,t=Math.sqrt(r*r+s*s),l=p+r/t*(t+3),o=q+s/t*(t+3)}var u=f[i]*d.texture.width,v=f[i+2]*d.texture.width,w=f[i+4]*d.texture.width,x=f[i+1]*d.texture.height,y=f[i+3]*d.texture.height,z=f[i+5]*d.texture.height;b.save(),b.beginPath(),b.moveTo(j,m),b.lineTo(k,n),b.lineTo(l,o),b.closePath(),b.clip();var A=u*y+x*w+v*z-y*w-x*v-u*z,B=j*y+x*l+k*z-y*l-x*k-j*z,C=u*k+j*w+v*l-k*w-j*v-u*l,D=u*y*l+x*k*w+j*v*z-j*y*w-x*v*l-u*k*z,E=m*y+x*o+n*z-y*o-x*n-m*z,F=u*n+m*w+v*o-n*w-m*v-u*o,G=u*y*o+x*n*w+m*v*z-m*y*w-x*v*o-u*n*z;b.transform(B/A,E/A,C/A,F/A,D/A,G/A),b.drawImage(d.texture.baseTexture.source,0,0),b.restore()}},b.Strip.prototype.renderStripFlat=function(a){var b=this.context,c=a.verticies,d=c.length/2;this.count++,b.beginPath();for(var e=1;d-2>e;e++){var f=2*e,g=c[f],h=c[f+2],i=c[f+4],j=c[f+1],k=c[f+3],l=c[f+5];b.moveTo(g,j),b.lineTo(h,k),b.lineTo(i,l)}b.fillStyle="#FF0000",b.fill(),b.closePath()},b.Strip.prototype.onTextureUpdate=function(){this.updateFrame=!0},b.Rope=function(a,c){b.Strip.call(this,a),this.points=c,this.verticies=new b.Float32Array(4*c.length),this.uvs=new b.Float32Array(4*c.length),this.colors=new b.Float32Array(2*c.length),this.indices=new b.Uint16Array(2*c.length),this.refresh()},b.Rope.prototype=Object.create(b.Strip.prototype),b.Rope.prototype.constructor=b.Rope,b.Rope.prototype.refresh=function(){var a=this.points;if(!(a.length<1)){var b=this.uvs,c=a[0],d=this.indices,e=this.colors;this.count-=.2,b[0]=0,b[1]=0,b[2]=0,b[3]=1,e[0]=1,e[1]=1,d[0]=0,d[1]=1;for(var f,g,h,i=a.length,j=1;i>j;j++)f=a[j],g=4*j,h=j/(i-1),j%2?(b[g]=h,b[g+1]=0,b[g+2]=h,b[g+3]=1):(b[g]=h,b[g+1]=0,b[g+2]=h,b[g+3]=1),g=2*j,e[g]=1,e[g+1]=1,g=2*j,d[g]=g,d[g+1]=g+1,c=f}},b.Rope.prototype.updateTransform=function(){var a=this.points;if(!(a.length<1)){var c,d=a[0],e={x:0,y:0};this.count-=.2;for(var f,g,h,i,j,k=this.verticies,l=a.length,m=0;l>m;m++)f=a[m],g=4*m,c=m1&&(h=1),i=Math.sqrt(e.x*e.x+e.y*e.y),j=this.texture.height/2,e.x/=i,e.y/=i,e.x*=j,e.y*=j,k[g]=f.x+e.x,k[g+1]=f.y+e.y,k[g+2]=f.x-e.x,k[g+3]=f.y-e.y,d=f;b.DisplayObjectContainer.prototype.updateTransform.call(this)}},b.Rope.prototype.setTexture=function(a){this.texture=a},b.TilingSprite=function(a,c,d){b.Sprite.call(this,a),this._width=c||100,this._height=d||100,this.tileScale=new b.Point(1,1),this.tileScaleOffset=new b.Point(1,1),this.tilePosition=new b.Point(0,0),this.renderable=!0,this.tint=16777215,this.blendMode=b.blendModes.NORMAL},b.TilingSprite.prototype=Object.create(b.Sprite.prototype),b.TilingSprite.prototype.constructor=b.TilingSprite,Object.defineProperty(b.TilingSprite.prototype,"width",{get:function(){return this._width},set:function(a){this._width=a}}),Object.defineProperty(b.TilingSprite.prototype,"height",{get:function(){return this._height},set:function(a){this._height=a}}),b.TilingSprite.prototype.setTexture=function(a){this.texture!==a&&(this.texture=a,this.refreshTexture=!0,this.cachedTint=16777215)},b.TilingSprite.prototype._renderWebGL=function(a){if(this.visible!==!1&&0!==this.alpha){var c,d;for(this._mask&&(a.spriteBatch.stop(),a.maskManager.pushMask(this.mask,a),a.spriteBatch.start()),this._filters&&(a.spriteBatch.flush(),a.filterManager.pushFilter(this._filterBlock)),!this.tilingTexture||this.refreshTexture?(this.generateTilingTexture(!0),this.tilingTexture&&this.tilingTexture.needsUpdate&&(b.updateWebGLTexture(this.tilingTexture.baseTexture,a.gl),this.tilingTexture.needsUpdate=!1)):a.spriteBatch.renderTilingSprite(this),c=0,d=this.children.length;d>c;c++)this.children[c]._renderWebGL(a);a.spriteBatch.stop(),this._filters&&a.filterManager.popFilter(),this._mask&&a.maskManager.popMask(this._mask,a),a.spriteBatch.start()}},b.TilingSprite.prototype._renderCanvas=function(a){if(this.visible!==!1&&0!==this.alpha){var c=a.context;this._mask&&a.maskManager.pushMask(this._mask,c),c.globalAlpha=this.worldAlpha;var d,e,f=this.worldTransform,g=a.resolution;if(c.setTransform(f.a*g,f.c*g,f.b*g,f.d*g,f.tx*g,f.ty*g),!this.__tilePattern||this.refreshTexture){if(this.generateTilingTexture(!1),!this.tilingTexture)return;this.__tilePattern=c.createPattern(this.tilingTexture.baseTexture.source,"repeat")}this.blendMode!==a.currentBlendMode&&(a.currentBlendMode=this.blendMode,c.globalCompositeOperation=b.blendModesCanvas[a.currentBlendMode]);var h=this.tilePosition,i=this.tileScale;for(h.x%=this.tilingTexture.baseTexture.width,h.y%=this.tilingTexture.baseTexture.height,c.scale(i.x,i.y),c.translate(h.x+this.anchor.x*-this._width,h.y+this.anchor.y*-this._height),c.fillStyle=this.__tilePattern,c.fillRect(-h.x,-h.y,this._width/i.x,this._height/i.y),c.scale(1/i.x,1/i.y),c.translate(-h.x+this.anchor.x*this._width,-h.y+this.anchor.y*this._height),this._mask&&a.maskManager.popMask(a.context),d=0,e=this.children.length;e>d;d++)this.children[d]._renderCanvas(a)}},b.TilingSprite.prototype.getBounds=function(){var a=this._width,b=this._height,c=a*(1-this.anchor.x),d=a*-this.anchor.x,e=b*(1-this.anchor.y),f=b*-this.anchor.y,g=this.worldTransform,h=g.a,i=g.b,j=g.c,k=g.d,l=g.tx,m=g.ty,n=h*d+j*f+l,o=k*f+i*d+m,p=h*c+j*f+l,q=k*f+i*c+m,r=h*c+j*e+l,s=k*e+i*c+m,t=h*d+j*e+l,u=k*e+i*d+m,v=-1/0,w=-1/0,x=1/0,y=1/0;x=x>n?n:x,x=x>p?p:x,x=x>r?r:x,x=x>t?t:x,y=y>o?o:y,y=y>q?q:y,y=y>s?s:y,y=y>u?u:y,v=n>v?n:v,v=p>v?p:v,v=r>v?r:v,v=t>v?t:v,w=o>w?o:w,w=q>w?q:w,w=s>w?s:w,w=u>w?u:w;var z=this._bounds;return z.x=x,z.width=v-x,z.y=y,z.height=w-y,this._currentBounds=z,z},b.TilingSprite.prototype.onTextureUpdate=function(){},b.TilingSprite.prototype.generateTilingTexture=function(a){if(this.texture.baseTexture.hasLoaded){var c,d,e=this.originalTexture||this.texture,f=e.frame,g=f.width!==e.baseTexture.width||f.height!==e.baseTexture.height,h=!1;if(a?(c=b.getNextPowerOfTwo(f.width),d=b.getNextPowerOfTwo(f.height),(f.width!==c||f.height!==d)&&(h=!0)):g&&(c=f.width,d=f.height,h=!0),h){var i;this.tilingTexture&&this.tilingTexture.isTiling?(i=this.tilingTexture.canvasBuffer,i.resize(c,d),this.tilingTexture.baseTexture.width=c,this.tilingTexture.baseTexture.height=d,this.tilingTexture.needsUpdate=!0):(i=new b.CanvasBuffer(c,d),this.tilingTexture=b.Texture.fromCanvas(i.canvas),this.tilingTexture.canvasBuffer=i,this.tilingTexture.isTiling=!0),i.context.drawImage(e.baseTexture.source,e.crop.x,e.crop.y,e.crop.width,e.crop.height,0,0,c,d),this.tileScaleOffset.x=f.width/c,this.tileScaleOffset.y=f.height/d}else this.tilingTexture&&this.tilingTexture.isTiling&&this.tilingTexture.destroy(!0),this.tileScaleOffset.x=1,this.tileScaleOffset.y=1,this.tilingTexture=e;this.refreshTexture=!1,this.originalTexture=this.texture,this.texture=this.tilingTexture,this.tilingTexture.baseTexture._powerOf2=!0}};var c={};c.BoneData=function(a,b){this.name=a,this.parent=b},c.BoneData.prototype={length:0,x:0,y:0,rotation:0,scaleX:1,scaleY:1},c.SlotData=function(a,b){this.name=a,this.boneData=b},c.SlotData.prototype={r:1,g:1,b:1,a:1,attachmentName:null},c.Bone=function(a,b){this.data=a,this.parent=b,this.setToSetupPose() +},c.Bone.yDown=!1,c.Bone.prototype={x:0,y:0,rotation:0,scaleX:1,scaleY:1,m00:0,m01:0,worldX:0,m10:0,m11:0,worldY:0,worldRotation:0,worldScaleX:1,worldScaleY:1,updateWorldTransform:function(a,b){var d=this.parent;null!=d?(this.worldX=this.x*d.m00+this.y*d.m01+d.worldX,this.worldY=this.x*d.m10+this.y*d.m11+d.worldY,this.worldScaleX=d.worldScaleX*this.scaleX,this.worldScaleY=d.worldScaleY*this.scaleY,this.worldRotation=d.worldRotation+this.rotation):(this.worldX=this.x,this.worldY=this.y,this.worldScaleX=this.scaleX,this.worldScaleY=this.scaleY,this.worldRotation=this.rotation);var e=this.worldRotation*Math.PI/180,f=Math.cos(e),g=Math.sin(e);this.m00=f*this.worldScaleX,this.m10=g*this.worldScaleX,this.m01=-g*this.worldScaleY,this.m11=f*this.worldScaleY,a&&(this.m00=-this.m00,this.m01=-this.m01),b&&(this.m10=-this.m10,this.m11=-this.m11),c.Bone.yDown&&(this.m10=-this.m10,this.m11=-this.m11)},setToSetupPose:function(){var a=this.data;this.x=a.x,this.y=a.y,this.rotation=a.rotation,this.scaleX=a.scaleX,this.scaleY=a.scaleY}},c.Slot=function(a,b,c){this.data=a,this.skeleton=b,this.bone=c,this.setToSetupPose()},c.Slot.prototype={r:1,g:1,b:1,a:1,_attachmentTime:0,attachment:null,setAttachment:function(a){this.attachment=a,this._attachmentTime=this.skeleton.time},setAttachmentTime:function(a){this._attachmentTime=this.skeleton.time-a},getAttachmentTime:function(){return this.skeleton.time-this._attachmentTime},setToSetupPose:function(){var a=this.data;this.r=a.r,this.g=a.g,this.b=a.b,this.a=a.a;for(var b=this.skeleton.data.slots,c=0,d=b.length;d>c;c++)if(b[c]==a){this.setAttachment(a.attachmentName?this.skeleton.getAttachmentBySlotIndex(c,a.attachmentName):null);break}}},c.Skin=function(a){this.name=a,this.attachments={}},c.Skin.prototype={addAttachment:function(a,b,c){this.attachments[a+":"+b]=c},getAttachment:function(a,b){return this.attachments[a+":"+b]},_attachAll:function(a,b){for(var c in b.attachments){var d=c.indexOf(":"),e=parseInt(c.substring(0,d),10),f=c.substring(d+1),g=a.slots[e];if(g.attachment&&g.attachment.name==f){var h=this.getAttachment(e,f);h&&g.setAttachment(h)}}}},c.Animation=function(a,b,c){this.name=a,this.timelines=b,this.duration=c},c.Animation.prototype={apply:function(a,b,c){c&&this.duration&&(b%=this.duration);for(var d=this.timelines,e=0,f=d.length;f>e;e++)d[e].apply(a,b,1)},mix:function(a,b,c,d){c&&this.duration&&(b%=this.duration);for(var e=this.timelines,f=0,g=e.length;g>f;f++)e[f].apply(a,b,d)}},c.binarySearch=function(a,b,c){var d=0,e=Math.floor(a.length/c)-2;if(!e)return c;for(var f=e>>>1;;){if(a[(f+1)*c]<=b?d=f+1:e=f,d==e)return(d+1)*c;f=d+e>>>1}},c.linearSearch=function(a,b,c){for(var d=0,e=a.length-c;e>=d;d+=c)if(a[d]>b)return d;return-1},c.Curves=function(a){this.curves=[],this.curves.length=6*(a-1)},c.Curves.prototype={setLinear:function(a){this.curves[6*a]=0},setStepped:function(a){this.curves[6*a]=-1},setCurve:function(a,b,c,d,e){var f=.1,g=f*f,h=g*f,i=3*f,j=3*g,k=6*g,l=6*h,m=2*-b+d,n=2*-c+e,o=3*(b-d)+1,p=3*(c-e)+1,q=6*a,r=this.curves;r[q]=b*i+m*j+o*h,r[q+1]=c*i+n*j+p*h,r[q+2]=m*k+o*l,r[q+3]=n*k+p*l,r[q+4]=o*l,r[q+5]=p*l},getCurvePercent:function(a,b){b=0>b?0:b>1?1:b;var c=6*a,d=this.curves,e=d[c];if(!e)return b;if(-1==e)return 0;for(var f=d[c+1],g=d[c+2],h=d[c+3],i=d[c+4],j=d[c+5],k=e,l=f,m=8;;){if(k>=b){var n=k-e,o=l-f;return o+(l-o)*(b-n)/(k-n)}if(!m)break;m--,e+=g,f+=h,g+=i,h+=j,k+=e,l+=f}return l+(1-l)*(b-k)/(1-k)}},c.RotateTimeline=function(a){this.curves=new c.Curves(a),this.frames=[],this.frames.length=2*a},c.RotateTimeline.prototype={boneIndex:0,getFrameCount:function(){return this.frames.length/2},setFrame:function(a,b,c){a*=2,this.frames[a]=b,this.frames[a+1]=c},apply:function(a,b,d){var e,f=this.frames;if(!(b=f[f.length-2]){for(e=g.data.rotation+f[f.length-1]-g.rotation;e>180;)e-=360;for(;-180>e;)e+=360;return g.rotation+=e*d,void 0}var h=c.binarySearch(f,b,2),i=f[h-1],j=f[h],k=1-(b-j)/(f[h-2]-j);for(k=this.curves.getCurvePercent(h/2-1,k),e=f[h+1]-i;e>180;)e-=360;for(;-180>e;)e+=360;for(e=g.data.rotation+(i+e*k)-g.rotation;e>180;)e-=360;for(;-180>e;)e+=360;g.rotation+=e*d}}},c.TranslateTimeline=function(a){this.curves=new c.Curves(a),this.frames=[],this.frames.length=3*a},c.TranslateTimeline.prototype={boneIndex:0,getFrameCount:function(){return this.frames.length/3},setFrame:function(a,b,c,d){a*=3,this.frames[a]=b,this.frames[a+1]=c,this.frames[a+2]=d},apply:function(a,b,d){var e=this.frames;if(!(b=e[e.length-3])return f.x+=(f.data.x+e[e.length-2]-f.x)*d,f.y+=(f.data.y+e[e.length-1]-f.y)*d,void 0;var g=c.binarySearch(e,b,3),h=e[g-2],i=e[g-1],j=e[g],k=1-(b-j)/(e[g+-3]-j);k=this.curves.getCurvePercent(g/3-1,k),f.x+=(f.data.x+h+(e[g+1]-h)*k-f.x)*d,f.y+=(f.data.y+i+(e[g+2]-i)*k-f.y)*d}}},c.ScaleTimeline=function(a){this.curves=new c.Curves(a),this.frames=[],this.frames.length=3*a},c.ScaleTimeline.prototype={boneIndex:0,getFrameCount:function(){return this.frames.length/3},setFrame:function(a,b,c,d){a*=3,this.frames[a]=b,this.frames[a+1]=c,this.frames[a+2]=d},apply:function(a,b,d){var e=this.frames;if(!(b=e[e.length-3])return f.scaleX+=(f.data.scaleX-1+e[e.length-2]-f.scaleX)*d,f.scaleY+=(f.data.scaleY-1+e[e.length-1]-f.scaleY)*d,void 0;var g=c.binarySearch(e,b,3),h=e[g-2],i=e[g-1],j=e[g],k=1-(b-j)/(e[g+-3]-j);k=this.curves.getCurvePercent(g/3-1,k),f.scaleX+=(f.data.scaleX-1+h+(e[g+1]-h)*k-f.scaleX)*d,f.scaleY+=(f.data.scaleY-1+i+(e[g+2]-i)*k-f.scaleY)*d}}},c.ColorTimeline=function(a){this.curves=new c.Curves(a),this.frames=[],this.frames.length=5*a},c.ColorTimeline.prototype={slotIndex:0,getFrameCount:function(){return this.frames.length/5},setFrame:function(a,b,c,d,e,f){a*=5,this.frames[a]=b,this.frames[a+1]=c,this.frames[a+2]=d,this.frames[a+3]=e,this.frames[a+4]=f},apply:function(a,b,d){var e=this.frames;if(!(b=e[e.length-5]){var g=e.length-1;return f.r=e[g-3],f.g=e[g-2],f.b=e[g-1],f.a=e[g],void 0}var h=c.binarySearch(e,b,5),i=e[h-4],j=e[h-3],k=e[h-2],l=e[h-1],m=e[h],n=1-(b-m)/(e[h-5]-m);n=this.curves.getCurvePercent(h/5-1,n);var o=i+(e[h+1]-i)*n,p=j+(e[h+2]-j)*n,q=k+(e[h+3]-k)*n,r=l+(e[h+4]-l)*n;1>d?(f.r+=(o-f.r)*d,f.g+=(p-f.g)*d,f.b+=(q-f.b)*d,f.a+=(r-f.a)*d):(f.r=o,f.g=p,f.b=q,f.a=r)}}},c.AttachmentTimeline=function(a){this.curves=new c.Curves(a),this.frames=[],this.frames.length=a,this.attachmentNames=[],this.attachmentNames.length=a},c.AttachmentTimeline.prototype={slotIndex:0,getFrameCount:function(){return this.frames.length},setFrame:function(a,b,c){this.frames[a]=b,this.attachmentNames[a]=c},apply:function(a,b){var d=this.frames;if(!(b=d[d.length-1]?d.length-1:c.binarySearch(d,b,1)-1;var f=this.attachmentNames[e];a.slots[this.slotIndex].setAttachment(f?a.getAttachmentBySlotIndex(this.slotIndex,f):null)}}},c.SkeletonData=function(){this.bones=[],this.slots=[],this.skins=[],this.animations=[]},c.SkeletonData.prototype={defaultSkin:null,findBone:function(a){for(var b=this.bones,c=0,d=b.length;d>c;c++)if(b[c].name==a)return b[c];return null},findBoneIndex:function(a){for(var b=this.bones,c=0,d=b.length;d>c;c++)if(b[c].name==a)return c;return-1},findSlot:function(a){for(var b=this.slots,c=0,d=b.length;d>c;c++)if(b[c].name==a)return slot[c];return null},findSlotIndex:function(a){for(var b=this.slots,c=0,d=b.length;d>c;c++)if(b[c].name==a)return c;return-1},findSkin:function(a){for(var b=this.skins,c=0,d=b.length;d>c;c++)if(b[c].name==a)return b[c];return null},findAnimation:function(a){for(var b=this.animations,c=0,d=b.length;d>c;c++)if(b[c].name==a)return b[c];return null}},c.Skeleton=function(a){this.data=a,this.bones=[];for(var b=0,d=a.bones.length;d>b;b++){var e=a.bones[b],f=e.parent?this.bones[a.bones.indexOf(e.parent)]:null;this.bones.push(new c.Bone(e,f))}for(this.slots=[],this.drawOrder=[],b=0,d=a.slots.length;d>b;b++){var g=a.slots[b],h=this.bones[a.bones.indexOf(g.boneData)],i=new c.Slot(g,this,h);this.slots.push(i),this.drawOrder.push(i)}},c.Skeleton.prototype={x:0,y:0,skin:null,r:1,g:1,b:1,a:1,time:0,flipX:!1,flipY:!1,updateWorldTransform:function(){for(var a=this.flipX,b=this.flipY,c=this.bones,d=0,e=c.length;e>d;d++)c[d].updateWorldTransform(a,b)},setToSetupPose:function(){this.setBonesToSetupPose(),this.setSlotsToSetupPose()},setBonesToSetupPose:function(){for(var a=this.bones,b=0,c=a.length;c>b;b++)a[b].setToSetupPose()},setSlotsToSetupPose:function(){for(var a=this.slots,b=0,c=a.length;c>b;b++)a[b].setToSetupPose(b)},getRootBone:function(){return this.bones.length?this.bones[0]:null},findBone:function(a){for(var b=this.bones,c=0,d=b.length;d>c;c++)if(b[c].data.name==a)return b[c];return null},findBoneIndex:function(a){for(var b=this.bones,c=0,d=b.length;d>c;c++)if(b[c].data.name==a)return c;return-1},findSlot:function(a){for(var b=this.slots,c=0,d=b.length;d>c;c++)if(b[c].data.name==a)return b[c];return null},findSlotIndex:function(a){for(var b=this.slots,c=0,d=b.length;d>c;c++)if(b[c].data.name==a)return c;return-1},setSkinByName:function(a){var b=this.data.findSkin(a);if(!b)throw"Skin not found: "+a;this.setSkin(b)},setSkin:function(a){this.skin&&a&&a._attachAll(this,this.skin),this.skin=a},getAttachmentBySlotName:function(a,b){return this.getAttachmentBySlotIndex(this.data.findSlotIndex(a),b)},getAttachmentBySlotIndex:function(a,b){if(this.skin){var c=this.skin.getAttachment(a,b);if(c)return c}return this.data.defaultSkin?this.data.defaultSkin.getAttachment(a,b):null},setAttachment:function(a,b){for(var c=this.slots,d=0,e=c.size;e>d;d++){var f=c[d];if(f.data.name==a){var g=null;if(b&&(g=this.getAttachment(d,b),null==g))throw"Attachment not found: "+b+", for slot: "+a;return f.setAttachment(g),void 0}}throw"Slot not found: "+a},update:function(a){time+=a}},c.AttachmentType={region:0},c.RegionAttachment=function(){this.offset=[],this.offset.length=8,this.uvs=[],this.uvs.length=8},c.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(a,b,c,d,e){var f=this.uvs;e?(f[2]=a,f[3]=d,f[4]=a,f[5]=b,f[6]=c,f[7]=b,f[0]=c,f[1]=d):(f[0]=a,f[1]=d,f[2]=a,f[3]=b,f[4]=c,f[5]=b,f[6]=c,f[7]=d)},updateOffset:function(){var a=this.width/this.regionOriginalWidth*this.scaleX,b=this.height/this.regionOriginalHeight*this.scaleY,c=-this.width/2*this.scaleX+this.regionOffsetX*a,d=-this.height/2*this.scaleY+this.regionOffsetY*b,e=c+this.regionWidth*a,f=d+this.regionHeight*b,g=this.rotation*Math.PI/180,h=Math.cos(g),i=Math.sin(g),j=c*h+this.x,k=c*i,l=d*h+this.y,m=d*i,n=e*h+this.x,o=e*i,p=f*h+this.y,q=f*i,r=this.offset;r[0]=j-m,r[1]=l+k,r[2]=j-q,r[3]=p+k,r[4]=n-q,r[5]=p+o,r[6]=n-m,r[7]=l+o},computeVertices:function(a,b,c,d){a+=c.worldX,b+=c.worldY;var e=c.m00,f=c.m01,g=c.m10,h=c.m11,i=this.offset;d[0]=i[0]*e+i[1]*f+a,d[1]=i[0]*g+i[1]*h+b,d[2]=i[2]*e+i[3]*f+a,d[3]=i[2]*g+i[3]*h+b,d[4]=i[4]*e+i[5]*f+a,d[5]=i[4]*g+i[5]*h+b,d[6]=i[6]*e+i[7]*f+a,d[7]=i[6]*g+i[7]*h+b}},c.AnimationStateData=function(a){this.skeletonData=a,this.animationToMixTime={}},c.AnimationStateData.prototype={defaultMix:0,setMixByName:function(a,b,c){var d=this.skeletonData.findAnimation(a);if(!d)throw"Animation not found: "+a;var e=this.skeletonData.findAnimation(b);if(!e)throw"Animation not found: "+b;this.setMix(d,e,c)},setMix:function(a,b,c){this.animationToMixTime[a.name+":"+b.name]=c},getMix:function(a,b){var c=this.animationToMixTime[a.name+":"+b.name];return c?c:this.defaultMix}},c.AnimationState=function(a){this.data=a,this.queue=[]},c.AnimationState.prototype={animationSpeed:1,current:null,previous:null,currentTime:0,previousTime:0,currentLoop:!1,previousLoop:!1,mixTime:0,mixDuration:0,update:function(a){if(this.currentTime+=a*this.animationSpeed,this.previousTime+=a,this.mixTime+=a,this.queue.length>0){var b=this.queue[0];this.currentTime>=b.delay&&(this._setAnimation(b.animation,b.loop),this.queue.shift())}},apply:function(a){if(this.current)if(this.previous){this.previous.apply(a,this.previousTime,this.previousLoop);var b=this.mixTime/this.mixDuration;b>=1&&(b=1,this.previous=null),this.current.mix(a,this.currentTime,this.currentLoop,b)}else this.current.apply(a,this.currentTime,this.currentLoop)},clearAnimation:function(){this.previous=null,this.current=null,this.queue.length=0},_setAnimation:function(a,b){this.previous=null,a&&this.current&&(this.mixDuration=this.data.getMix(this.current,a),this.mixDuration>0&&(this.mixTime=0,this.previous=this.current,this.previousTime=this.currentTime,this.previousLoop=this.currentLoop)),this.current=a,this.currentLoop=b,this.currentTime=0},setAnimationByName:function(a,b){var c=this.data.skeletonData.findAnimation(a);if(!c)throw"Animation not found: "+a;this.setAnimation(c,b)},setAnimation:function(a,b){this.queue.length=0,this._setAnimation(a,b)},addAnimationByName:function(a,b,c){var d=this.data.skeletonData.findAnimation(a);if(!d)throw"Animation not found: "+a;this.addAnimation(d,b,c)},addAnimation:function(a,b,c){var d={};if(d.animation=a,d.loop=b,!c||0>=c){var e=this.queue.length?this.queue[this.queue.length-1].animation:this.current;c=null!=e?e.duration-this.data.getMix(e,a)+(c||0):0}d.delay=c,this.queue.push(d)},isComplete:function(){return!this.current||this.currentTime>=this.current.duration}},c.SkeletonJson=function(a){this.attachmentLoader=a},c.SkeletonJson.prototype={scale:1,readSkeletonData:function(a){for(var b,d=new c.SkeletonData,e=a.bones,f=0,g=e.length;g>f;f++){var h=e[f],i=null;if(h.parent&&(i=d.findBone(h.parent),!i))throw"Parent bone not found: "+h.parent;b=new c.BoneData(h.name,i),b.length=(h.length||0)*this.scale,b.x=(h.x||0)*this.scale,b.y=(h.y||0)*this.scale,b.rotation=h.rotation||0,b.scaleX=h.scaleX||1,b.scaleY=h.scaleY||1,d.bones.push(b)}var j=a.slots;for(f=0,g=j.length;g>f;f++){var k=j[f];if(b=d.findBone(k.bone),!b)throw"Slot bone not found: "+k.bone;var l=new c.SlotData(k.name,b),m=k.color;m&&(l.r=c.SkeletonJson.toColor(m,0),l.g=c.SkeletonJson.toColor(m,1),l.b=c.SkeletonJson.toColor(m,2),l.a=c.SkeletonJson.toColor(m,3)),l.attachmentName=k.attachment,d.slots.push(l)}var n=a.skins;for(var o in n)if(n.hasOwnProperty(o)){var p=n[o],q=new c.Skin(o);for(var r in p)if(p.hasOwnProperty(r)){var s=d.findSlotIndex(r),t=p[r];for(var u in t)if(t.hasOwnProperty(u)){var v=this.readAttachment(q,u,t[u]);null!=v&&q.addAttachment(s,u,v)}}d.skins.push(q),"default"==q.name&&(d.defaultSkin=q)}var w=a.animations;for(var x in w)w.hasOwnProperty(x)&&this.readAnimation(x,w[x],d);return d},readAttachment:function(a,b,d){b=d.name||b;var e=c.AttachmentType[d.type||"region"];if(e==c.AttachmentType.region){var f=new c.RegionAttachment;return f.x=(d.x||0)*this.scale,f.y=(d.y||0)*this.scale,f.scaleX=d.scaleX||1,f.scaleY=d.scaleY||1,f.rotation=d.rotation||0,f.width=(d.width||32)*this.scale,f.height=(d.height||32)*this.scale,f.updateOffset(),f.rendererObject={},f.rendererObject.name=b,f.rendererObject.scale={},f.rendererObject.scale.x=f.scaleX,f.rendererObject.scale.y=f.scaleY,f.rendererObject.rotation=-f.rotation*Math.PI/180,f}throw"Unknown attachment type: "+e},readAnimation:function(a,b,d){var e,f,g,h,i,j,k,l=[],m=0,n=b.bones;for(var o in n)if(n.hasOwnProperty(o)){var p=d.findBoneIndex(o);if(-1==p)throw"Bone not found: "+o;var q=n[o];for(g in q)if(q.hasOwnProperty(g))if(i=q[g],"rotate"==g){for(f=new c.RotateTimeline(i.length),f.boneIndex=p,e=0,j=0,k=i.length;k>j;j++)h=i[j],f.setFrame(e,h.time,h.angle),c.SkeletonJson.readCurve(f,e,h),e++;l.push(f),m=Math.max(m,f.frames[2*f.getFrameCount()-2])}else{if("translate"!=g&&"scale"!=g)throw"Invalid timeline type for a bone: "+g+" ("+o+")";var r=1;for("scale"==g?f=new c.ScaleTimeline(i.length):(f=new c.TranslateTimeline(i.length),r=this.scale),f.boneIndex=p,e=0,j=0,k=i.length;k>j;j++){h=i[j];var s=(h.x||0)*r,t=(h.y||0)*r;f.setFrame(e,h.time,s,t),c.SkeletonJson.readCurve(f,e,h),e++}l.push(f),m=Math.max(m,f.frames[3*f.getFrameCount()-3])}}var u=b.slots;for(var v in u)if(u.hasOwnProperty(v)){var w=u[v],x=d.findSlotIndex(v);for(g in w)if(w.hasOwnProperty(g))if(i=w[g],"color"==g){for(f=new c.ColorTimeline(i.length),f.slotIndex=x,e=0,j=0,k=i.length;k>j;j++){h=i[j];var y=h.color,z=c.SkeletonJson.toColor(y,0),A=c.SkeletonJson.toColor(y,1),B=c.SkeletonJson.toColor(y,2),C=c.SkeletonJson.toColor(y,3);f.setFrame(e,h.time,z,A,B,C),c.SkeletonJson.readCurve(f,e,h),e++}l.push(f),m=Math.max(m,f.frames[5*f.getFrameCount()-5])}else{if("attachment"!=g)throw"Invalid timeline type for a slot: "+g+" ("+v+")";for(f=new c.AttachmentTimeline(i.length),f.slotIndex=x,e=0,j=0,k=i.length;k>j;j++)h=i[j],f.setFrame(e++,h.time,h.name);l.push(f),m=Math.max(m,f.frames[f.getFrameCount()-1])}}d.animations.push(new c.Animation(a,l,m))}},c.SkeletonJson.readCurve=function(a,b,c){var d=c.curve;d&&("stepped"==d?a.curves.setStepped(b):d instanceof Array&&a.curves.setCurve(b,d[0],d[1],d[2],d[3]))},c.SkeletonJson.toColor=function(a,b){if(8!=a.length)throw"Color hexidecimal length must be 8, recieved: "+a;return parseInt(a.substr(2*b,2),16)/255},c.Atlas=function(a,b){this.textureLoader=b,this.pages=[],this.regions=[];var d=new c.AtlasReader(a),e=[];e.length=4;for(var f=null;;){var g=d.readLine();if(null==g)break;if(g=d.trim(g),g.length)if(f){var h=new c.AtlasRegion;h.name=g,h.page=f,h.rotate="true"==d.readValue(),d.readTuple(e);var i=parseInt(e[0],10),j=parseInt(e[1],10);d.readTuple(e);var k=parseInt(e[0],10),l=parseInt(e[1],10);h.u=i/f.width,h.v=j/f.height,h.rotate?(h.u2=(i+l)/f.width,h.v2=(j+k)/f.height):(h.u2=(i+k)/f.width,h.v2=(j+l)/f.height),h.x=i,h.y=j,h.width=Math.abs(k),h.height=Math.abs(l),4==d.readTuple(e)&&(h.splits=[parseInt(e[0],10),parseInt(e[1],10),parseInt(e[2],10),parseInt(e[3],10)],4==d.readTuple(e)&&(h.pads=[parseInt(e[0],10),parseInt(e[1],10),parseInt(e[2],10),parseInt(e[3],10)],d.readTuple(e))),h.originalWidth=parseInt(e[0],10),h.originalHeight=parseInt(e[1],10),d.readTuple(e),h.offsetX=parseInt(e[0],10),h.offsetY=parseInt(e[1],10),h.index=parseInt(d.readValue(),10),this.regions.push(h)}else{f=new c.AtlasPage,f.name=g,f.format=c.Atlas.Format[d.readValue()],d.readTuple(e),f.minFilter=c.Atlas.TextureFilter[e[0]],f.magFilter=c.Atlas.TextureFilter[e[1]];var m=d.readValue();f.uWrap=c.Atlas.TextureWrap.clampToEdge,f.vWrap=c.Atlas.TextureWrap.clampToEdge,"x"==m?f.uWrap=c.Atlas.TextureWrap.repeat:"y"==m?f.vWrap=c.Atlas.TextureWrap.repeat:"xy"==m&&(f.uWrap=f.vWrap=c.Atlas.TextureWrap.repeat),b.load(f,g),this.pages.push(f)}else f=null}},c.Atlas.prototype={findRegion:function(a){for(var b=this.regions,c=0,d=b.length;d>c;c++)if(b[c].name==a)return b[c];return null},dispose:function(){for(var a=this.pages,b=0,c=a.length;c>b;b++)this.textureLoader.unload(a[b].rendererObject)},updateUVs:function(a){for(var b=this.regions,c=0,d=b.length;d>c;c++){var e=b[c];e.page==a&&(e.u=e.x/a.width,e.v=e.y/a.height,e.rotate?(e.u2=(e.x+e.height)/a.width,e.v2=(e.y+e.width)/a.height):(e.u2=(e.x+e.width)/a.width,e.v2=(e.y+e.height)/a.height))}}},c.Atlas.Format={alpha:0,intensity:1,luminanceAlpha:2,rgb565:3,rgba4444:4,rgb888:5,rgba8888:6},c.Atlas.TextureFilter={nearest:0,linear:1,mipMap:2,mipMapNearestNearest:3,mipMapLinearNearest:4,mipMapNearestLinear:5,mipMapLinearLinear:6},c.Atlas.TextureWrap={mirroredRepeat:0,clampToEdge:1,repeat:2},c.AtlasPage=function(){},c.AtlasPage.prototype={name:null,format:null,minFilter:null,magFilter:null,uWrap:null,vWrap:null,rendererObject:null,width:0,height:0},c.AtlasRegion=function(){},c.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:!1,splits:null,pads:null},c.AtlasReader=function(a){this.lines=a.split(/\r\n|\r|\n/)},c.AtlasReader.prototype={index:0,trim:function(a){return a.replace(/^\s+|\s+$/g,"")},readLine:function(){return this.index>=this.lines.length?null:this.lines[this.index++]},readValue:function(){var a=this.readLine(),b=a.indexOf(":");if(-1==b)throw"Invalid line: "+a;return this.trim(a.substring(b+1))},readTuple:function(a){var b=this.readLine(),c=b.indexOf(":");if(-1==c)throw"Invalid line: "+b;for(var d=0,e=c+1;3>d;d++){var f=b.indexOf(",",e);if(-1==f){if(!d)throw"Invalid line: "+b;break}a[d]=this.trim(b.substr(e,f-e)),e=f+1}return a[d]=this.trim(b.substring(e)),d+1}},c.AtlasAttachmentLoader=function(a){this.atlas=a},c.AtlasAttachmentLoader.prototype={newAttachment:function(a,b,d){switch(b){case c.AttachmentType.region:var e=this.atlas.findRegion(d);if(!e)throw"Region not found in atlas: "+d+" ("+b+")";var f=new c.RegionAttachment(d);return f.rendererObject=e,f.setUVs(e.u,e.v,e.u2,e.v2,e.rotate),f.regionOffsetX=e.offsetX,f.regionOffsetY=e.offsetY,f.regionWidth=e.width,f.regionHeight=e.height,f.regionOriginalWidth=e.originalWidth,f.regionOriginalHeight=e.originalHeight,f}throw"Unknown attachment type: "+b}},c.Bone.yDown=!0,b.AnimCache={},b.Spine=function(a){if(b.DisplayObjectContainer.call(this),this.spineData=b.AnimCache[a],!this.spineData)throw new Error("Spine data must be preloaded using PIXI.SpineLoader or PIXI.AssetLoader: "+a);this.skeleton=new c.Skeleton(this.spineData),this.skeleton.updateWorldTransform(),this.stateData=new c.AnimationStateData(this.spineData),this.state=new c.AnimationState(this.stateData),this.slotContainers=[];for(var d=0,e=this.skeleton.drawOrder.length;e>d;d++){var f=this.skeleton.drawOrder[d],g=f.attachment,h=new b.DisplayObjectContainer;if(this.slotContainers.push(h),this.addChild(h),g instanceof c.RegionAttachment){var i=g.rendererObject.name,j=this.createSprite(f,g.rendererObject);f.currentSprite=j,f.currentSpriteName=i,h.addChild(j)}}},b.Spine.prototype=Object.create(b.DisplayObjectContainer.prototype),b.Spine.prototype.constructor=b.Spine,b.Spine.prototype.updateTransform=function(){this.lastTime=this.lastTime||Date.now();var a=.001*(Date.now()-this.lastTime);this.lastTime=Date.now(),this.state.update(a),this.state.apply(this.skeleton),this.skeleton.updateWorldTransform();for(var d=this.skeleton.drawOrder,e=0,f=d.length;f>e;e++){var g=d[e],h=g.attachment,i=this.slotContainers[e];if(h instanceof c.RegionAttachment){if(h.rendererObject&&(!g.currentSpriteName||g.currentSpriteName!=h.name)){var j=h.rendererObject.name;if(void 0!==g.currentSprite&&(g.currentSprite.visible=!1),g.sprites=g.sprites||{},void 0!==g.sprites[j])g.sprites[j].visible=!0;else{var k=this.createSprite(g,h.rendererObject);i.addChild(k)}g.currentSprite=g.sprites[j],g.currentSpriteName=j}i.visible=!0;var l=g.bone;i.position.x=l.worldX+h.x*l.m00+h.y*l.m01,i.position.y=l.worldY+h.x*l.m10+h.y*l.m11,i.scale.x=l.worldScaleX,i.scale.y=l.worldScaleY,i.rotation=-(g.bone.worldRotation*Math.PI/180),i.alpha=g.a,g.currentSprite.tint=b.rgb2hex([g.r,g.g,g.b])}else i.visible=!1}b.DisplayObjectContainer.prototype.updateTransform.call(this)},b.Spine.prototype.createSprite=function(a,c){var d=b.TextureCache[c.name]?c.name:c.name+".png",e=new b.Sprite(b.Texture.fromFrame(d));return e.scale=c.scale,e.rotation=c.rotation,e.anchor.x=e.anchor.y=.5,a.sprites=a.sprites||{},a.sprites[c.name]=e,e},b.BaseTextureCache={},b.BaseTextureCacheIdGenerator=0,b.BaseTexture=function(a,c){if(this.resolution=1,this.width=100,this.height=100,this.scaleMode=c||b.scaleModes.DEFAULT,this.hasLoaded=!1,this.source=a,this._UID=b._UID++,this.premultipliedAlpha=!0,this._glTextures=[],this._dirty=[!0,!0,!0,!0],a){if((this.source.complete||this.source.getContext)&&this.source.width&&this.source.height)this.hasLoaded=!0,this.width=this.source.naturalWidth||this.source.width,this.height=this.source.naturalHeight||this.source.height,this.dirty();else{var d=this;this.source.onload=function(){d.hasLoaded=!0,d.width=d.source.naturalWidth||d.source.width,d.height=d.source.naturalHeight||d.source.height,d.dirty(),d.dispatchEvent({type:"loaded",content:d})},this.source.onerror=function(){d.dispatchEvent({type:"error",content:d})}}this.imageUrl=null,this._powerOf2=!1}},b.BaseTexture.prototype.constructor=b.BaseTexture,b.EventTarget.mixin(b.BaseTexture.prototype),b.BaseTexture.prototype.destroy=function(){this.imageUrl?(delete b.BaseTextureCache[this.imageUrl],delete b.TextureCache[this.imageUrl],this.imageUrl=null,navigator.isCocoonJS||(this.source.src="")):this.source&&this.source._pixiId&&delete b.BaseTextureCache[this.source._pixiId],this.source=null,this.unloadFromGPU()},b.BaseTexture.prototype.updateSourceImage=function(a){this.hasLoaded=!1,this.source.src=null,this.source.src=a},b.BaseTexture.prototype.dirty=function(){for(var a=0;a=0;a--){var c=this._glTextures[a],d=b.glContexts[a];d&&c&&d.deleteTexture(c)}this._glTextures.length=0,this.dirty()},b.BaseTexture.fromImage=function(a,c,d){var e=b.BaseTextureCache[a];if(void 0===c&&-1===a.indexOf("data:")&&(c=!0),!e){var f=new Image;c&&(f.crossOrigin=""),f.src=a,e=new b.BaseTexture(f,d),e.imageUrl=a,b.BaseTextureCache[a]=e,-1!==a.indexOf(b.RETINA_PREFIX+".")&&(e.resolution=2)}return e},b.BaseTexture.fromCanvas=function(a,c){a._pixiId||(a._pixiId="canvas_"+b.TextureCacheIdGenerator++);var d=b.BaseTextureCache[a._pixiId];return d||(d=new b.BaseTexture(a,c),b.BaseTextureCache[a._pixiId]=d),d},b.TextureCache={},b.FrameCache={},b.TextureCacheIdGenerator=0,b.Texture=function(a,c,d,e){this.noFrame=!1,c||(this.noFrame=!0,c=new b.Rectangle(0,0,1,1)),a instanceof b.Texture&&(a=a.baseTexture),this.baseTexture=a,this.frame=c,this.trim=e,this.valid=!1,this.requiresUpdate=!1,this._uvs=null,this.width=0,this.height=0,this.crop=d||new b.Rectangle(0,0,1,1),a.hasLoaded?(this.noFrame&&(c=new b.Rectangle(0,0,a.width,a.height)),this.setFrame(c)):a.addEventListener("loaded",this.onBaseTextureLoaded.bind(this))},b.Texture.prototype.constructor=b.Texture,b.EventTarget.mixin(b.Texture.prototype),b.Texture.prototype.onBaseTextureLoaded=function(){var a=this.baseTexture;a.removeEventListener("loaded",this.onLoaded),this.noFrame&&(this.frame=new b.Rectangle(0,0,a.width,a.height)),this.setFrame(this.frame),this.dispatchEvent({type:"update",content:this})},b.Texture.prototype.destroy=function(a){a&&this.baseTexture.destroy(),this.valid=!1},b.Texture.prototype.setFrame=function(a){if(this.noFrame=!1,this.frame=a,this.width=a.width,this.height=a.height,this.crop.x=a.x,this.crop.y=a.y,this.crop.width=a.width,this.crop.height=a.height,!this.trim&&(a.x+a.width>this.baseTexture.width||a.y+a.height>this.baseTexture.height))throw new Error("Texture Error: frame does not fit inside the base Texture dimensions "+this);this.valid=a&&a.width&&a.height&&this.baseTexture.source&&this.baseTexture.hasLoaded,this.trim&&(this.width=this.trim.width,this.height=this.trim.height,this.frame.width=this.trim.width,this.frame.height=this.trim.height),this.valid&&this._updateUvs()},b.Texture.prototype._updateUvs=function(){this._uvs||(this._uvs=new b.TextureUvs);var a=this.crop,c=this.baseTexture.width,d=this.baseTexture.height;this._uvs.x0=a.x/c,this._uvs.y0=a.y/d,this._uvs.x1=(a.x+a.width)/c,this._uvs.y1=a.y/d,this._uvs.x2=(a.x+a.width)/c,this._uvs.y2=(a.y+a.height)/d,this._uvs.x3=a.x/c,this._uvs.y3=(a.y+a.height)/d},b.Texture.fromImage=function(a,c,d){var e=b.TextureCache[a];return e||(e=new b.Texture(b.BaseTexture.fromImage(a,c,d)),b.TextureCache[a]=e),e},b.Texture.fromFrame=function(a){var c=b.TextureCache[a];if(!c)throw new Error('The frameId "'+a+'" does not exist in the texture cache ');return c},b.Texture.fromCanvas=function(a,c){var d=b.BaseTexture.fromCanvas(a,c);return new b.Texture(d)},b.Texture.addTextureToCache=function(a,c){b.TextureCache[c]=a},b.Texture.removeTextureFromCache=function(a){var c=b.TextureCache[a];return delete b.TextureCache[a],delete b.BaseTextureCache[a],c},b.TextureUvs=function(){this.x0=0,this.y0=0,this.x1=0,this.y1=0,this.x2=0,this.y2=0,this.x3=0,this.y3=0},b.Texture.emptyTexture=new b.Texture(new b.BaseTexture),b.RenderTexture=function(a,c,d,e,f){if(this.width=a||100,this.height=c||100,this.resolution=f||1,this.frame=new b.Rectangle(0,0,this.width*this.resolution,this.height*this.resolution),this.crop=new b.Rectangle(0,0,this.width*this.resolution,this.height*this.resolution),this.baseTexture=new b.BaseTexture,this.baseTexture.width=this.width*this.resolution,this.baseTexture.height=this.height*this.resolution,this.baseTexture._glTextures=[],this.baseTexture.resolution=this.resolution,this.baseTexture.scaleMode=e||b.scaleModes.DEFAULT,this.baseTexture.hasLoaded=!0,b.Texture.call(this,this.baseTexture,new b.Rectangle(0,0,this.width,this.height)),this.renderer=d||b.defaultRenderer,this.renderer.type===b.WEBGL_RENDERER){var g=this.renderer.gl;this.baseTexture._dirty[g.id]=!1,this.textureBuffer=new b.FilterTexture(g,this.width*this.resolution,this.height*this.resolution,this.baseTexture.scaleMode),this.baseTexture._glTextures[g.id]=this.textureBuffer.texture,this.render=this.renderWebGL,this.projection=new b.Point(.5*this.width,.5*-this.height)}else this.render=this.renderCanvas,this.textureBuffer=new b.CanvasBuffer(this.width*this.resolution,this.height*this.resolution),this.baseTexture.source=this.textureBuffer.canvas;this.valid=!0,this._updateUvs()},b.RenderTexture.prototype=Object.create(b.Texture.prototype),b.RenderTexture.prototype.constructor=b.RenderTexture,b.RenderTexture.prototype.resize=function(a,c,d){(a!==this.width||c!==this.height)&&(this.valid=a>0&&c>0,this.width=this.frame.width=this.crop.width=a,this.height=this.frame.height=this.crop.height=c,d&&(this.baseTexture.width=this.width,this.baseTexture.height=this.height),this.renderer.type===b.WEBGL_RENDERER&&(this.projection.x=this.width/2,this.projection.y=-this.height/2),this.valid&&this.textureBuffer.resize(this.width*this.resolution,this.height*this.resolution))},b.RenderTexture.prototype.clear=function(){this.valid&&(this.renderer.type===b.WEBGL_RENDERER&&this.renderer.gl.bindFramebuffer(this.renderer.gl.FRAMEBUFFER,this.textureBuffer.frameBuffer),this.textureBuffer.clear())},b.RenderTexture.prototype.renderWebGL=function(a,b,c){if(this.valid){var d=a.worldTransform;d.identity(),d.translate(0,2*this.projection.y),b&&d.append(b),d.scale(1,-1),a.worldAlpha=1;for(var e=a.children,f=0,g=e.length;g>f;f++)e[f].updateTransform();var h=this.renderer.gl;h.viewport(0,0,this.width*this.resolution,this.height*this.resolution),h.bindFramebuffer(h.FRAMEBUFFER,this.textureBuffer.frameBuffer),c&&this.textureBuffer.clear(),this.renderer.spriteBatch.dirty=!0,this.renderer.renderDisplayObject(a,this.projection,this.textureBuffer.frameBuffer),this.renderer.spriteBatch.dirty=!0}},b.RenderTexture.prototype.renderCanvas=function(a,b,c){if(this.valid){var d=a.worldTransform;d.identity(),b&&d.append(b),a.worldAlpha=1;for(var e=a.children,f=0,g=e.length;g>f;f++)e[f].updateTransform();c&&this.textureBuffer.clear();var h=this.textureBuffer.context,i=this.renderer.resolution;this.renderer.resolution=this.resolution,this.renderer.renderDisplayObject(a,h),this.renderer.resolution=i}},b.RenderTexture.prototype.getImage=function(){var a=new Image;return a.src=this.getBase64(),a},b.RenderTexture.prototype.getBase64=function(){return this.getCanvas().toDataURL()},b.RenderTexture.prototype.getCanvas=function(){if(this.renderer.type===b.WEBGL_RENDERER){var a=this.renderer.gl,c=this.textureBuffer.width,d=this.textureBuffer.height,e=new Uint8Array(4*c*d);a.bindFramebuffer(a.FRAMEBUFFER,this.textureBuffer.frameBuffer),a.readPixels(0,0,c,d,a.RGBA,a.UNSIGNED_BYTE,e),a.bindFramebuffer(a.FRAMEBUFFER,null);var f=new b.CanvasBuffer(c,d),g=f.context.getImageData(0,0,c,d);return g.data.set(e),f.context.putImageData(g,0,0),f.canvas}return this.textureBuffer.canvas},b.RenderTexture.tempMatrix=new b.Matrix,b.VideoTexture=function(a,c){if(!a)throw new Error("No video source element specified.");(a.readyState===a.HAVE_ENOUGH_DATA||a.readyState===a.HAVE_FUTURE_DATA)&&a.width&&a.height&&(a.complete=!0),b.BaseTexture.call(this,a,c),this.autoUpdate=!1,this.updateBound=this._onUpdate.bind(this),a.complete||(this._onCanPlay=this.onCanPlay.bind(this),a.addEventListener("canplay",this._onCanPlay),a.addEventListener("canplaythrough",this._onCanPlay),a.addEventListener("play",this.onPlayStart.bind(this)),a.addEventListener("pause",this.onPlayStop.bind(this))) +},b.VideoTexture.prototype=Object.create(b.BaseTexture.prototype),b.VideoTexture.constructor=b.VideoTexture,b.VideoTexture.prototype._onUpdate=function(){this.autoUpdate&&(window.requestAnimationFrame(this.updateBound),this.dirty())},b.VideoTexture.prototype.onPlayStart=function(){this.autoUpdate||(window.requestAnimationFrame(this.updateBound),this.autoUpdate=!0)},b.VideoTexture.prototype.onPlayStop=function(){this.autoUpdate=!1},b.VideoTexture.prototype.onCanPlay=function(){"canplaythrough"===event.type&&(this.hasLoaded=!0,this.source&&(this.source.removeEventListener("canplay",this._onCanPlay),this.source.removeEventListener("canplaythrough",this._onCanPlay),this.width=this.source.videoWidth,this.height=this.source.videoHeight,this.__loaded||(this.__loaded=!0,this.dispatchEvent({type:"loaded",content:this}))))},b.VideoTexture.baseTextureFromVideo=function(a,c){a._pixiId||(a._pixiId="video_"+b.TextureCacheIdGenerator++);var d=b.BaseTextureCache[a._pixiId];return d||(d=new b.VideoTexture(a,c),b.BaseTextureCache[a._pixiId]=d),d},b.VideoTexture.prototype.destroy=function(){this.source&&this.source._pixiId&&(b.BaseTextureCache[this.source._pixiId]=null,delete b.BaseTextureCache[this.source._pixiId],this.source._pixiId=null,delete this.source._pixiId),b.BaseTexture.prototype.destroy.call(this)},b.VideoTexture.textureFromVideo=function(a,c){var d=b.VideoTexture.baseTextureFromVideo(a,c);return new b.Texture(d)},b.VideoTexture.fromUrl=function(a,c){var d=document.createElement("video");return d.src=a,d.autoPlay=!0,d.play(),b.VideoTexture.textureFromVideo(d,c)},b.AssetLoader=function(a,c){this.assetURLs=a,this.crossorigin=c,this.loadersByType={jpg:b.ImageLoader,jpeg:b.ImageLoader,png:b.ImageLoader,gif:b.ImageLoader,webp:b.ImageLoader,json:b.JsonLoader,atlas:b.AtlasLoader,anim:b.SpineLoader,xml:b.BitmapFontLoader,fnt:b.BitmapFontLoader}},b.EventTarget.mixin(b.AssetLoader.prototype),b.AssetLoader.prototype.constructor=b.AssetLoader,b.AssetLoader.prototype._getDataType=function(a){var b="data:",c=a.slice(0,b.length).toLowerCase();if(c===b){var d=a.slice(b.length),e=d.indexOf(",");if(-1===e)return null;var f=d.slice(0,e).split(";")[0];return f&&"text/plain"!==f.toLowerCase()?f.split("/").pop().toLowerCase():"txt"}return null},b.AssetLoader.prototype.load=function(){function a(a){b.onAssetLoaded(a.data.content)}var b=this;this.loadCount=this.assetURLs.length;for(var c=0;c0){if(f===g)this.atlas.meta.image.push(a[g]),d=this.atlas.meta.image.length-1,this.atlas.frames.push({}),c=-3;else if(c>0)if(c%7===1)null!=e&&(this.atlas.frames[d][e.name]=e),e={name:a[g],frame:{}};else{var j=a[g].split(" ");if(c%7===3)e.frame.x=Number(j[1].replace(",","")),e.frame.y=Number(j[2]);else if(c%7===4)e.frame.w=Number(j[1].replace(",","")),e.frame.h=Number(j[2]);else if(c%7===5){var k={x:0,y:0,w:Number(j[1].replace(",","")),h:Number(j[2])};k.w>e.frame.w||k.h>e.frame.h?(e.trimmed=!0,e.realSize=k):e.trimmed=!1}}c++}if(null!=e&&(this.atlas.frames[d][e.name]=e),this.atlas.meta.image.length>0){for(this.images=[],h=0;hthis.currentImageId?(this.currentImageId++,this.images[this.currentImageId].load()):(this.loaded=!0,this.emit("loaded",{content:this}))},b.AtlasLoader.prototype.onError=function(){this.emit("error",{content:this})},b.SpriteSheetLoader=function(a,b){this.url=a,this.crossorigin=b,this.baseUrl=a.replace(/[^\/]*$/,""),this.texture=null,this.frames={}},b.SpriteSheetLoader.prototype.constructor=b.SpriteSheetLoader,b.EventTarget.mixin(b.SpriteSheetLoader.prototype),b.SpriteSheetLoader.prototype.load=function(){var a=this,c=new b.JsonLoader(this.url,this.crossorigin);c.on("loaded",function(b){a.json=b.data.content.json,a.onLoaded()}),c.load()},b.SpriteSheetLoader.prototype.onLoaded=function(){this.emit("loaded",{content:this})},b.ImageLoader=function(a,c){this.texture=b.Texture.fromImage(a,c),this.frames=[]},b.ImageLoader.prototype.constructor=b.ImageLoader,b.EventTarget.mixin(b.ImageLoader.prototype),b.ImageLoader.prototype.load=function(){this.texture.baseTexture.hasLoaded?this.onLoaded():this.texture.baseTexture.on("loaded",this.onLoaded.bind(this))},b.ImageLoader.prototype.onLoaded=function(){this.emit("loaded",{content:this})},b.ImageLoader.prototype.loadFramedSpriteSheet=function(a,c,d){this.frames=[];for(var e=Math.floor(this.texture.width/a),f=Math.floor(this.texture.height/c),g=0,h=0;f>h;h++)for(var i=0;e>i;i++,g++){var j=new b.Texture(this.texture.baseTexture,{x:i*a,y:h*c,width:a,height:c});this.frames.push(j),d&&(b.TextureCache[d+"-"+g]=j)}this.load()},b.BitmapFontLoader=function(a,b){this.url=a,this.crossorigin=b,this.baseUrl=a.replace(/[^\/]*$/,""),this.texture=null},b.BitmapFontLoader.prototype.constructor=b.BitmapFontLoader,b.EventTarget.mixin(b.BitmapFontLoader.prototype),b.BitmapFontLoader.prototype.load=function(){this.ajaxRequest=new b.AjaxRequest,this.ajaxRequest.onreadystatechange=this.onXMLLoaded.bind(this),this.ajaxRequest.open("GET",this.url,!0),this.ajaxRequest.overrideMimeType&&this.ajaxRequest.overrideMimeType("application/xml"),this.ajaxRequest.send(null)},b.BitmapFontLoader.prototype.onXMLLoaded=function(){if(4===this.ajaxRequest.readyState&&(200===this.ajaxRequest.status||-1===window.location.protocol.indexOf("http"))){var a=this.ajaxRequest.responseXML;if(!a||/MSIE 9/i.test(navigator.userAgent)||navigator.isCocoonJS)if("function"==typeof window.DOMParser){var c=new DOMParser;a=c.parseFromString(this.ajaxRequest.responseText,"text/xml")}else{var d=document.createElement("div");d.innerHTML=this.ajaxRequest.responseText,a=d}var e=this.baseUrl+a.getElementsByTagName("page")[0].getAttribute("file"),f=new b.ImageLoader(e,this.crossorigin);this.texture=f.texture.baseTexture;var g={},h=a.getElementsByTagName("info")[0],i=a.getElementsByTagName("common")[0];g.font=h.getAttribute("face"),g.size=parseInt(h.getAttribute("size"),10),g.lineHeight=parseInt(i.getAttribute("lineHeight"),10),g.chars={};for(var j=a.getElementsByTagName("char"),k=0;ka;a++)this.shaders[a].dirty=!0},b.AlphaMaskFilter=function(a){b.AbstractFilter.call(this),this.passes=[this],a.baseTexture._powerOf2=!0,this.uniforms={mask:{type:"sampler2D",value:a},mapDimensions:{type:"2f",value:{x:1,y:5112}},dimensions:{type:"4fv",value:[0,0,0,0]}},a.baseTexture.hasLoaded?(this.uniforms.mask.value.x=a.width,this.uniforms.mask.value.y=a.height):(this.boundLoadedFunction=this.onTextureLoaded.bind(this),a.baseTexture.on("loaded",this.boundLoadedFunction)),this.fragmentSrc=["precision mediump float;","varying vec2 vTextureCoord;","varying vec4 vColor;","uniform sampler2D mask;","uniform sampler2D uSampler;","uniform vec2 offset;","uniform vec4 dimensions;","uniform vec2 mapDimensions;","void main(void) {"," vec2 mapCords = vTextureCoord.xy;"," mapCords += (dimensions.zw + offset)/ dimensions.xy ;"," mapCords.y *= -1.0;"," mapCords.y += 1.0;"," mapCords *= dimensions.xy / mapDimensions;"," vec4 original = texture2D(uSampler, vTextureCoord);"," float maskAlpha = texture2D(mask, mapCords).r;"," original *= maskAlpha;"," gl_FragColor = original;","}"]},b.AlphaMaskFilter.prototype=Object.create(b.AbstractFilter.prototype),b.AlphaMaskFilter.prototype.constructor=b.AlphaMaskFilter,b.AlphaMaskFilter.prototype.onTextureLoaded=function(){this.uniforms.mapDimensions.value.x=this.uniforms.mask.value.width,this.uniforms.mapDimensions.value.y=this.uniforms.mask.value.height,this.uniforms.mask.value.baseTexture.off("loaded",this.boundLoadedFunction)},Object.defineProperty(b.AlphaMaskFilter.prototype,"map",{get:function(){return this.uniforms.mask.value},set:function(a){this.uniforms.mask.value=a}}),b.ColorMatrixFilter=function(){b.AbstractFilter.call(this),this.passes=[this],this.uniforms={matrix:{type:"mat4",value:[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]}},this.fragmentSrc=["precision mediump float;","varying vec2 vTextureCoord;","varying vec4 vColor;","uniform float invert;","uniform mat4 matrix;","uniform sampler2D uSampler;","void main(void) {"," gl_FragColor = texture2D(uSampler, vTextureCoord) * matrix;","}"]},b.ColorMatrixFilter.prototype=Object.create(b.AbstractFilter.prototype),b.ColorMatrixFilter.prototype.constructor=b.ColorMatrixFilter,Object.defineProperty(b.ColorMatrixFilter.prototype,"matrix",{get:function(){return this.uniforms.matrix.value},set:function(a){this.uniforms.matrix.value=a}}),b.GrayFilter=function(){b.AbstractFilter.call(this),this.passes=[this],this.uniforms={gray:{type:"1f",value:1}},this.fragmentSrc=["precision mediump float;","varying vec2 vTextureCoord;","varying vec4 vColor;","uniform sampler2D uSampler;","uniform float gray;","void main(void) {"," gl_FragColor = texture2D(uSampler, vTextureCoord);"," gl_FragColor.rgb = mix(gl_FragColor.rgb, vec3(0.2126*gl_FragColor.r + 0.7152*gl_FragColor.g + 0.0722*gl_FragColor.b), gray);","}"]},b.GrayFilter.prototype=Object.create(b.AbstractFilter.prototype),b.GrayFilter.prototype.constructor=b.GrayFilter,Object.defineProperty(b.GrayFilter.prototype,"gray",{get:function(){return this.uniforms.gray.value},set:function(a){this.uniforms.gray.value=a}}),b.DisplacementFilter=function(a){b.AbstractFilter.call(this),this.passes=[this],a.baseTexture._powerOf2=!0,this.uniforms={displacementMap:{type:"sampler2D",value:a},scale:{type:"2f",value:{x:30,y:30}},offset:{type:"2f",value:{x:0,y:0}},mapDimensions:{type:"2f",value:{x:1,y:5112}},dimensions:{type:"4fv",value:[0,0,0,0]}},a.baseTexture.hasLoaded?(this.uniforms.mapDimensions.value.x=a.width,this.uniforms.mapDimensions.value.y=a.height):(this.boundLoadedFunction=this.onTextureLoaded.bind(this),a.baseTexture.on("loaded",this.boundLoadedFunction)),this.fragmentSrc=["precision mediump float;","varying vec2 vTextureCoord;","varying vec4 vColor;","uniform sampler2D displacementMap;","uniform sampler2D uSampler;","uniform vec2 scale;","uniform vec2 offset;","uniform vec4 dimensions;","uniform vec2 mapDimensions;","void main(void) {"," vec2 mapCords = vTextureCoord.xy;"," mapCords += (dimensions.zw + offset)/ dimensions.xy ;"," mapCords.y *= -1.0;"," mapCords.y += 1.0;"," vec2 matSample = texture2D(displacementMap, mapCords).xy;"," matSample -= 0.5;"," matSample *= scale;"," matSample /= mapDimensions;"," gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x + matSample.x, vTextureCoord.y + matSample.y));"," gl_FragColor.rgb = mix( gl_FragColor.rgb, gl_FragColor.rgb, 1.0);"," vec2 cord = vTextureCoord;","}"]},b.DisplacementFilter.prototype=Object.create(b.AbstractFilter.prototype),b.DisplacementFilter.prototype.constructor=b.DisplacementFilter,b.DisplacementFilter.prototype.onTextureLoaded=function(){this.uniforms.mapDimensions.value.x=this.uniforms.displacementMap.value.width,this.uniforms.mapDimensions.value.y=this.uniforms.displacementMap.value.height,this.uniforms.displacementMap.value.baseTexture.off("loaded",this.boundLoadedFunction)},Object.defineProperty(b.DisplacementFilter.prototype,"map",{get:function(){return this.uniforms.displacementMap.value},set:function(a){this.uniforms.displacementMap.value=a}}),Object.defineProperty(b.DisplacementFilter.prototype,"scale",{get:function(){return this.uniforms.scale.value},set:function(a){this.uniforms.scale.value=a}}),Object.defineProperty(b.DisplacementFilter.prototype,"offset",{get:function(){return this.uniforms.offset.value},set:function(a){this.uniforms.offset.value=a}}),b.PixelateFilter=function(){b.AbstractFilter.call(this),this.passes=[this],this.uniforms={invert:{type:"1f",value:0},dimensions:{type:"4fv",value:new b.Float32Array([1e4,100,10,10])},pixelSize:{type:"2f",value:{x:10,y:10}}},this.fragmentSrc=["precision mediump float;","varying vec2 vTextureCoord;","varying vec4 vColor;","uniform vec2 testDim;","uniform vec4 dimensions;","uniform vec2 pixelSize;","uniform sampler2D uSampler;","void main(void) {"," vec2 coord = vTextureCoord;"," vec2 size = dimensions.xy/pixelSize;"," vec2 color = floor( ( vTextureCoord * size ) ) / size + pixelSize/dimensions.xy * 0.5;"," gl_FragColor = texture2D(uSampler, color);","}"]},b.PixelateFilter.prototype=Object.create(b.AbstractFilter.prototype),b.PixelateFilter.prototype.constructor=b.PixelateFilter,Object.defineProperty(b.PixelateFilter.prototype,"size",{get:function(){return this.uniforms.pixelSize.value},set:function(a){this.dirty=!0,this.uniforms.pixelSize.value=a}}),b.BlurXFilter=function(){b.AbstractFilter.call(this),this.passes=[this],this.uniforms={blur:{type:"1f",value:1/512}},this.fragmentSrc=["precision mediump float;","varying vec2 vTextureCoord;","varying vec4 vColor;","uniform float blur;","uniform sampler2D uSampler;","void main(void) {"," vec4 sum = vec4(0.0);"," sum += texture2D(uSampler, vec2(vTextureCoord.x - 4.0*blur, vTextureCoord.y)) * 0.05;"," sum += texture2D(uSampler, vec2(vTextureCoord.x - 3.0*blur, vTextureCoord.y)) * 0.09;"," sum += texture2D(uSampler, vec2(vTextureCoord.x - 2.0*blur, vTextureCoord.y)) * 0.12;"," sum += texture2D(uSampler, vec2(vTextureCoord.x - blur, vTextureCoord.y)) * 0.15;"," sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * 0.16;"," sum += texture2D(uSampler, vec2(vTextureCoord.x + blur, vTextureCoord.y)) * 0.15;"," sum += texture2D(uSampler, vec2(vTextureCoord.x + 2.0*blur, vTextureCoord.y)) * 0.12;"," sum += texture2D(uSampler, vec2(vTextureCoord.x + 3.0*blur, vTextureCoord.y)) * 0.09;"," sum += texture2D(uSampler, vec2(vTextureCoord.x + 4.0*blur, vTextureCoord.y)) * 0.05;"," gl_FragColor = sum;","}"]},b.BlurXFilter.prototype=Object.create(b.AbstractFilter.prototype),b.BlurXFilter.prototype.constructor=b.BlurXFilter,Object.defineProperty(b.BlurXFilter.prototype,"blur",{get:function(){return this.uniforms.blur.value/(1/7e3)},set:function(a){this.dirty=!0,this.uniforms.blur.value=1/7e3*a}}),b.BlurYFilter=function(){b.AbstractFilter.call(this),this.passes=[this],this.uniforms={blur:{type:"1f",value:1/512}},this.fragmentSrc=["precision mediump float;","varying vec2 vTextureCoord;","varying vec4 vColor;","uniform float blur;","uniform sampler2D uSampler;","void main(void) {"," vec4 sum = vec4(0.0);"," sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y - 4.0*blur)) * 0.05;"," sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y - 3.0*blur)) * 0.09;"," sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y - 2.0*blur)) * 0.12;"," sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y - blur)) * 0.15;"," sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * 0.16;"," sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y + blur)) * 0.15;"," sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y + 2.0*blur)) * 0.12;"," sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y + 3.0*blur)) * 0.09;"," sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y + 4.0*blur)) * 0.05;"," gl_FragColor = sum;","}"]},b.BlurYFilter.prototype=Object.create(b.AbstractFilter.prototype),b.BlurYFilter.prototype.constructor=b.BlurYFilter,Object.defineProperty(b.BlurYFilter.prototype,"blur",{get:function(){return this.uniforms.blur.value/(1/7e3)},set:function(a){this.uniforms.blur.value=1/7e3*a}}),b.BlurFilter=function(){this.blurXFilter=new b.BlurXFilter,this.blurYFilter=new b.BlurYFilter,this.passes=[this.blurXFilter,this.blurYFilter]},b.BlurFilter.prototype=Object.create(b.AbstractFilter.prototype),b.BlurFilter.prototype.constructor=b.BlurFilter,Object.defineProperty(b.BlurFilter.prototype,"blur",{get:function(){return this.blurXFilter.blur},set:function(a){this.blurXFilter.blur=this.blurYFilter.blur=a}}),Object.defineProperty(b.BlurFilter.prototype,"blurX",{get:function(){return this.blurXFilter.blur},set:function(a){this.blurXFilter.blur=a}}),Object.defineProperty(b.BlurFilter.prototype,"blurY",{get:function(){return this.blurYFilter.blur},set:function(a){this.blurYFilter.blur=a}}),b.InvertFilter=function(){b.AbstractFilter.call(this),this.passes=[this],this.uniforms={invert:{type:"1f",value:1}},this.fragmentSrc=["precision mediump float;","varying vec2 vTextureCoord;","varying vec4 vColor;","uniform float invert;","uniform sampler2D uSampler;","void main(void) {"," gl_FragColor = texture2D(uSampler, vTextureCoord);"," gl_FragColor.rgb = mix( (vec3(1)-gl_FragColor.rgb) * gl_FragColor.a, gl_FragColor.rgb, 1.0 - invert);","}"]},b.InvertFilter.prototype=Object.create(b.AbstractFilter.prototype),b.InvertFilter.prototype.constructor=b.InvertFilter,Object.defineProperty(b.InvertFilter.prototype,"invert",{get:function(){return this.uniforms.invert.value},set:function(a){this.uniforms.invert.value=a}}),b.SepiaFilter=function(){b.AbstractFilter.call(this),this.passes=[this],this.uniforms={sepia:{type:"1f",value:1}},this.fragmentSrc=["precision mediump float;","varying vec2 vTextureCoord;","varying vec4 vColor;","uniform float sepia;","uniform sampler2D uSampler;","const mat3 sepiaMatrix = mat3(0.3588, 0.7044, 0.1368, 0.2990, 0.5870, 0.1140, 0.2392, 0.4696, 0.0912);","void main(void) {"," gl_FragColor = texture2D(uSampler, vTextureCoord);"," gl_FragColor.rgb = mix( gl_FragColor.rgb, gl_FragColor.rgb * sepiaMatrix, sepia);","}"]},b.SepiaFilter.prototype=Object.create(b.AbstractFilter.prototype),b.SepiaFilter.prototype.constructor=b.SepiaFilter,Object.defineProperty(b.SepiaFilter.prototype,"sepia",{get:function(){return this.uniforms.sepia.value},set:function(a){this.uniforms.sepia.value=a}}),b.TwistFilter=function(){b.AbstractFilter.call(this),this.passes=[this],this.uniforms={radius:{type:"1f",value:.5},angle:{type:"1f",value:5},offset:{type:"2f",value:{x:.5,y:.5}}},this.fragmentSrc=["precision mediump float;","varying vec2 vTextureCoord;","varying vec4 vColor;","uniform vec4 dimensions;","uniform sampler2D uSampler;","uniform float radius;","uniform float angle;","uniform vec2 offset;","void main(void) {"," vec2 coord = vTextureCoord - offset;"," float distance = length(coord);"," if (distance < radius) {"," float ratio = (radius - distance) / radius;"," float angleMod = ratio * ratio * angle;"," float s = sin(angleMod);"," float c = cos(angleMod);"," coord = vec2(coord.x * c - coord.y * s, coord.x * s + coord.y * c);"," }"," gl_FragColor = texture2D(uSampler, coord+offset);","}"]},b.TwistFilter.prototype=Object.create(b.AbstractFilter.prototype),b.TwistFilter.prototype.constructor=b.TwistFilter,Object.defineProperty(b.TwistFilter.prototype,"offset",{get:function(){return this.uniforms.offset.value},set:function(a){this.dirty=!0,this.uniforms.offset.value=a}}),Object.defineProperty(b.TwistFilter.prototype,"radius",{get:function(){return this.uniforms.radius.value},set:function(a){this.dirty=!0,this.uniforms.radius.value=a}}),Object.defineProperty(b.TwistFilter.prototype,"angle",{get:function(){return this.uniforms.angle.value},set:function(a){this.dirty=!0,this.uniforms.angle.value=a}}),b.ColorStepFilter=function(){b.AbstractFilter.call(this),this.passes=[this],this.uniforms={step:{type:"1f",value:5}},this.fragmentSrc=["precision mediump float;","varying vec2 vTextureCoord;","varying vec4 vColor;","uniform sampler2D uSampler;","uniform float step;","void main(void) {"," vec4 color = texture2D(uSampler, vTextureCoord);"," color = floor(color * step) / step;"," gl_FragColor = color;","}"]},b.ColorStepFilter.prototype=Object.create(b.AbstractFilter.prototype),b.ColorStepFilter.prototype.constructor=b.ColorStepFilter,Object.defineProperty(b.ColorStepFilter.prototype,"step",{get:function(){return this.uniforms.step.value},set:function(a){this.uniforms.step.value=a}}),b.DotScreenFilter=function(){b.AbstractFilter.call(this),this.passes=[this],this.uniforms={scale:{type:"1f",value:1},angle:{type:"1f",value:5},dimensions:{type:"4fv",value:[0,0,0,0]}},this.fragmentSrc=["precision mediump float;","varying vec2 vTextureCoord;","varying vec4 vColor;","uniform vec4 dimensions;","uniform sampler2D uSampler;","uniform float angle;","uniform float scale;","float pattern() {"," float s = sin(angle), c = cos(angle);"," vec2 tex = vTextureCoord * dimensions.xy;"," vec2 point = vec2("," c * tex.x - s * tex.y,"," s * tex.x + c * tex.y"," ) * scale;"," return (sin(point.x) * sin(point.y)) * 4.0;","}","void main() {"," vec4 color = texture2D(uSampler, vTextureCoord);"," float average = (color.r + color.g + color.b) / 3.0;"," gl_FragColor = vec4(vec3(average * 10.0 - 5.0 + pattern()), color.a);","}"]},b.DotScreenFilter.prototype=Object.create(b.AbstractFilter.prototype),b.DotScreenFilter.prototype.constructor=b.DotScreenFilter,Object.defineProperty(b.DotScreenFilter.prototype,"scale",{get:function(){return this.uniforms.scale.value},set:function(a){this.dirty=!0,this.uniforms.scale.value=a}}),Object.defineProperty(b.DotScreenFilter.prototype,"angle",{get:function(){return this.uniforms.angle.value},set:function(a){this.dirty=!0,this.uniforms.angle.value=a}}),b.CrossHatchFilter=function(){b.AbstractFilter.call(this),this.passes=[this],this.uniforms={blur:{type:"1f",value:1/512}},this.fragmentSrc=["precision mediump float;","varying vec2 vTextureCoord;","varying vec4 vColor;","uniform float blur;","uniform sampler2D uSampler;","void main(void) {"," float lum = length(texture2D(uSampler, vTextureCoord.xy).rgb);"," gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);"," if (lum < 1.00) {"," if (mod(gl_FragCoord.x + gl_FragCoord.y, 10.0) == 0.0) {"," gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);"," }"," }"," if (lum < 0.75) {"," if (mod(gl_FragCoord.x - gl_FragCoord.y, 10.0) == 0.0) {"," gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);"," }"," }"," if (lum < 0.50) {"," if (mod(gl_FragCoord.x + gl_FragCoord.y - 5.0, 10.0) == 0.0) {"," gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);"," }"," }"," if (lum < 0.3) {"," if (mod(gl_FragCoord.x - gl_FragCoord.y - 5.0, 10.0) == 0.0) {"," gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);"," }"," }","}"]},b.CrossHatchFilter.prototype=Object.create(b.AbstractFilter.prototype),b.CrossHatchFilter.prototype.constructor=b.CrossHatchFilter,Object.defineProperty(b.CrossHatchFilter.prototype,"blur",{get:function(){return this.uniforms.blur.value/(1/7e3)},set:function(a){this.uniforms.blur.value=1/7e3*a}}),b.RGBSplitFilter=function(){b.AbstractFilter.call(this),this.passes=[this],this.uniforms={red:{type:"2f",value:{x:20,y:20}},green:{type:"2f",value:{x:-20,y:20}},blue:{type:"2f",value:{x:20,y:-20}},dimensions:{type:"4fv",value:[0,0,0,0]}},this.fragmentSrc=["precision mediump float;","varying vec2 vTextureCoord;","varying vec4 vColor;","uniform vec2 red;","uniform vec2 green;","uniform vec2 blue;","uniform vec4 dimensions;","uniform sampler2D uSampler;","void main(void) {"," gl_FragColor.r = texture2D(uSampler, vTextureCoord + red/dimensions.xy).r;"," gl_FragColor.g = texture2D(uSampler, vTextureCoord + green/dimensions.xy).g;"," gl_FragColor.b = texture2D(uSampler, vTextureCoord + blue/dimensions.xy).b;"," gl_FragColor.a = texture2D(uSampler, vTextureCoord).a;","}"]},b.RGBSplitFilter.prototype=Object.create(b.AbstractFilter.prototype),b.RGBSplitFilter.prototype.constructor=b.RGBSplitFilter,Object.defineProperty(b.RGBSplitFilter.prototype,"red",{get:function(){return this.uniforms.red.value},set:function(a){this.uniforms.red.value=a}}),Object.defineProperty(b.RGBSplitFilter.prototype,"green",{get:function(){return this.uniforms.green.value},set:function(a){this.uniforms.green.value=a}}),Object.defineProperty(b.RGBSplitFilter.prototype,"blue",{get:function(){return this.uniforms.blue.value},set:function(a){this.uniforms.blue.value=a}}),"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=b),exports.PIXI=b):"undefined"!=typeof define&&define.amd?define(b):a.PIXI=b}).call(this); diff --git a/src/renderers/pixi-renderer.js b/src/renderers/pixi-renderer.js index 17048b87..7ae45be6 100644 --- a/src/renderers/pixi-renderer.js +++ b/src/renderers/pixi-renderer.js @@ -1,3 +1,6 @@ +/* + * @requires pixi.js + */ /** * class PixiRenderer < Renderer * @@ -7,7 +10,6 @@ * * Additional config options: * - * - debug: Draw debug shapes and bounding boxes. (default: `false`) * - metaEl: HTMLElement to write meta information like FPS and IPF into. (default: autogenerated) * - offset: Offset the shapes by this amount. (default: `{ x: 0, y: 0 }`) * - styles: Styles to use to draw the shapes. (see below) @@ -45,47 +47,67 @@ Physics.renderer('pixi', function( parent ){ return {}; } - var Pi2 = Math.PI * 2; - - var defaults = { - - // draw aabbs of bodies for debugging - debug: false, - // the element to place meta data into - metaEl: null, - offset: { x: 0, y: 0 }, - // Provide some default colours - styles: { - // Defines the default canvas colour - 'color': '0x66FF99', - - 'point' : '0xE8900C', - - 'circle' : { - strokeStyle: '0xE8900C', - lineWidth: 3, - fillStyle: '0xD5DE4C', - angleIndicator: '0xE8900C' - }, - - 'convex-polygon' : { - strokeStyle: '0xE8900C', - lineWidth: 3, - fillStyle: '0xD5DE4C', - angleIndicator: '0xE8900C' + var Pi2 = Math.PI * 2 + ,colors = { + white: '0xFFFFFF' + ,violet: '0x542437' + ,blue: '0x53777A' + } + + ,defaults = { + + // the element to place meta data into + metaEl: null, + offset: { x: 0, y: 0 }, + // Provide some default colours + styles: { + // Defines the default canvas colour + 'color': false, + + 'point': colors.blue, + + 'circle' : { + strokeStyle: colors.blue, + lineWidth: 1, + fillStyle: colors.blue, + angleIndicator: colors.white, + fillAlpha: 1, + alpha: 1 + }, + + 'rectangle' : { + strokeStyle: colors.violet, + lineWidth: 1, + fillStyle: colors.violet, + angleIndicator: colors.white, + fillAlpha: 1, + alpha: 1 + }, + + 'convex-polygon' : { + strokeStyle: colors.violet, + lineWidth: 1, + fillStyle: colors.violet, + angleIndicator: colors.white, + fillAlpha: 1, + alpha: 1 + } } } - }; + ; return { // extended init: function( options ){ - var self = this; + var self = this + ,el + ,isTransparent + ; if (typeof PIXI === 'undefined') { - throw "PIXI obj not present - cannot continue "; + throw "PIXI not present - cannot continue"; } // call parent init @@ -98,24 +120,25 @@ Physics.renderer('pixi', function( parent ){ }); this.options( options, true ); + isTransparent = (!this.options.styles.color || this.options.styles.color === 'transparent'); // Hook in PIXI stage here this.stage = new PIXI.Stage(this.options.styles.color); - this.renderer = new PIXI.autoDetectRenderer(this.options.width, this.options.height); // Create empty meta object for use later this.meta = {}; + el = (this.el && this.el.nodeName === 'CANVAS') ? el : null; // add the renderer view element to the DOM according to its type - if ( this.el.nodeName === 'CANVAS' ){ - this.renderer = new PIXI.autoDetectRenderer(this.options.width, this.options.height, this.el); - } else { - this.renderer = new PIXI.autoDetectRenderer(this.options.width, this.options.height); + this.renderer = new PIXI.autoDetectRenderer(this.options.width, this.options.height, { + view: el, + transparent: isTransparent, + resolution: window.devicePixelRatio || 1 + }); - if ( this.el !== null ) { - this.el.appendChild(this.renderer.view); - } else { - document.body.appendChild(this.renderer.view); - } + if ( !el ){ + this.el = this.el || document.body; + // add to passed in element + this.el.appendChild( this.renderer.view ); } }, @@ -181,31 +204,86 @@ Physics.renderer('pixi', function( parent ){ }, /** - * PixiRenderer#createCircle( x, y, r, style ) -> PIXI.Graphics + * PixiRenderer#setStyles( graphics, styles ) -> PIXI.Graphics + * - graphics (PIXI.Graphics): The graphics object to set styles on + * - styles (Object): The styles configuration + * + (PIXI.Graphics): A graphic object + * + * Set styles on pixi graphics object + **/ + setStyles: function( graphics, styles ){ + + if ( Physics.util.isObject(styles) ){ + + if ( styles.fillStyle && styles.fillStyle !== 'transparent' ){ + graphics.beginFill( styles.fillStyle ); + graphics.fillAlpha = styles.fillAlpha || 1; + } else { + graphics.beginFill(); + graphics.fillAlpha = 0; + } + + graphics.lineStyle( styles.lineWidth || 0, styles.strokeStyle ); + graphics.alpha = styles.alpha || 1; + + } else { + + if ( styles && styles !== 'transparent' ){ + graphics.beginFill( styles ); + } else { + graphics.beginFill(); + graphics.fillAlpha = 0; + } + + graphics.lineStyle( 0 ); + } + + return graphics; + }, + + /** + * PixiRenderer#createCircle( x, y, r, styles ) -> PIXI.Graphics * - x (Number): The x coord * - y (Number): The y coord * - r (Number): The circle radius - * - style (Object): The styles configuration + * - styles (Object): The styles configuration * + (PIXI.Graphics): A graphic object representing a circle. * * Create a circle for use in PIXI stage **/ - createCircle: function( x, y, r, style ){ + createCircle: function( x, y, r, styles ){ + + var graphics = new PIXI.Graphics(); + this.setStyles( graphics, styles ); + graphics.drawCircle( x, y, r ); + graphics.endFill(); + return graphics; + }, + + /** + * PixiRenderer#createRect( x, y, r, styles ) -> PIXI.Graphics + * - x (Number): The x coord + * - y (Number): The y coord + * - width (Number): The rectangle width + * - height (Number): The rectangle height + * - styles (Object): The styles configuration + * + (PIXI.Graphics): A graphic object representing a circle. + * + * Create a rectangle for use in PIXI stage + **/ + createRect: function( x, y, width, height, styles ){ var graphics = new PIXI.Graphics(); - graphics.beginFill(style.fillStyle); - graphics.lineStyle(style.lineWidth, style.strokeStyle); - graphics.drawCircle(x, y, r); - // Center the graphics to the circle - graphics.pivot.x = (x / 2) + (r / 2); - graphics.pivot.y = (y / 2) + (r / 2); + this.setStyles( graphics, styles ); + graphics.drawRect( x, y, width, height ); + graphics.endFill(); return graphics; }, /** - * PixiRenderer#createPolygon( verts, style ) -> PIXI.Graphics + * PixiRenderer#createPolygon( verts, styles ) -> PIXI.Graphics * - verts (Array): Array of [[Vectorish]] vertices - * - style (Object): The styles configuration + * - styles (Object): The styles configuration * + (PIXI.Graphics): A graphic object representing a polygon. * * Create a polygon for use in PIXI stage @@ -223,8 +301,7 @@ Physics.renderer('pixi', function( parent ){ ,graphics = new PIXI.Graphics() ; - graphics.beginFill(styles.fillStyle); - graphics.lineStyle(styles.lineWidth, styles.strokeStyle); + this.setStyles( graphics, styles ); graphics.moveTo(x, y); @@ -245,10 +322,10 @@ Physics.renderer('pixi', function( parent ){ }, /** - * PixiRenderer#createLine( from, to, style ) -> PIXI.Graphics + * PixiRenderer#createLine( from, to, styles ) -> PIXI.Graphics * - from (Vectorish): Starting point * - to (Vectorish): Ending point - * - style (Object): The styles configuration + * - styles (Object): The styles configuration * + (PIXI.Graphics): A graphic object representing a polygon. * * Create a line for use in PIXI stage @@ -260,8 +337,7 @@ Physics.renderer('pixi', function( parent ){ ; var graphics = new PIXI.Graphics(); - graphics.beginFill(styles.fillStyle); - graphics.lineStyle(styles.lineWidth, styles.strokeStyle); + this.setStyles( graphics, styles ); graphics.moveTo(x, y); @@ -275,53 +351,50 @@ Physics.renderer('pixi', function( parent ){ }, // extended - createView: function( geometry ){ + createView: function( geometry, styles ){ var view = null ,aabb = geometry.aabb() ,hw = aabb.hw + Math.abs(aabb.x) ,hh = aabb.hh + Math.abs(aabb.y) - ,x = hw + 1 - ,y = hh + 1 ,name = geometry.name ; - var styles = styles || this.options.styles[ name ]; - - x += styles.lineWidth | 0; - y += styles.lineWidth | 0; + styles = styles || this.options.styles[ name ] || this.options.styles.circle || {}; if (name === 'circle'){ - view = this.createCircle(x, y, geometry.radius, styles); + view = this.createCircle(0, 0, geometry.radius, styles); } else if (name === 'convex-polygon'){ view = this.createPolygon(geometry.vertices, styles); - } - if (styles.angleIndicator){ + } else if (name === 'rectangle'){ - view.beginFill(styles.angleIndicator); - view.moveTo((x / 2), (5 + styles.lineWidth)); - view.lineTo((x / 2) + (geometry.radius / 2), geometry.radius); - // Center the graphics to the circle - view.endFill(); + view = this.createRect(-geometry.width/2, -geometry.height/2, geometry.width, geometry.height, styles); + } else { + // assume it's a point + view = this.createCircle(0, 0, 1, styles); } - if (view) { - this.stage.addChild(view); - return view; - } else { - throw "Invalid view name passed."; + + if ( styles.angleIndicator && styles.angleIndicator !== 'transparent' ){ + + view.lineStyle( styles.lineWidth, styles.angleIndicator ); + view.moveTo( 0, 0 ); + view.lineTo( hw, 0 ); } + view.cacheAsBitmap = true; + this.stage.addChild(view); + return view; }, // extended drawMeta: function( meta ){ if (!this.meta.loaded){ - // define the font style here + // define the font styles here var fontStyles = { font: "18px Snippet", fill: "white", diff --git a/test/pixi-sandbox.html b/test/pixi-sandbox.html new file mode 100644 index 00000000..24d8d482 --- /dev/null +++ b/test/pixi-sandbox.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + From 2e35ad7498433a07cefc0cc74afaadca031cbcfa Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Wed, 19 Nov 2014 17:25:18 -0500 Subject: [PATCH 045/110] fix tests for pixi --- gruntfile.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/gruntfile.js b/gruntfile.js index b998a0cb..76a2bab0 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -91,6 +91,9 @@ module.exports = function(grunt) { main: 'physicsjs-'+pkg.version } ], + paths: { + 'pixi': '../lib/pixi' + }, optimize: 'none', name: '../' + config.rjsHelper.replace(/\.js$/, ''), out: 'test/physicsjs-built.js' @@ -132,7 +135,7 @@ module.exports = function(grunt) { src.replace(/@requires\s([\w-_\/]+(\.js)?)/g, function( match, dep ){ var i = dep.indexOf('.js'); - + if ( i > -1 ){ // must be a 3rd party dep dep = dep.substr( 0, i ); @@ -266,7 +269,10 @@ module.exports = function(grunt) { location: 'physicsjs', main: 'physicsjs' } - ] + ], + paths: { + 'pixi': '../lib/pixi' + } } } } @@ -298,7 +304,8 @@ module.exports = function(grunt) { requireConfig: { baseUrl: './', paths: { - 'bundle': config.distRequireJS.out.replace(/\.js$/, '') + 'bundle': config.distRequireJS.out.replace(/\.js$/, ''), + 'pixi': 'lib/pixi' } } } From c037e6589fdc8a4c9d400df50a0f20fd6322ebff Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Thu, 20 Nov 2014 16:43:29 -0500 Subject: [PATCH 046/110] re #11. initial work on body sleeping --- src/behaviors/body-impulse-response.js | 4 +++ src/behaviors/interactive.js | 7 ++++ src/core/body.js | 48 +++++++++++++++++++++++++- src/core/world.js | 2 ++ src/integrators/improved-euler.js | 17 +++++---- src/integrators/velocity-verlet.js | 6 ++-- src/integrators/verlet.js | 4 +-- src/renderers/debug.js | 15 ++++++++ 8 files changed, 88 insertions(+), 15 deletions(-) diff --git a/src/behaviors/body-impulse-response.js b/src/behaviors/body-impulse-response.js index cd617b0d..3ca3946c 100644 --- a/src/behaviors/body-impulse-response.js +++ b/src/behaviors/body-impulse-response.js @@ -222,6 +222,10 @@ Physics.behavior('body-impulse-response', function( parent ){ } } + // wake up bodies if necessary + bodyA.sleepCheck(); + bodyB.sleepCheck(); + scratch.done(); }, diff --git a/src/behaviors/interactive.js b/src/behaviors/interactive.js index dc28f958..7726b959 100644 --- a/src/behaviors/interactive.js +++ b/src/behaviors/interactive.js @@ -137,6 +137,8 @@ Physics.behavior('interactive', function( parent ){ // remember the currently grabbed bodies data = self.bodyData[touchId] || {}; data.body = body; + // wake the body up + body.sleep( false ); // if we're grabbing the same body twice we don't want to remember the wrong treatment. data.treatment = self.bodyDataByUID[ body.uid ] ? self.bodyDataByUID[ body.uid ].treatment : body.treatment; // change its treatment but remember its old treatment @@ -194,6 +196,9 @@ Physics.behavior('interactive', function( parent ){ if ( data ){ body = data.body; + // wake the body up + body.sleep( false ); + time = Physics.util.ticker.now(); // set old mouse position @@ -241,6 +246,8 @@ Physics.behavior('interactive', function( parent ){ // release the body if ( data ){ body = data.body; + // wake the body up + body.sleep( false ); // get new mouse position data.pos.clone( pos ); diff --git a/src/core/body.js b/src/core/body.js index 8dfd0fe7..5d6e8003 100644 --- a/src/core/body.js +++ b/src/core/body.js @@ -235,6 +235,52 @@ **/ }, + sleep: function( dt ){ + + if ( dt === true ){ + // force sleep + this.asleep = true; + + } else if ( dt === false ){ + // force wakup + this.asleep = false; + this.sleepIdleTime = 0; + + } else if ( dt && !this.asleep ) { + + this.sleepCheck( dt ); + } + + return this.asleep; + }, + + sleepCheck: function( dt ){ + + var limit + ,v + ,aabb + ; + + dt = dt || 0; + // check velocity + limit = this.sleepSpeedLimit || (this._world && this._world.sleepSpeedLimit) || 0; + aabb = this.aabb(); + v = this.state.vel.norm() + Math.abs(Math.max(aabb.hw, aabb.hh) * this.state.angular.vel); + + if ( v <= limit ){ + // check idle time + limit = this.sleepTimeLimit || (this._world && this._world.sleepTimeLimit) || 0; + this.sleepIdleTime = (this.sleepIdleTime || 0) + dt; + + if ( this.sleepIdleTime > limit ){ + this.asleep = true; + } + } else { + this.sleepIdleTime = 0; + this.asleep = false; + } + }, + /** * Body#setWorld( world ) -> this * - world (Object): The world (or null) @@ -293,7 +339,7 @@ // if no point at which to apply the force... apply at center of mass if ( p && this.moi ){ - + // apply torques state = this.state; r.clone( p ); diff --git a/src/core/world.js b/src/core/world.js index f9c50239..a4949a1c 100644 --- a/src/core/world.js +++ b/src/core/world.js @@ -143,6 +143,8 @@ this._paused = false; this._warp = 1; this._time = 0; + this.sleepSpeedLimit = 0.01; + this.sleepTimeLimit = 1000; // set options this.options = Physics.util.options( defaults ); diff --git a/src/integrators/improved-euler.js b/src/integrators/improved-euler.js index 33a68856..f25bd876 100644 --- a/src/integrators/improved-euler.js +++ b/src/integrators/improved-euler.js @@ -1,7 +1,7 @@ Physics.integrator('improved-euler', function( parent ){ return { - /** + /** * class ImprovedEuler < Integrator * * `Physics.integrator('improved-euler')`. @@ -15,7 +15,7 @@ Physics.integrator('improved-euler', function( parent ){ // call parent init parent.init.call(this, options); }, - + // extended integrateVelocities: function( bodies, dt ){ @@ -31,15 +31,15 @@ Physics.integrator('improved-euler', function( parent ){ state = body.state; // only integrate if the body isn't fixed - if ( body.treatment !== 'static' ){ + if ( body.treatment !== 'static' && !body.sleep( dt ) ){ // Inspired from https://github.com/soulwire/Coffee-Physics // @licence MIT - // + // // x += (v * dt) + (a * 0.5 * dt * dt) // v += a * dt - + // Scale force to mass. // state.acc.mult( body.massInv ); @@ -65,7 +65,7 @@ Physics.integrator('improved-euler', function( parent ){ // // Angular components - // + // state.old.angular.vel = state.angular.vel; state.angular.vel += state.angular.acc * dt; @@ -101,7 +101,7 @@ Physics.integrator('improved-euler', function( parent ){ state = body.state; // only integrate if the body isn't fixed - if ( body.treatment !== 'static' ){ + if ( body.treatment !== 'static' && !body.sleep() ){ // Store previous location. @@ -119,7 +119,7 @@ Physics.integrator('improved-euler', function( parent ){ // // Angular components - // + // state.old.angular.pos = state.angular.pos; state.angular.pos += state.old.angular.vel * dt + state.old.angular.acc * halfdtdt; @@ -132,4 +132,3 @@ Physics.integrator('improved-euler', function( parent ){ } }; }); - diff --git a/src/integrators/velocity-verlet.js b/src/integrators/velocity-verlet.js index 4e572897..65c9379e 100644 --- a/src/integrators/velocity-verlet.js +++ b/src/integrators/velocity-verlet.js @@ -82,7 +82,7 @@ Physics.integrator('velocity-verlet', function( parent ){ state = body.state; // only integrate if the body isn't static - if ( body.treatment !== 'static' ){ + if ( body.treatment !== 'static' && !body.sleep() ){ // v = v_prev + 0.5 * (a_prev + a) * dt // x = x_prev + v_prev * dt + 0.5 * a_prev * dt * dt @@ -140,7 +140,7 @@ Physics.integrator('velocity-verlet', function( parent ){ state = body.state; // only integrate if the body isn't static - if ( body.treatment !== 'static' ){ + if ( body.treatment !== 'static' && !body.sleep( dt ) ){ // x = x_prev + v_prev * dt + 0.5 * a_prev * dt * dt @@ -176,7 +176,7 @@ Physics.integrator('velocity-verlet', function( parent ){ state.old.angular.acc = state.angular.acc; state.old.angular.vel = state.angular.vel - state.old.angular.acc * dt; } - + state.old.angular.pos = state.angular.pos; state.angular.pos += state.angular.vel * dt + 0.5 * state.old.angular.acc * dtdt; diff --git a/src/integrators/verlet.js b/src/integrators/verlet.js index 346da500..61c5c91c 100644 --- a/src/integrators/verlet.js +++ b/src/integrators/verlet.js @@ -49,7 +49,7 @@ Physics.integrator('verlet', function( parent ){ state = body.state; // only integrate if the body isn't static - if ( body.treatment !== 'static' ){ + if ( body.treatment !== 'static' && !body.sleep( dt ) ){ // Inspired from https://github.com/soulwire/Coffee-Physics // @licence MIT @@ -138,7 +138,7 @@ Physics.integrator('verlet', function( parent ){ state = body.state; // only integrate if the body isn't static - if ( body.treatment !== 'static' ){ + if ( body.treatment !== 'static' && !body.sleep() ){ // so we need to scale the value by dt so it // complies with other integration methods diff --git a/src/renderers/debug.js b/src/renderers/debug.js index b6e63523..9bb46fc0 100644 --- a/src/renderers/debug.js +++ b/src/renderers/debug.js @@ -17,6 +17,7 @@ * - drawRealPosition: whether or not to draw the non-interpolated position of bodies. (default: `false`) * - drawIntervals: whether or not to draw the broadphase (sweep-prune) intervals. (default: `false`) * - drawContacts: whether or not to draw contact points. (default: `false`) + * - drawSleepState: whether or not to highlight sleeping bodies. (default: `false`) * - aabbColor: the color of AABBs * - realBodyStyle: styles used to draw the image of the body at its true non-interpolated position * - intervalMinColor: color of interval minima @@ -64,6 +65,7 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ drawRealPosition: false, drawIntervals: false, drawContacts: false, + drawSleepState: false, // *** colors // color of the aabbs @@ -242,6 +244,7 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ f.add( op, 'drawRealPosition' ); f.add( op, 'drawIntervals' ); f.add( op, 'drawContacts' ); + f.add( op, 'drawSleepState' ); f.open(); f = gui.addFolder( 'Colors' ); @@ -307,6 +310,18 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ ctx.drawImage(body._debugView, -body._debugView.width * 0.5, -body._debugView.height * 0.5); ctx.restore(); } + + if ( this.options.drawSleepState && body.sleep() ){ + aabb = aabb || body.aabb(); + body._sleepView = body._sleepView || this.createView(body.geometry, 'rgba(100,100,100,0.3)'); + ctx.save(); + ctx.globalCompositeOperation = 'color'; + ctx.translate( x, y ); + ctx.rotate( ang ); + ctx.drawImage(body._sleepView, -view.width/2, -view.height/2, view.width, view.height); + // ctx.globalCompositeOperation = ''; + ctx.restore(); + } } }; }); From 6f319858fe9558be56d2761e9197ec9e12b82b81 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Thu, 20 Nov 2014 16:58:00 -0500 Subject: [PATCH 047/110] minor interaction bug --- src/behaviors/interactive.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/behaviors/interactive.js b/src/behaviors/interactive.js index 7726b959..659a16a6 100644 --- a/src/behaviors/interactive.js +++ b/src/behaviors/interactive.js @@ -151,6 +151,7 @@ Physics.behavior('interactive', function( parent ){ data.offset.clone( pos ).vsub( body.state.pos ); // init touchPointsOld here, too, so we don't have to do it in "move" data.oldPos = data.oldPos || new Physics.vector(); + data.oldPos.clone( pos ); pos.body = body; self.bodyData[touchId] = data; From cd090fdb0e1aa7c2d193d889e51a08ea3bb4c445 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Thu, 20 Nov 2014 16:58:00 -0500 Subject: [PATCH 048/110] minor interaction bug --- src/behaviors/interactive.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/behaviors/interactive.js b/src/behaviors/interactive.js index dc28f958..83cda7e6 100644 --- a/src/behaviors/interactive.js +++ b/src/behaviors/interactive.js @@ -149,6 +149,7 @@ Physics.behavior('interactive', function( parent ){ data.offset.clone( pos ).vsub( body.state.pos ); // init touchPointsOld here, too, so we don't have to do it in "move" data.oldPos = data.oldPos || new Physics.vector(); + data.oldPos.clone( pos ); pos.body = body; self.bodyData[touchId] = data; From 734d2f4484319a172496e807da1debc180834427 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Thu, 20 Nov 2014 16:58:00 -0500 Subject: [PATCH 049/110] improvements to hibernation --- src/behaviors/body-impulse-response.js | 12 ++++- src/behaviors/edge-collision-detection.js | 4 +- src/behaviors/sweep-prune.js | 4 +- src/core/body.js | 19 +++++++- src/renderers/debug.js | 36 ++++++++++++++ test/collision-sandbox.html | 57 +++++++++++++++++++++-- test/integrator-sandbox.html | 2 +- 7 files changed, 121 insertions(+), 13 deletions(-) diff --git a/src/behaviors/body-impulse-response.js b/src/behaviors/body-impulse-response.js index 3ca3946c..42807326 100644 --- a/src/behaviors/body-impulse-response.js +++ b/src/behaviors/body-impulse-response.js @@ -123,18 +123,22 @@ Physics.behavior('body-impulse-response', function( parent ){ // extract bodies bodyB.state.pos.vadd( mtv ); + bodyB.state.old.pos.vadd( mtv ); } else if ( fixedB ){ // extract bodies bodyA.state.pos.vsub( mtv ); + bodyA.state.old.pos.vsub( mtv ); } else { // extract bodies mtv.mult( 0.5 ); bodyA.state.pos.vsub( mtv ); + bodyA.state.old.pos.vsub( mtv ); bodyB.state.pos.vadd( mtv ); + bodyB.state.old.pos.vadd( mtv ); } } @@ -223,8 +227,12 @@ Physics.behavior('body-impulse-response', function( parent ){ } // wake up bodies if necessary - bodyA.sleepCheck(); - bodyB.sleepCheck(); + if ( bodyA.sleep() ){ + bodyA.sleepCheck(); + } + if ( bodyB.sleep() ){ + bodyB.sleepCheck(); + } scratch.done(); }, diff --git a/src/behaviors/edge-collision-detection.js b/src/behaviors/edge-collision-detection.js index b261d31c..a36e2989 100644 --- a/src/behaviors/edge-collision-detection.js +++ b/src/behaviors/edge-collision-detection.js @@ -208,13 +208,13 @@ Physics.behavior('edge-collision-detection', function( parent ){ // extended connect: function( world ){ - world.on( 'integrate:velocities', this.checkAll, this ); + world.on( 'integrate:positions', this.checkAll, this, 2 ); }, // extended disconnect: function( world ){ - world.off( 'integrate:velocities', this.checkAll, this ); + world.off( 'integrate:positions', this.checkAll, this, 2 ); }, /** internal diff --git a/src/behaviors/sweep-prune.js b/src/behaviors/sweep-prune.js index 226caae0..92e3da86 100644 --- a/src/behaviors/sweep-prune.js +++ b/src/behaviors/sweep-prune.js @@ -67,7 +67,7 @@ Physics.behavior('sweep-prune', function( parent ){ world.on( 'add:body', this.trackBody, this ); world.on( 'remove:body', this.untrackBody, this ); - world.on( 'integrate:velocities', this.sweep, this ); + world.on( 'integrate:positions', this.sweep, this, 1 ); // add current bodies var bodies = world.getBodies(); @@ -82,7 +82,7 @@ Physics.behavior('sweep-prune', function( parent ){ world.off( 'add:body', this.trackBody, this ); world.off( 'remove:body', this.untrackBody, this ); - world.off( 'integrate:velocities', this.sweep, this ); + world.off( 'integrate:positions', this.sweep, this, 1 ); this.clear(); }, diff --git a/src/core/body.js b/src/core/body.js index 5d6e8003..d08306cd 100644 --- a/src/core/body.js +++ b/src/core/body.js @@ -124,6 +124,9 @@ } }; + this._sleepMeanVel = new vector(); + this._sleepMeanK = 0; + // cleanup delete this.x; delete this.y; @@ -244,6 +247,8 @@ } else if ( dt === false ){ // force wakup this.asleep = false; + this._sleepMeanK = 0; + this._sleepMeanVel.zero(); this.sleepIdleTime = 0; } else if ( dt && !this.asleep ) { @@ -258,14 +263,22 @@ var limit ,v + ,d + ,r ,aabb + ,scratch = Physics.scratchpad() + ,diff = scratch.vector() ; dt = dt || 0; // check velocity limit = this.sleepSpeedLimit || (this._world && this._world.sleepSpeedLimit) || 0; aabb = this.aabb(); - v = this.state.vel.norm() + Math.abs(Math.max(aabb.hw, aabb.hh) * this.state.angular.vel); + r = Math.max(aabb.hw, aabb.hh); + this._sleepMeanK++; + diff.clone( this.state.vel ).vsub( this._sleepMeanVel ).mult( 1 / this._sleepMeanK ); + this._sleepMeanVel.vadd( diff ); + v = this._sleepMeanVel.norm() + Math.abs(r * this.state.angular.vel); if ( v <= limit ){ // check idle time @@ -277,8 +290,12 @@ } } else { this.sleepIdleTime = 0; + this._sleepMeanK = 0; + this._sleepMeanVel.zero(); this.asleep = false; } + + scratch.done(); }, /** diff --git a/src/renderers/debug.js b/src/renderers/debug.js index 9bb46fc0..6486c412 100644 --- a/src/renderers/debug.js +++ b/src/renderers/debug.js @@ -18,6 +18,7 @@ * - drawIntervals: whether or not to draw the broadphase (sweep-prune) intervals. (default: `false`) * - drawContacts: whether or not to draw contact points. (default: `false`) * - drawSleepState: whether or not to highlight sleeping bodies. (default: `false`) + * - drawBodyState: whether or not to show body position and velocity. (default: `false`) * - aabbColor: the color of AABBs * - realBodyStyle: styles used to draw the image of the body at its true non-interpolated position * - intervalMinColor: color of interval minima @@ -55,6 +56,10 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ return {}; } + function format( num ){ + return (num >= 0 ? ' ' : '') + num.toPrecision(2); + } + var defaults = { // the element to place meta data into @@ -66,6 +71,7 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ drawIntervals: false, drawContacts: false, drawSleepState: false, + drawBodyState: false, // *** colors // color of the aabbs @@ -221,6 +227,22 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ self._world.options({ maxIPF: m }); } } + ,get sleepTimeLimit(){ + return self._world ? self._world.sleepTimeLimit : 1000; + } + ,set sleepTimeLimit( t ){ + if ( self._world ){ + self._world.sleepTimeLimit = t; + } + } + ,get sleepSpeedLimit(){ + return self._world ? self._world.sleepSpeedLimit : 0.01; + } + ,set sleepSpeedLimit( t ){ + if ( self._world ){ + self._world.sleepSpeedLimit = t; + } + } }; function pauseWorld(){ @@ -236,6 +258,8 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ f = gui.addFolder( 'General' ); f.add( getset, 'timestep', 1, 20).step( 1 ); f.add( getset, 'maxIPF', 1, 100).step( 1 ); + f.add( getset, 'sleepTimeLimit', 1, 10000).step( 10 ); + f.add( getset, 'sleepSpeedLimit', 0.001, 0.1); f.add( { pause: pauseWorld }, 'pause'); f.open(); @@ -245,6 +269,7 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ f.add( op, 'drawIntervals' ); f.add( op, 'drawContacts' ); f.add( op, 'drawSleepState' ); + f.add( op, 'drawBodyState' ); f.open(); f = gui.addFolder( 'Colors' ); @@ -322,6 +347,17 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ // ctx.globalCompositeOperation = ''; ctx.restore(); } + + if ( this.options.drawBodyState ){ + ctx.strokeStyle = 'black'; + ctx.shadowColor = '#fff'; + ctx.shadowBlur = 4; + ctx.font = '12px monospace'; + ctx.strokeText('r: ('+x.toFixed(0)+', '+y.toFixed(0)+')', x, y-8); + ctx.strokeText('v: ('+format(v.x)+', '+format(v.y)+')', x, y+12); + ctx.shadowBlur = 0; + ctx.shadowColor = ''; + } } }; }); diff --git a/test/collision-sandbox.html b/test/collision-sandbox.html index eb67c1c9..d59af11c 100644 --- a/test/collision-sandbox.html +++ b/test/collision-sandbox.html @@ -34,6 +34,12 @@ var bodyStyles = { strokeStyle: '#888', fillStyle: 'transparent', lineWidth: 2, angleIndicator: 'rgba(200, 200, 100, 1)' }; var intr; + + function random(min, max){ + var a = (max - min); + return a * Math.random() + min; + } + function setup(world) { var renderer = Physics.renderer('debug', { @@ -155,14 +161,55 @@ }); } + function lotsOfCircles( world ){ + + var circles = []; + for ( var i = 0; i < 200; i++ ){ + circles.push( + Physics.body('circle', { + x: viewWidth * Math.random(), + y: viewHeight * Math.random(), + radius: random(10, 20), + mass: random(1, 10), + //cof: 0, + //vy: -0.2, + // vx: 0.1, + restitution: 1 + }) + ); + } + + world.add( circles ); + } + + function lotsOfRects( world ){ + + var rects = []; + for ( var i = 0; i < 100; i++ ){ + rects.push( + Physics.body('rectangle', { + x: viewWidth * Math.random(), + y: viewHeight * Math.random(), + width: random(30, 60), + height: random(30, 60), + mass: random(1, 10), + //cof: 0, + //vy: -0.2, + // vx: 0.1, + restitution: 1 + }) + ); + } + + world.add( rects ); + } + function addBodies( world ){ world.add( Physics.body('circle', { x: viewWidth/2, y: viewHeight - 20,//+100, radius: 20, - width: 40, - height: 40, mass: 1.4, //cof: 0, //vy: -0.2, @@ -174,8 +221,6 @@ x: viewWidth/2, y: viewHeight - 100,//+100, radius: 20, - width: 40, - height: 40, mass: 1.4, //cof: 0, //vy: -0.2, @@ -228,7 +273,9 @@ Physics({ timestep: 6, maxIPF: 24 }, [ setup - ,addBodies + // ,addBodies + // ,lotsOfCircles + ,lotsOfRects //,initEvents ]); diff --git a/test/integrator-sandbox.html b/test/integrator-sandbox.html index 41aefe56..5ee66ee9 100644 --- a/test/integrator-sandbox.html +++ b/test/integrator-sandbox.html @@ -162,7 +162,7 @@ var center = Physics.vector({x: viewWidth/2, y: viewHeight/2 }); world.add([ - Physics.behavior('spring', { k: 0.00001, center: center, body: ball }), + // Physics.behavior('spring', { k: 0.00001, center: center, body: ball }), Physics.behavior('body-collision-detection', { checkAll: false }), From 25d55c729fcc56e858c371f87ef3b0c0332794f5 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Fri, 21 Nov 2014 12:33:28 -0500 Subject: [PATCH 050/110] added wakeup forcing on large overlap --- src/behaviors/body-impulse-response.js | 7 +++++++ src/renderers/debug.js | 9 +++++++++ test/collision-sandbox.html | 4 ++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/behaviors/body-impulse-response.js b/src/behaviors/body-impulse-response.js index 42807326..b64f834b 100644 --- a/src/behaviors/body-impulse-response.js +++ b/src/behaviors/body-impulse-response.js @@ -10,6 +10,7 @@ * - mtvThreshold: apply partial extraction of bodies if the minimum transit vector is less than this value ( default: `1`) * this will depend on your simulation characteristic length scale * - bodyExtractDropoff: every body overlap correction (underneith mtvThreshold) will only extract by this fraction (0..1). Helps with stablizing contacts. (default: `0.5`) + * - forceWakeupAboveOverlapThreshold: force bodies to wake up if the overlap is above mtvThreshold ( default: `true` ) **/ Physics.behavior('body-impulse-response', function( parent ){ @@ -22,6 +23,8 @@ Physics.behavior('body-impulse-response', function( parent ){ // every body overlap correction (underneith mtvThreshold) will only extract by this fraction (0..1) // helps with stablizing contacts. ,bodyExtractDropoff: 0.5 + // force bodies to wake up if the overlap is above mtvThreshold + ,forceWakeupAboveOverlapThreshold: true }; return { @@ -117,6 +120,10 @@ Physics.behavior('body-impulse-response', function( parent ){ if ( contact ){ if ( mtv.normSq() < this.options.mtvThreshold ){ mtv.mult( this.options.bodyExtractDropoff ); + } else if ( this.options.forceWakeupAboveOverlapThreshold ) { + // wake up bodies if necessary + bodyA.sleep( false ); + bodyB.sleep( false ); } if ( fixedA ){ diff --git a/src/renderers/debug.js b/src/renderers/debug.js index 6486c412..c0c94e2e 100644 --- a/src/renderers/debug.js +++ b/src/renderers/debug.js @@ -219,6 +219,14 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ self._world.timestep( dt ); } } + ,get warp(){ + return self._world ? self._world.warp() : 1; + } + ,set warp( w ){ + if ( self._world ) { + self._world.warp( w ); + } + } ,get maxIPF(){ return self._world ? self._world.options.maxIPF : 16; } @@ -258,6 +266,7 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ f = gui.addFolder( 'General' ); f.add( getset, 'timestep', 1, 20).step( 1 ); f.add( getset, 'maxIPF', 1, 100).step( 1 ); + f.add( getset, 'warp', 0.01, 2); f.add( getset, 'sleepTimeLimit', 1, 10000).step( 10 ); f.add( getset, 'sleepSpeedLimit', 0.001, 0.1); f.add( { pause: pauseWorld }, 'pause'); diff --git a/test/collision-sandbox.html b/test/collision-sandbox.html index d59af11c..5005c80b 100644 --- a/test/collision-sandbox.html +++ b/test/collision-sandbox.html @@ -273,9 +273,9 @@ Physics({ timestep: 6, maxIPF: 24 }, [ setup - // ,addBodies + ,addBodies // ,lotsOfCircles - ,lotsOfRects + // ,lotsOfRects //,initEvents ]); From 4575353c1a84cb88605cb80d8602754c3da3067c Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Fri, 21 Nov 2014 13:32:05 -0500 Subject: [PATCH 051/110] minor friction fix for impulse response --- src/behaviors/body-impulse-response.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/behaviors/body-impulse-response.js b/src/behaviors/body-impulse-response.js index b64f834b..2b6df9ba 100644 --- a/src/behaviors/body-impulse-response.js +++ b/src/behaviors/body-impulse-response.js @@ -184,7 +184,14 @@ Physics.behavior('body-impulse-response', function( parent ){ bodyA.state.angular.vel += impulse * invMoiA * rAreg; } - // inContact = (impulse < 0.004); + // recalculate vreg + vAB.clone( bodyB.state.vel ) + .vadd( tmp.clone(rB).perp().mult( bodyB.state.angular.vel ) ) + .vsub( bodyA.state.vel ) + .vsub( tmp.clone(rA).perp().mult( bodyA.state.angular.vel ) ) + ; + + vreg = vAB.proj( perp ); // if we have friction and a relative velocity perpendicular to the normal if ( cof && vreg ){ From 81a39cef1b66b9ee5d28856ca623d290c9ff2cb9 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Fri, 21 Nov 2014 13:32:05 -0500 Subject: [PATCH 052/110] minor friction fix for impulse response --- src/behaviors/body-impulse-response.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/behaviors/body-impulse-response.js b/src/behaviors/body-impulse-response.js index cd617b0d..2fa39e1d 100644 --- a/src/behaviors/body-impulse-response.js +++ b/src/behaviors/body-impulse-response.js @@ -173,7 +173,14 @@ Physics.behavior('body-impulse-response', function( parent ){ bodyA.state.angular.vel += impulse * invMoiA * rAreg; } - // inContact = (impulse < 0.004); + // recalculate vreg + vAB.clone( bodyB.state.vel ) + .vadd( tmp.clone(rB).perp().mult( bodyB.state.angular.vel ) ) + .vsub( bodyA.state.vel ) + .vsub( tmp.clone(rA).perp().mult( bodyA.state.angular.vel ) ) + ; + + vreg = vAB.proj( perp ); // if we have friction and a relative velocity perpendicular to the normal if ( cof && vreg ){ From 9e55fdfa196edb553be2b3c903b21d5e8a2bbba1 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Fri, 21 Nov 2014 15:58:53 -0500 Subject: [PATCH 053/110] even better sleep monitoring --- src/core/body.js | 77 +++++++++++++++++++++++++++++++------ src/core/world.js | 5 ++- src/renderers/debug.js | 13 ++++++- test/collision-sandbox.html | 24 ++++++++---- 4 files changed, 95 insertions(+), 24 deletions(-) diff --git a/src/core/body.js b/src/core/body.js index d08306cd..1a4bbd0d 100644 --- a/src/core/body.js +++ b/src/core/body.js @@ -17,6 +17,44 @@ view: null }; + // Running average + // http://www.johndcook.com/blog/standard_deviation + // k is num elements + // m is current mean + // s is current std deviation + // v is value to push + function pushRunningAvg( k, m, s, v ){ + + var x = v - m; + + // Mk = Mk-1+ (xk – Mk-1)/k + // Sk = Sk-1 + (xk – Mk-1)*(xk – Mk). + m += x / k; + s += x * (v - m) + } + + // Running vector average + // http://www.johndcook.com/blog/standard_deviation + // k is num elements + // m is current mean (vector) + // s is current std deviation (vector) + // v is vector to push + function pushRunningVectorAvg( k, m, s, v ){ + var invK = 1/k + ,x = v.get(0) - m.get(0) + ,y = v.get(1) - m.get(1) + ; + + // Mk = Mk-1+ (xk – Mk-1)/k + // Sk = Sk-1 + (xk – Mk-1)*(xk – Mk). + m.add( x * invK, y * invK ); + + x *= v.get(0) - m.get(0); + y *= v.get(1) - m.get(1); + + s.add( x, y ); + } + var uidGen = 1; /** related to: Physics.util.decorator @@ -124,7 +162,10 @@ } }; - this._sleepMeanVel = new vector(); + this._sleepAngPosMean = 0; + this._sleepAngPosVariance = 0; + this._sleepPosMean = new vector(); + this._sleepPosVariance = new vector(); this._sleepMeanK = 0; // cleanup @@ -248,7 +289,10 @@ // force wakup this.asleep = false; this._sleepMeanK = 0; - this._sleepMeanVel.zero(); + this._sleepAngPosMean = 0; + this._sleepAngPosVariance = 0; + this._sleepPosMean.zero(); + this._sleepPosVariance.zero(); this.sleepIdleTime = 0; } else if ( dt && !this.asleep ) { @@ -268,17 +312,29 @@ ,aabb ,scratch = Physics.scratchpad() ,diff = scratch.vector() + ,diff2 = scratch.vector() ; dt = dt || 0; - // check velocity - limit = this.sleepSpeedLimit || (this._world && this._world.sleepSpeedLimit) || 0; - aabb = this.aabb(); + aabb = this.geometry.aabb(); r = Math.max(aabb.hw, aabb.hh); + + if ( this.asleep ){ + // check velocity + v = this.state.vel.norm() + Math.abs(r * this.state.angular.vel); + limit = this.sleepSpeedLimit || (this._world && this._world.sleepSpeedLimit) || 0; + + if ( v >= limit ){ + this.sleep( false ); + return scratch.done(); + } + } + this._sleepMeanK++; - diff.clone( this.state.vel ).vsub( this._sleepMeanVel ).mult( 1 / this._sleepMeanK ); - this._sleepMeanVel.vadd( diff ); - v = this._sleepMeanVel.norm() + Math.abs(r * this.state.angular.vel); + pushRunningVectorAvg( this._sleepMeanK, this._sleepPosMean, this._sleepPosVariance, this.state.pos ); + pushRunningAvg( this._sleepMeanK, this._sleepAngPosMean, this._sleepAngPosVariance, this.state.angular.pos ); + v = this._sleepPosVariance.norm() + Math.abs(r * this._sleepAngPosVariance); + limit = this.sleepVarianceLimit || (this._world && this._world.sleepVarianceLimit) || 0; if ( v <= limit ){ // check idle time @@ -289,10 +345,7 @@ this.asleep = true; } } else { - this.sleepIdleTime = 0; - this._sleepMeanK = 0; - this._sleepMeanVel.zero(); - this.asleep = false; + this.sleep( false ); } scratch.done(); diff --git a/src/core/world.js b/src/core/world.js index a4949a1c..b7043d1d 100644 --- a/src/core/world.js +++ b/src/core/world.js @@ -143,8 +143,9 @@ this._paused = false; this._warp = 1; this._time = 0; - this.sleepSpeedLimit = 0.01; - this.sleepTimeLimit = 1000; + this.sleepSpeedLimit = 0.1; + this.sleepVarianceLimit = 2; + this.sleepTimeLimit = 500; // set options this.options = Physics.util.options( defaults ); diff --git a/src/renderers/debug.js b/src/renderers/debug.js index c0c94e2e..0ce69264 100644 --- a/src/renderers/debug.js +++ b/src/renderers/debug.js @@ -236,7 +236,7 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ } } ,get sleepTimeLimit(){ - return self._world ? self._world.sleepTimeLimit : 1000; + return self._world ? self._world.sleepTimeLimit : 500; } ,set sleepTimeLimit( t ){ if ( self._world ){ @@ -251,6 +251,14 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ self._world.sleepSpeedLimit = t; } } + ,get sleepVarianceLimit(){ + return self._world ? self._world.sleepVarianceLimit : 2; + } + ,set sleepVarianceLimit( t ){ + if ( self._world ){ + self._world.sleepVarianceLimit = t; + } + } }; function pauseWorld(){ @@ -268,7 +276,8 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ f.add( getset, 'maxIPF', 1, 100).step( 1 ); f.add( getset, 'warp', 0.01, 2); f.add( getset, 'sleepTimeLimit', 1, 10000).step( 10 ); - f.add( getset, 'sleepSpeedLimit', 0.001, 0.1); + f.add( getset, 'sleepSpeedLimit', 0.001, 1); + f.add( getset, 'sleepVarianceLimit', 0.01, 100); f.add( { pause: pauseWorld }, 'pause'); f.open(); diff --git a/test/collision-sandbox.html b/test/collision-sandbox.html index 5005c80b..c1c5abd3 100644 --- a/test/collision-sandbox.html +++ b/test/collision-sandbox.html @@ -87,21 +87,20 @@ }, true); + var imp, acc; world.add([ edgeBounce ,Physics.behavior('sweep-prune') - ,Physics.behavior('body-collision-detection', { - checkAll: false - }) + ,Physics.behavior('body-collision-detection') - ,Physics.behavior('body-impulse-response') + ,imp = Physics.behavior('body-impulse-response') // add gravity - ,Physics.behavior('constant-acceleration') + ,acc = Physics.behavior('constant-acceleration') // ,Physics.behavior('newtonian') ,intr = Physics.behavior('interactive', {el: renderer.el.parentNode}) ]); - renderer.gui.add({ + var opts = { get interaction(){ return world.has(intr); } @@ -112,7 +111,16 @@ world.remove(intr); } } - }, 'interaction'); + ,get gravity(){ + return acc._acc.y; + } + ,set gravity( val ){ + acc.setAcceleration({ x: 0, y: val }); + } + }; + renderer.gui.add(opts, 'interaction'); + renderer.gui.add(imp.options, 'forceWakeupAboveOverlapThreshold'); + renderer.gui.add(opts, 'gravity', -0.001, 0.001); Physics.util.ticker.on(function (time, dt) { @@ -222,7 +230,7 @@ y: viewHeight - 100,//+100, radius: 20, mass: 1.4, - //cof: 0, + // cof: 0.1, //vy: -0.2, // vx: 0.1, restitution: 0.9 From 7d73cf646dc7d63268f9e2d3d9db66c06455442c Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Fri, 21 Nov 2014 16:02:46 -0500 Subject: [PATCH 054/110] minor fix for interactive behavior --- src/behaviors/interactive.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/behaviors/interactive.js b/src/behaviors/interactive.js index 659a16a6..eb239ee8 100644 --- a/src/behaviors/interactive.js +++ b/src/behaviors/interactive.js @@ -79,9 +79,7 @@ Physics.behavior('interactive', function( parent ){ // extended init: function( options ){ - var self = this - ,time - ; + var self = this; // call parent init method parent.init.call( this ); @@ -139,6 +137,7 @@ Physics.behavior('interactive', function( parent ){ data.body = body; // wake the body up body.sleep( false ); + data.time = Physics.util.ticker.now(); // if we're grabbing the same body twice we don't want to remember the wrong treatment. data.treatment = self.bodyDataByUID[ body.uid ] ? self.bodyDataByUID[ body.uid ].treatment : body.treatment; // change its treatment but remember its old treatment @@ -200,7 +199,7 @@ Physics.behavior('interactive', function( parent ){ // wake the body up body.sleep( false ); - time = Physics.util.ticker.now(); + data.time = Physics.util.ticker.now(); // set old mouse position data.oldPos.clone( data.pos ); @@ -225,7 +224,7 @@ Physics.behavior('interactive', function( parent ){ ,touch ,offset ,data - ,dt = Math.max(Physics.util.ticker.now() - time, self.options.moveThrottle) + ,dt ,touchIndex ,l ; @@ -252,6 +251,7 @@ Physics.behavior('interactive', function( parent ){ // get new mouse position data.pos.clone( pos ); + dt = Math.max(Physics.util.ticker.now() - data.time, self.options.moveThrottle); body.treatment = data.treatment; // calculate the release velocity body.state.vel.clone( data.pos ).vsub( data.oldPos ).mult( 1 / dt ); From 19e5de491e22fd3775cbbcaad0d3596c9083ddb0 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Fri, 21 Nov 2014 16:02:46 -0500 Subject: [PATCH 055/110] minor fix for interactive behavior Conflicts: src/behaviors/interactive.js --- src/behaviors/interactive.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/behaviors/interactive.js b/src/behaviors/interactive.js index 83cda7e6..673b029a 100644 --- a/src/behaviors/interactive.js +++ b/src/behaviors/interactive.js @@ -79,9 +79,7 @@ Physics.behavior('interactive', function( parent ){ // extended init: function( options ){ - var self = this - ,time - ; + var self = this; // call parent init method parent.init.call( this ); @@ -137,6 +135,8 @@ Physics.behavior('interactive', function( parent ){ // remember the currently grabbed bodies data = self.bodyData[touchId] || {}; data.body = body; + data.time = Physics.util.ticker.now(); + // if we're grabbing the same body twice we don't want to remember the wrong treatment. data.treatment = self.bodyDataByUID[ body.uid ] ? self.bodyDataByUID[ body.uid ].treatment : body.treatment; // change its treatment but remember its old treatment @@ -195,7 +195,7 @@ Physics.behavior('interactive', function( parent ){ if ( data ){ body = data.body; - time = Physics.util.ticker.now(); + data.time = Physics.util.ticker.now(); // set old mouse position data.oldPos.clone( data.pos ); @@ -220,7 +220,7 @@ Physics.behavior('interactive', function( parent ){ ,touch ,offset ,data - ,dt = Math.max(Physics.util.ticker.now() - time, self.options.moveThrottle) + ,dt ,touchIndex ,l ; @@ -245,6 +245,7 @@ Physics.behavior('interactive', function( parent ){ // get new mouse position data.pos.clone( pos ); + dt = Math.max(Physics.util.ticker.now() - data.time, self.options.moveThrottle); body.treatment = data.treatment; // calculate the release velocity body.state.vel.clone( data.pos ).vsub( data.oldPos ).mult( 1 / dt ); From ee6ed313671cab463383d07e37af5d428bec06b9 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Fri, 21 Nov 2014 16:05:12 -0500 Subject: [PATCH 056/110] missed semicolon --- src/core/body.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/body.js b/src/core/body.js index 1a4bbd0d..582496e2 100644 --- a/src/core/body.js +++ b/src/core/body.js @@ -30,7 +30,7 @@ // Mk = Mk-1+ (xk – Mk-1)/k // Sk = Sk-1 + (xk – Mk-1)*(xk – Mk). m += x / k; - s += x * (v - m) + s += x * (v - m); } // Running vector average From 67e19f27c84aafddb25b8412e0c57a8cd9a13d1b Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Fri, 21 Nov 2014 16:02:46 -0500 Subject: [PATCH 057/110] added options for sleeping, world.wakeUpAll() --- src/core/body.js | 31 +++++++++++++++++++--- src/core/world.js | 52 ++++++++++++++++++++++++++++++------- src/renderers/debug.js | 35 ++++++++++++++++++++----- test/collision-sandbox.html | 2 +- 4 files changed, 100 insertions(+), 20 deletions(-) diff --git a/src/core/body.js b/src/core/body.js index 582496e2..0cbe68c4 100644 --- a/src/core/body.js +++ b/src/core/body.js @@ -162,6 +162,7 @@ } }; + // private storage for sleeping this._sleepAngPosMean = 0; this._sleepAngPosVariance = 0; this._sleepPosMean = new vector(); @@ -279,6 +280,15 @@ **/ }, + /** + * Body#sleep( [dt] ) -> Boolean + * - dt (Number): Time to advance the idle time + * - dt (Boolean): If `true`, the body will be forced to sleep. If `false`, the body will be forced to awake. + * + * Get and/or set whether the body is asleep. + * + * If called with a time (in ms), the time will be added to the idle time and sleep conditions will be checked. + **/ sleep: function( dt ){ if ( dt === true ){ @@ -303,8 +313,23 @@ return this.asleep; }, + /** + * Body#sleepCheck( [dt] ) + * - dt (Number): Time to advance the idle time + * + * Check if the body should be sleeping. + * + * Call with no arguments if some event could possibly wake up the body. This will force the body to recheck. + **/ sleepCheck: function( dt ){ + var opts = this._world && this._world.options; + + // if sleeping disabled. stop. + if ( this.sleepDisabled || (opts && opts.sleepDisabled) ){ + return; + } + var limit ,v ,d @@ -322,7 +347,7 @@ if ( this.asleep ){ // check velocity v = this.state.vel.norm() + Math.abs(r * this.state.angular.vel); - limit = this.sleepSpeedLimit || (this._world && this._world.sleepSpeedLimit) || 0; + limit = this.sleepSpeedLimit || (opts && opts.sleepSpeedLimit) || 0; if ( v >= limit ){ this.sleep( false ); @@ -334,11 +359,11 @@ pushRunningVectorAvg( this._sleepMeanK, this._sleepPosMean, this._sleepPosVariance, this.state.pos ); pushRunningAvg( this._sleepMeanK, this._sleepAngPosMean, this._sleepAngPosVariance, this.state.angular.pos ); v = this._sleepPosVariance.norm() + Math.abs(r * this._sleepAngPosVariance); - limit = this.sleepVarianceLimit || (this._world && this._world.sleepVarianceLimit) || 0; + limit = this.sleepVarianceLimit || (opts && opts.sleepVarianceLimit) || 0; if ( v <= limit ){ // check idle time - limit = this.sleepTimeLimit || (this._world && this._world.sleepTimeLimit) || 0; + limit = this.sleepTimeLimit || (opts && opts.sleepTimeLimit) || 0; this.sleepIdleTime = (this.sleepIdleTime || 0) + dt; if ( this.sleepIdleTime > limit ){ diff --git a/src/core/world.js b/src/core/world.js index b7043d1d..9d52c30d 100644 --- a/src/core/world.js +++ b/src/core/world.js @@ -35,7 +35,16 @@ webworker: false, // NOT YET IMPLEMENTED // default integrator - integrator: 'verlet' + integrator: 'verlet', + + // is sleeping disabled? + sleepDisabled: false, + // speed at which bodies wake up + sleepSpeedLimit: 0.1, + // variance in position below which bodies fall asleep + sleepVarianceLimit: 2, + // time (ms) before sleepy bodies fall asleep + sleepTimeLimit: 500 }; // begin world definitions @@ -55,12 +64,22 @@ * * ```javascript * { - * // default timestep - * timestep: 1000.0 / 120, - * // maximum number of iterations per step - * maxIPF: 16, - * // default integrator - * integrator: 'verlet' + * // default timestep + * timestep: 1000.0 / 120, + * // maximum number of iterations per step + * maxIPF: 16, + * + * // default integrator + * integrator: 'verlet', + * + * // is sleeping disabled? + * sleepDisabled: false, + * // speed at which bodies wake up + * sleepSpeedLimit: 0.1, + * // variance in position below which bodies fall asleep + * sleepVarianceLimit: 2, + * // time (ms) before sleepy bodies fall asleep + * sleepTimeLimit: 500 * } * ``` * @@ -143,9 +162,6 @@ this._paused = false; this._warp = 1; this._time = 0; - this.sleepSpeedLimit = 0.1; - this.sleepVarianceLimit = 2; - this.sleepTimeLimit = 500; // set options this.options = Physics.util.options( defaults ); @@ -426,6 +442,22 @@ return this._dt; }, + /** chainable + * Physics.world#wakeUpAll() -> this + * + (this): for chaining + * + * Wake up all bodies in world. + **/ + wakeUpAll: function(){ + var i = 0 + ,l = this._bodies.length + ; + + for ( i = 0; i < l; i++ ){ + this._bodies[ i ].sleep( false ); + } + }, + /** chainable * Physics.world#addBehavior( behavior ) -> this * - behavior (Behavior): The behavior to add diff --git a/src/renderers/debug.js b/src/renderers/debug.js index 0ce69264..641a8568 100644 --- a/src/renderers/debug.js +++ b/src/renderers/debug.js @@ -235,28 +235,49 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ self._world.options({ maxIPF: m }); } } + ,get sleepDisabled(){ + return self._world ? self._world.options.sleepDisabled : false; + } + ,set sleepDisabled( t ){ + if ( self._world ){ + self._world.options.sleepDisabled = t; + if ( t ){ + self._world.wakeUpAll(); + } + } + } ,get sleepTimeLimit(){ - return self._world ? self._world.sleepTimeLimit : 500; + return self._world ? self._world.options.sleepTimeLimit : 500; } ,set sleepTimeLimit( t ){ if ( self._world ){ - self._world.sleepTimeLimit = t; + self._world.options.sleepTimeLimit = t; } } ,get sleepSpeedLimit(){ - return self._world ? self._world.sleepSpeedLimit : 0.01; + return self._world ? self._world.options.sleepSpeedLimit : 0.01; } ,set sleepSpeedLimit( t ){ if ( self._world ){ - self._world.sleepSpeedLimit = t; + self._world.options.sleepSpeedLimit = t; } } ,get sleepVarianceLimit(){ - return self._world ? self._world.sleepVarianceLimit : 2; + return self._world ? self._world.options.sleepVarianceLimit : 2; } ,set sleepVarianceLimit( t ){ if ( self._world ){ - self._world.sleepVarianceLimit = t; + self._world.options.sleepVarianceLimit = t; + } + } + ,get integrator(){ + return self._world ? self._world.integrator().name : 'verlet'; + } + ,set integrator( t ){ + var intr; + if ( self._world ){ + intr = self._world.integrator(); + self._world.integrator( Physics.integrator(t, intr.options) ); } } }; @@ -272,9 +293,11 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ } f = gui.addFolder( 'General' ); + f.add( getset, 'integrator', [ 'improved-euler', 'verlet', 'velocity-verlet' ]); f.add( getset, 'timestep', 1, 20).step( 1 ); f.add( getset, 'maxIPF', 1, 100).step( 1 ); f.add( getset, 'warp', 0.01, 2); + f.add( getset, 'sleepDisabled'); f.add( getset, 'sleepTimeLimit', 1, 10000).step( 10 ); f.add( getset, 'sleepSpeedLimit', 0.001, 1); f.add( getset, 'sleepVarianceLimit', 0.01, 100); diff --git a/test/collision-sandbox.html b/test/collision-sandbox.html index c1c5abd3..305cd02d 100644 --- a/test/collision-sandbox.html +++ b/test/collision-sandbox.html @@ -249,7 +249,7 @@ vertices: polygons[i].v, angle: polygons[i].angle, - restitution: 0.5, + restitution: 1, cof: 1 }) ); From a910cbeabf5766c1938a345e16836000efbb84e3 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Mon, 24 Nov 2014 17:00:59 -0500 Subject: [PATCH 058/110] Revert "minor friction fix for impulse response" This reverts commit 4575353c1a84cb88605cb80d8602754c3da3067c. --- src/behaviors/body-impulse-response.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/behaviors/body-impulse-response.js b/src/behaviors/body-impulse-response.js index 2b6df9ba..b64f834b 100644 --- a/src/behaviors/body-impulse-response.js +++ b/src/behaviors/body-impulse-response.js @@ -184,14 +184,7 @@ Physics.behavior('body-impulse-response', function( parent ){ bodyA.state.angular.vel += impulse * invMoiA * rAreg; } - // recalculate vreg - vAB.clone( bodyB.state.vel ) - .vadd( tmp.clone(rB).perp().mult( bodyB.state.angular.vel ) ) - .vsub( bodyA.state.vel ) - .vsub( tmp.clone(rA).perp().mult( bodyA.state.angular.vel ) ) - ; - - vreg = vAB.proj( perp ); + // inContact = (impulse < 0.004); // if we have friction and a relative velocity perpendicular to the normal if ( cof && vreg ){ From 517f7c0039132bab657cae39e23fb06432bc048d Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Mon, 24 Nov 2014 17:10:21 -0500 Subject: [PATCH 059/110] minor fix --- src/renderers/debug.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderers/debug.js b/src/renderers/debug.js index 641a8568..a04b9f10 100644 --- a/src/renderers/debug.js +++ b/src/renderers/debug.js @@ -277,7 +277,7 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ var intr; if ( self._world ){ intr = self._world.integrator(); - self._world.integrator( Physics.integrator(t, intr.options) ); + self._world.integrator( Physics.integrator(t, Physics.util.extend({}, intr, null)) ); } } }; From 194b69991100e5163a6927634040232bdc195668 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Mon, 24 Nov 2014 20:52:59 -0500 Subject: [PATCH 060/110] fix for pixi renderer meta drawing --- src/renderers/pixi-renderer.js | 10 +++--- test/collision-sandbox.html | 4 +-- test/pixi-sandbox.html | 60 ++++++++++++++++++++++++++++++---- 3 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/renderers/pixi-renderer.js b/src/renderers/pixi-renderer.js index 7ae45be6..0c8c8ddd 100644 --- a/src/renderers/pixi-renderer.js +++ b/src/renderers/pixi-renderer.js @@ -53,6 +53,11 @@ Physics.renderer('pixi', function( parent ){ ,violet: '0x542437' ,blue: '0x53777A' } + ,fontStyles = { + font: "18px monospace", + fill: "black", + align: "left" + } ,defaults = { @@ -395,11 +400,6 @@ Physics.renderer('pixi', function( parent ){ drawMeta: function( meta ){ if (!this.meta.loaded){ // define the font styles here - var fontStyles = { - font: "18px Snippet", - fill: "white", - align: "left" - }; this.meta.fps = new PIXI.Text('FPS: ' + meta.fps.toFixed(2), fontStyles); this.meta.fps.position.x = 15; this.meta.fps.position.y = 5; diff --git a/test/collision-sandbox.html b/test/collision-sandbox.html index 305cd02d..ac6f0799 100644 --- a/test/collision-sandbox.html +++ b/test/collision-sandbox.html @@ -281,8 +281,8 @@ Physics({ timestep: 6, maxIPF: 24 }, [ setup - ,addBodies - // ,lotsOfCircles + // ,addBodies + ,lotsOfCircles // ,lotsOfRects //,initEvents ]); diff --git a/test/pixi-sandbox.html b/test/pixi-sandbox.html index 24d8d482..b385f76b 100644 --- a/test/pixi-sandbox.html +++ b/test/pixi-sandbox.html @@ -33,6 +33,11 @@ var bodyStyles = { strokeStyle: '0x888888', fillStyle: 'transparent', lineWidth: 2, angleIndicator: '0xcccc44' }; + function random(min, max){ + var a = (max - min); + return a * Math.random() + min; + } + function setup(world) { var renderer = Physics.renderer('pixi', { @@ -141,14 +146,55 @@ }); } + function lotsOfCircles( world ){ + + var circles = []; + for ( var i = 0; i < 200; i++ ){ + circles.push( + Physics.body('circle', { + x: viewWidth * Math.random(), + y: viewHeight * Math.random(), + radius: random(10, 20), + mass: random(1, 10), + //cof: 0, + //vy: -0.2, + // vx: 0.1, + restitution: 1 + }) + ); + } + + world.add( circles ); + } + + function lotsOfRects( world ){ + + var rects = []; + for ( var i = 0; i < 100; i++ ){ + rects.push( + Physics.body('rectangle', { + x: viewWidth * Math.random(), + y: viewHeight * Math.random(), + width: random(30, 60), + height: random(30, 60), + mass: random(1, 10), + //cof: 0, + //vy: -0.2, + // vx: 0.1, + restitution: 1 + }) + ); + } + + world.add( rects ); + } + function addBodies( world ){ world.add( Physics.body('circle', { x: viewWidth/2, y: viewHeight - 20,//+100, radius: 20, - width: 40, - height: 40, mass: 1.4, //cof: 0, //vy: -0.2, @@ -160,10 +206,8 @@ x: viewWidth/2, y: viewHeight - 100,//+100, radius: 20, - width: 40, - height: 40, mass: 1.4, - //cof: 0, + // cof: 0.1, //vy: -0.2, // vx: 0.1, restitution: 0.9 @@ -182,7 +226,7 @@ vertices: polygons[i].v, angle: polygons[i].angle, - restitution: 0.5, + restitution: 1, cof: 1 }) ); @@ -214,7 +258,9 @@ Physics({ timestep: 6, maxIPF: 24 }, [ setup - ,addBodies + // ,addBodies + ,lotsOfCircles + // ,lotsOfRects //,initEvents ]); From 62ff4ca4b096a1c3fbe4426582496b7e86c6455b Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Tue, 25 Nov 2014 15:43:46 -0500 Subject: [PATCH 061/110] broken work in progress --- src/bodies/compound.js | 82 +++++++++++++++++++++++++ src/bodies/convex-polygon.js | 6 +- src/core/body.js | 31 ++++++++++ src/geometries/compound.js | 115 +++++++++++++++++++++++++++++++++++ 4 files changed, 231 insertions(+), 3 deletions(-) create mode 100644 src/bodies/compound.js create mode 100644 src/geometries/compound.js diff --git a/src/bodies/compound.js b/src/bodies/compound.js new file mode 100644 index 00000000..1a06880b --- /dev/null +++ b/src/bodies/compound.js @@ -0,0 +1,82 @@ +/* + * @requires geometries/compound + */ + /** + * class CompoundBody < Body + * + * Physics.body('compound') + * + * Body for convex polygons. The position of the body is the centroid of the polygon. + * + * Additional config options: + * + * - vertices: Array of [[Vectorish]] objects representing the polygon vertices in clockwise (or counterclockwise) order. + * + * Example: + * + * ```javascript + * var thing = Physics.body('compound', { + * // place the centroid of the body at (300, 200) + * x: 300, + * y: 200, + * // the centroid is automatically calculated and used to position the shape + * children: [ + * body1, + * body2, + * // ... + * ] + * }); + * ``` + **/ +Physics.body('compound', function( parent ){ + + var defaults = { + + }; + + return { + + // extended + init: function( options ){ + + // call parent init method + parent.init.call(this, options); + + this.children = []; + this.geometry = Physics.geometry('compound'); + this.addChildren( options.children ); + }, + + addChild: function( body ){ + + this.children.push( body ); + this.geometry.addChild( body.state.pos.x, body.state.pos.y, body.geometry ); + this.recalc(); + + return this; + }, + + addChildren: function( arr ){ + + this.geometry.addChildren( arr ); + this.recalc(); + + return this; + }, + + clear: function(){ + this.children = []; + this.geometry.clear(); + this.recalc(); + + return this; + }, + + // extended + recalc: function(){ + parent.recalc.call(this); + // moment of inertia + this.moi = Physics.geometry.getPolygonMOI( this.geometry.vertices ); + } + }; +}); diff --git a/src/bodies/convex-polygon.js b/src/bodies/convex-polygon.js index cd1b015c..1332bf9c 100644 --- a/src/bodies/convex-polygon.js +++ b/src/bodies/convex-polygon.js @@ -1,7 +1,7 @@ /* * @requires geometries/convex-polygon */ - /** + /** * class ConvexPolygonBody < Body * * Physics.body('convex-polygon') @@ -9,7 +9,7 @@ * Body for convex polygons. The position of the body is the centroid of the polygon. * * Additional config options: - * + * * - vertices: Array of [[Vectorish]] objects representing the polygon vertices in clockwise (or counterclockwise) order. * * Example: @@ -33,7 +33,7 @@ Physics.body('convex-polygon', function( parent ){ var defaults = { - + }; return { diff --git a/src/core/body.js b/src/core/body.js index 0cbe68c4..58b93809 100644 --- a/src/core/body.js +++ b/src/core/body.js @@ -479,4 +479,35 @@ } }); + /** + * Body.getCOM( bodies[, com] ) -> Physics.vector + * - bodies (Array): The list of bodies + * - com (Physics.vector): The vector to put result into. A new vector will be created if not provided. + * + (Physics.vector): The center of mass position + * + * Get center of mass position from list of bodies. + **/ + Physics.body.getCOM = function( bodies, com ){ + + var b + ,pos + ,i + ,l + ,M + ; + + com = com || new Physics.vector(); + + for ( i = 0, l = bodies.length; i < l; i++ ){ + b = bodies[ i ]; + pos = b.state.pos; + com.add( pos._[0] * b.mass, pos._[1] * b.mass ); + M += b.mass; + } + + com.mult( 1 / M ); + + return com; + }; + }()); diff --git a/src/geometries/compound.js b/src/geometries/compound.js new file mode 100644 index 00000000..5e339bb1 --- /dev/null +++ b/src/geometries/compound.js @@ -0,0 +1,115 @@ +/** + * class CompoundGeometry < Geometry + * + * Physics.geometry('compound') + * + * Geometry for compound shapes. + * + * Additional config options: + * + * - children: children + * + * Example: + * + * ```javascript + * var thing = Physics.geometry('compound', { + * // the centroid is automatically calculated and used to position the shape + * vertices: [ + * { x: 0, y: -30 }, + * { x: -29, y: -9 }, + * { x: -18, y: 24 }, + * { x: 18, y: 24 }, + * { x: 29, y: -9 } + * ] + * }); + * ``` + **/ +Physics.geometry('compound', function( parent ){ + + var ERROR_NOT_CONVEX = 'Error: The vertices specified do not match that of a _convex_ polygon.'; + + var defaults = { + + }; + + return { + + // extended + init: function( options ){ + + var self = this; + + // call parent init method + parent.init.call(this, options); + + this.options.defaults( defaults ); + this.options( options ); + + }, + + // extended + aabb: function( angle ){ + + if (!angle && this._aabb){ + return Physics.aabb.clone( this._aabb ); + } + + var scratch = Physics.scratchpad() + ,p = scratch.vector() + ,trans = scratch.transform().setRotation( angle || 0 ) + ,xaxis = scratch.vector().set( 1, 0 ).rotateInv( trans ) + ,yaxis = scratch.vector().set( 0, 1 ).rotateInv( trans ) + ,xmax = this.getFarthestHullPoint( xaxis, p ).proj( xaxis ) + ,xmin = - this.getFarthestHullPoint( xaxis.negate(), p ).proj( xaxis ) + ,ymax = this.getFarthestHullPoint( yaxis, p ).proj( yaxis ) + ,ymin = - this.getFarthestHullPoint( yaxis.negate(), p ).proj( yaxis ) + ,aabb + ; + + aabb = Physics.aabb( xmin, ymin, xmax, ymax ); + + if (!angle){ + // if we don't have an angle specified (or it's zero) + // then we can cache this result + this._aabb = Physics.aabb.clone( aabb ); + } + + scratch.done(); + return aabb; + }, + + // extended + getFarthestHullPoint: function( dir, result, data ){ + + var verts = this.vertices + ,val + ,prev + ,l = verts.length + ,i = 2 + ,idx + ; + + result = result || new Physics.vector(); + + return result; + }, + + // extended + getFarthestCorePoint: function( dir, result, margin ){ + + var norm + ,scratch = Physics.scratchpad() + ,next = scratch.vector() + ,prev = scratch.vector() + ,verts = this.vertices + ,l = verts.length + ,mag + ,sign = this._area > 0 + ,data = {} + ; + + scratch.done(); + return result; + } + }; +}); From 8372642b2c947f6d9dd05fc1739fa6b89d98f8a8 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Tue, 25 Nov 2014 16:59:18 -0500 Subject: [PATCH 062/110] attempt at com offset --- src/behaviors/body-collision-detection.js | 8 ++++--- src/behaviors/body-impulse-response.js | 8 +++++-- src/bodies/convex-polygon.js | 9 +++++--- src/core/body.js | 26 ++++++++++++++++------- src/geometries/convex-polygon.js | 14 +++--------- src/renderers/canvas.js | 5 +++-- src/renderers/debug.js | 17 +++++++++------ test/collision-sandbox.html | 12 ++++++----- test/contact-sandbox.html | 1 + 9 files changed, 60 insertions(+), 40 deletions(-) diff --git a/src/behaviors/body-collision-detection.js b/src/behaviors/body-collision-detection.js index 466b0b42..5157d7de 100644 --- a/src/behaviors/body-collision-detection.js +++ b/src/behaviors/body-collision-detection.js @@ -78,7 +78,9 @@ Physics.behavior('body-collision-detection', function( parent ){ fn.useCore = false; fn.margin = 0; fn.tA.setTranslation( bodyA.state.pos ).setRotation( bodyA.state.angular.pos ); + fn.tA.v.vadd( bodyA.offset ); fn.tB.setTranslation( bodyB.state.pos ).setRotation( bodyB.state.angular.pos ); + fn.tB.v.vadd( bodyB.offset ); fn.bodyA = bodyA; fn.bodyB = bodyB; @@ -110,7 +112,7 @@ Physics.behavior('body-collision-detection', function( parent ){ // just check the overlap first support = getSupportFn( bodyA, bodyB ); - d.clone( bodyA.state.pos ).vsub( bodyB.state.pos ); + d.clone( bodyA.state.pos ).vadd( bodyA.offset ).vsub( bodyB.state.pos ).vsub( bodyB.offset ); result = Physics.gjk(support, d, true); if ( result.overlap ){ @@ -154,7 +156,7 @@ Physics.behavior('body-collision-detection', function( parent ){ collision.norm = d.clone( result.closest.b ).vsub( tmp.clone( result.closest.a ) ).normalize().values(); collision.mtv = d.mult( overlap ).values(); // get a corresponding hull point for one of the core points.. relative to body A - collision.pos = d.clone( collision.norm ).mult( support.marginA ).vadd( tmp.clone( result.closest.a ) ).vsub( bodyA.state.pos ).values(); + collision.pos = d.clone( collision.norm ).mult( support.marginA ).vadd( tmp.clone( result.closest.a ) ).vsub( bodyA.state.pos ).vsub( bodyA.offset ).values(); } return scratch.done( collision ); @@ -177,7 +179,7 @@ Physics.behavior('body-collision-detection', function( parent ){ ,collision = false ; - d.clone( bodyB.state.pos ).vsub( bodyA.state.pos ); + d.clone( bodyB.state.pos ).vadd( bodyB.offset ).vsub( bodyA.state.pos ).vsub( bodyA.offset ); overlap = d.norm() - (bodyA.geometry.radius + bodyB.geometry.radius); // hmm... they overlap exactly... choose a direction diff --git a/src/behaviors/body-impulse-response.js b/src/behaviors/body-impulse-response.js index b64f834b..9c3c2640 100644 --- a/src/behaviors/body-impulse-response.js +++ b/src/behaviors/body-impulse-response.js @@ -92,11 +92,15 @@ Physics.behavior('body-impulse-response', function( parent ){ ,n = scratch.vector().clone( normal ) // vector perpendicular to n ,perp = scratch.vector().clone( n ).perp() + ,tmp = scratch.vector() // collision point from A's center ,rA = scratch.vector().clone( point ) // collision point from B's center - ,rB = scratch.vector().clone( point ).vadd( bodyA.state.pos ).vsub( bodyB.state.pos ) - ,tmp = scratch.vector() + ,rB = scratch.vector().clone( point ) + .vadd( bodyA.state.pos ) + .vadd( tmp.clone( bodyA.offset ).rotate( -bodyA.state.angular.pos ) ) + .vsub( bodyB.state.pos ) + .vsub( tmp.clone( bodyB.offset ).rotate( -bodyB.state.angular.pos ) ) ,angVelA = bodyA.state.angular.vel ,angVelB = bodyB.state.angular.vel // relative velocity towards B at collision point diff --git a/src/bodies/convex-polygon.js b/src/bodies/convex-polygon.js index cd1b015c..c58d810e 100644 --- a/src/bodies/convex-polygon.js +++ b/src/bodies/convex-polygon.js @@ -1,7 +1,7 @@ /* * @requires geometries/convex-polygon */ - /** + /** * class ConvexPolygonBody < Body * * Physics.body('convex-polygon') @@ -9,7 +9,7 @@ * Body for convex polygons. The position of the body is the centroid of the polygon. * * Additional config options: - * + * * - vertices: Array of [[Vectorish]] objects representing the polygon vertices in clockwise (or counterclockwise) order. * * Example: @@ -33,7 +33,7 @@ Physics.body('convex-polygon', function( parent ){ var defaults = { - + }; return { @@ -50,6 +50,9 @@ Physics.body('convex-polygon', function( parent ){ vertices: options.vertices }); + // offset the centroid to match the polygon centroid + this.offset.vadd( Physics.geometry.getPolygonCentroid( options.vertices ) ); + this.recalc(); }, diff --git a/src/core/body.js b/src/core/body.js index 0cbe68c4..20f5466a 100644 --- a/src/core/body.js +++ b/src/core/body.js @@ -102,6 +102,7 @@ **/ init: function( options ){ + var self = this; var vector = Physics.vector; /** related to: Physics.util.options @@ -122,6 +123,9 @@ **/ // all options get copied onto the body. this.options = Physics.util.options( defaults, this ); + this.options.onChange(function( opts ){ + self.offset = new vector( opts.offset ); + }); this.options( options ); /** @@ -142,18 +146,18 @@ * ``` **/ this.state = { - pos: vector( this.x, this.y ), - vel: vector( this.vx, this.vy ), - acc: vector(), + pos: new vector( this.x, this.y ), + vel: new vector( this.vx, this.vy ), + acc: new vector(), angular: { pos: this.angle || 0.0, vel: this.angularVelocity || 0.0, acc: 0.0 }, old: { - pos: vector(), - vel: vector(), - acc: vector(), + pos: new vector(), + vel: new vector(), + acc: new vector(), angular: { pos: 0.0, vel: 0.0, @@ -203,6 +207,12 @@ * The mass. **/ + /** + * Body#offset + * + * The vector offsetting the true position of the body. + **/ + /** * Body#restitution = 1.0 * @@ -460,8 +470,8 @@ ,aabb = this.geometry.aabb( angle ) ; - aabb.x += this.state.pos.x; - aabb.y += this.state.pos.y; + aabb.x += this.state.pos._[0] + this.offset._[0]; + aabb.y += this.state.pos._[1] + this.offset._[1]; return aabb; }, diff --git a/src/geometries/convex-polygon.js b/src/geometries/convex-polygon.js index d8bf99c1..4b1b5ea6 100644 --- a/src/geometries/convex-polygon.js +++ b/src/geometries/convex-polygon.js @@ -60,29 +60,21 @@ Physics.geometry('convex-polygon', function( parent ){ **/ setVertices: function( hull ){ - var scratch = Physics.scratchpad() - ,transl = scratch.transform() - ,verts = this.vertices = [] + var verts = this.vertices = [] ; if ( !Physics.geometry.isPolygonConvex( hull ) ){ throw ERROR_NOT_CONVEX; } - transl.setRotation( 0 ); - transl.setTranslation( Physics.geometry.getPolygonCentroid( hull ).negate() ); - - // translate each vertex so that the centroid is at the origin - // then add the vertex as a vector to this.vertices + // add the vertex as a vector to this.vertices for ( var i = 0, l = hull.length; i < l; ++i ){ - verts.push( new Physics.vector( hull[ i ] ).translate( transl ) ); + verts.push( new Physics.vector( hull[ i ] ) ); } this._area = Physics.geometry.getPolygonArea( verts ); - this._aabb = false; - scratch.done(); return this; }, diff --git a/src/renderers/canvas.js b/src/renderers/canvas.js index 25d33eb9..b0898d41 100644 --- a/src/renderers/canvas.js +++ b/src/renderers/canvas.js @@ -664,6 +664,7 @@ Physics.renderer('canvas', function( proto ){ drawBody: function( body, view, ctx, offset ){ var pos = body.state.pos + ,os = body.offset ,v = body.state.vel ,t = this._interpolateTime || 0 ,x @@ -676,8 +677,8 @@ Physics.renderer('canvas', function( proto ){ ctx = ctx || this.ctx; // interpolate positions - x = pos.x + offset.x + v.x * t; - y = pos.y + offset.y + v.y * t; + x = pos.x + os.x + offset.x + v.x * t; + y = pos.y + ox.y + offset.y + v.y * t; ang = body.state.angular.pos + body.state.angular.vel * t; ctx.save(); diff --git a/src/renderers/debug.js b/src/renderers/debug.js index a04b9f10..e94fb89e 100644 --- a/src/renderers/debug.js +++ b/src/renderers/debug.js @@ -170,15 +170,17 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ var scratch = Physics.scratchpad() ,opts = this.options + ,x = intr.tracker.body.state.pos.x + intr.tracker.body.offset.x + ,y = intr.tracker.body.state.pos.y + intr.tracker.body.offset.y ,from = scratch.vector().set( intr.val.x, 0 ) - ,to = scratch.vector().set( intr.val.x, intr.tracker.body.state.pos.y ) + ,to = scratch.vector().set( intr.val.x, y ) ; this.drawLine( from, to, opts[ intr.type ? 'intervalMaxColor' : 'intervalMinColor' ], ctx ); this.drawCircle( from.x, from.y, 4, opts[ intr.type ? 'intervalMaxColor' : 'intervalMinColor' ], ctx ); from.set( 0, intr.val.y ); - to.set( intr.tracker.body.state.pos.x, intr.val.y ); + to.set( x, intr.val.y ); this.drawLine( from, to, opts[ intr.type ? 'intervalMaxColor' : 'intervalMinColor' ], ctx ); this.drawCircle( from.x, from.y, 4, opts[ intr.type ? 'intervalMaxColor' : 'intervalMinColor' ], ctx ); @@ -189,7 +191,7 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ drawContact: function( c, ctx ){ var scratch = Physics.scratchpad() - ,from = scratch.vector().clone( c.pos ).vadd( c.bodyA.state.pos ) + ,from = scratch.vector().clone( c.pos ).vadd( c.bodyA.state.pos ).vadd( c.bodyA.offset ) ,to = scratch.vector().clone( from ).vsub( scratch.vector().clone( c.mtv ) ) ,opts = this.options ; @@ -339,6 +341,7 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ drawBody: function( body, view, ctx, offset ){ var pos = body.state.pos + ,os = body.offset ,v = body.state.vel ,t = this._interpolateTime || 0 ,x @@ -351,14 +354,15 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ ctx = ctx || this.ctx; // interpolate positions - x = pos.x + offset.x + v.x * t; - y = pos.y + offset.y + v.y * t; + x = pos.x + os.x + offset.x + v.x * t; + y = pos.y + os.y + offset.y + v.y * t; ang = body.state.angular.pos + body.state.angular.vel * t; ctx.save(); ctx.translate( x, y ); ctx.rotate( ang ); ctx.drawImage(view, -view.width/2, -view.height/2, view.width, view.height); + this.drawCircle( -os.x, -os.y, 2, 'red' ); ctx.restore(); if ( this.options.drawAABB ){ @@ -371,7 +375,7 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ // draw the non-interpolated body position body._debugView = body._debugView || this.createView(body.geometry, this.options.realBodyStyle); ctx.save(); - ctx.translate(pos.x + offset.x, pos.y + offset.y); + ctx.translate(pos.x + os.x + offset.x, pos.y + os.y + offset.y); ctx.rotate(body.state.angular.pos); ctx.drawImage(body._debugView, -body._debugView.width * 0.5, -body._debugView.height * 0.5); ctx.restore(); @@ -396,6 +400,7 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ ctx.font = '12px monospace'; ctx.strokeText('r: ('+x.toFixed(0)+', '+y.toFixed(0)+')', x, y-8); ctx.strokeText('v: ('+format(v.x)+', '+format(v.y)+')', x, y+12); + ctx.strokeText('o: ('+format(os.x)+', '+format(os.y)+')', x, y+26); ctx.shadowBlur = 0; ctx.shadowColor = ''; } diff --git a/test/collision-sandbox.html b/test/collision-sandbox.html index ac6f0799..ad837567 100644 --- a/test/collision-sandbox.html +++ b/test/collision-sandbox.html @@ -219,6 +219,7 @@ y: viewHeight - 20,//+100, radius: 20, mass: 1.4, + offset: Physics.vector(10, 2), //cof: 0, //vy: -0.2, // vx: 0.1, @@ -237,7 +238,7 @@ })); var polygons = [ - {x:410, y:220, v:[{x: 0, y: 80}, {x: 80, y: 0}, {x: 0, y: -80},{x: -30, y: -30}, {x: -30, y: 30}] }, + {x:410, y:220, v:[{x: 0, y: 80}, {x: 80, y: 0}, {x: 0, y: -80},{x: -30, y: -30}, {x: -30, y: 30}], offset: { x: 50, y: 100 } }, {x:290, y:320, v:[{x: 0, y: 80}, {x: 80, y: 0}, {x: 0, y: -80},{x: -30, y: -30}, {x: -30, y: 30}], angle: Math.PI } ]; @@ -248,7 +249,7 @@ y: polygons[i].y, vertices: polygons[i].v, angle: polygons[i].angle, - + offset: polygons[i].offset, restitution: 1, cof: 1 }) @@ -258,7 +259,7 @@ var rectangles = [ { x: 100, y: 60, w: 50, h: 20, mass: 1 } ,{ x: 300, y: 60, w: 100, h: 80, mass: 2 } - ,{ x: 500, y: 100, w: 150, h: 300, mass: 3 } + ,{ x: 500, y: 100, w: 150, h: 300, mass: 3, offset: {x: 100, y: 0} } ]; for(var i = 0;i < rectangles.length;i++){ @@ -270,6 +271,7 @@ height: rectangles[i].h, angle: rectangles[i].angle, mass: rectangles[i].mass, + offset: rectangles[i].offset, restitution: 0.6, cof: 1 @@ -281,8 +283,8 @@ Physics({ timestep: 6, maxIPF: 24 }, [ setup - // ,addBodies - ,lotsOfCircles + ,addBodies + // ,lotsOfCircles // ,lotsOfRects //,initEvents ]); diff --git a/test/contact-sandbox.html b/test/contact-sandbox.html index 2d80a824..99ab2d09 100644 --- a/test/contact-sandbox.html +++ b/test/contact-sandbox.html @@ -149,6 +149,7 @@ width: 40, height: 40, mass: 1.4, + offset: Physics.vector(10, 2), //cof: 0, //vy: -0.2, // vx: 0.1, From 23262f3ae07f0252d6dc954628318ce8a0a2910a Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Tue, 25 Nov 2014 22:21:02 -0500 Subject: [PATCH 063/110] better attempt at #107 --- src/behaviors/body-collision-detection.js | 39 ++++++++++++----------- src/behaviors/body-impulse-response.js | 2 -- src/core/body.js | 17 ++++++++-- src/renderers/debug.js | 12 ++++--- test/contact-sandbox.html | 3 ++ 5 files changed, 44 insertions(+), 29 deletions(-) diff --git a/src/behaviors/body-collision-detection.js b/src/behaviors/body-collision-detection.js index 5157d7de..0c90688e 100644 --- a/src/behaviors/body-collision-detection.js +++ b/src/behaviors/body-collision-detection.js @@ -55,11 +55,11 @@ Physics.behavior('body-collision-detection', function( parent ){ ; if ( fn.useCore ){ - vA = bodyA.geometry.getFarthestCorePoint( searchDir.rotateInv( tA ), vA, marginA ).transform( tA ); - vB = bodyB.geometry.getFarthestCorePoint( searchDir.rotate( tA ).rotateInv( tB ).negate(), vB, marginB ).transform( tB ); + vA = bodyA.geometry.getFarthestCorePoint( searchDir.rotateInv( tA ), vA, marginA ).vadd( bodyA.offset ).transform( tA ); + vB = bodyB.geometry.getFarthestCorePoint( searchDir.rotate( tA ).rotateInv( tB ).negate(), vB, marginB ).vadd( bodyB.offset ).transform( tB ); } else { - vA = bodyA.geometry.getFarthestHullPoint( searchDir.rotateInv( tA ), vA ).transform( tA ); - vB = bodyB.geometry.getFarthestHullPoint( searchDir.rotate( tA ).rotateInv( tB ).negate(), vB ).transform( tB ); + vA = bodyA.geometry.getFarthestHullPoint( searchDir.rotateInv( tA ), vA ).vadd( bodyA.offset ).transform( tA ); + vB = bodyB.geometry.getFarthestHullPoint( searchDir.rotate( tA ).rotateInv( tB ).negate(), vB ).vadd( bodyB.offset ).transform( tB ); } searchDir.negate().rotate( tB ); @@ -77,10 +77,8 @@ Physics.behavior('body-collision-detection', function( parent ){ fn.useCore = false; fn.margin = 0; - fn.tA.setTranslation( bodyA.state.pos ).setRotation( bodyA.state.angular.pos ); - fn.tA.v.vadd( bodyA.offset ); - fn.tB.setTranslation( bodyB.state.pos ).setRotation( bodyB.state.angular.pos ); - fn.tB.v.vadd( bodyB.offset ); + fn.tA.setRotation( bodyA.state.angular.pos ).setTranslation( bodyA.state.pos ); + fn.tB.setRotation( bodyB.state.angular.pos ).setTranslation( bodyB.state.pos ); fn.bodyA = bodyA; fn.bodyB = bodyB; @@ -100,6 +98,7 @@ Physics.behavior('body-collision-detection', function( parent ){ var scratch = Physics.scratchpad() ,d = scratch.vector() ,tmp = scratch.vector() + ,os = scratch.vector() ,overlap ,result ,support @@ -112,7 +111,11 @@ Physics.behavior('body-collision-detection', function( parent ){ // just check the overlap first support = getSupportFn( bodyA, bodyB ); - d.clone( bodyA.state.pos ).vadd( bodyA.offset ).vsub( bodyB.state.pos ).vsub( bodyB.offset ); + d.clone( bodyA.state.pos ) + .vadd( bodyA.getGlobalOffset( os ) ) + .vsub( bodyB.state.pos ) + .vsub( bodyB.getGlobalOffset( os ) ) + ; result = Physics.gjk(support, d, true); if ( result.overlap ){ @@ -156,7 +159,7 @@ Physics.behavior('body-collision-detection', function( parent ){ collision.norm = d.clone( result.closest.b ).vsub( tmp.clone( result.closest.a ) ).normalize().values(); collision.mtv = d.mult( overlap ).values(); // get a corresponding hull point for one of the core points.. relative to body A - collision.pos = d.clone( collision.norm ).mult( support.marginA ).vadd( tmp.clone( result.closest.a ) ).vsub( bodyA.state.pos ).vsub( bodyA.offset ).values(); + collision.pos = d.clone( collision.norm ).mult( support.marginA ).vadd( tmp.clone( result.closest.a ) ).vsub( bodyA.state.pos ).values(); } return scratch.done( collision ); @@ -179,7 +182,11 @@ Physics.behavior('body-collision-detection', function( parent ){ ,collision = false ; - d.clone( bodyB.state.pos ).vadd( bodyB.offset ).vsub( bodyA.state.pos ).vsub( bodyA.offset ); + d.clone( bodyB.state.pos ) + .vadd( bodyB.getGlobalOffset( tmp ) ) + .vsub( bodyA.state.pos ) + .vsub( bodyA.getGlobalOffset( tmp ) ) // save offset for later + ; overlap = d.norm() - (bodyA.geometry.radius + bodyB.geometry.radius); // hmm... they overlap exactly... choose a direction @@ -188,20 +195,14 @@ Physics.behavior('body-collision-detection', function( parent ){ d.set( 1, 0 ); } - // if ( overlap > 0 ){ - // // check the future - // d.vadd( tmp.clone(bodyB.state.vel).mult( dt ) ).vsub( tmp.clone(bodyA.state.vel).mult( dt ) ); - // overlap = d.norm() - (bodyA.geometry.radius + bodyB.geometry.radius); - // } - if ( overlap <= 0 ){ collision = { bodyA: bodyA, bodyB: bodyB, norm: d.normalize().values(), - pos: d.mult( bodyA.geometry.radius ).values(), - mtv: d.mult( -overlap/bodyA.geometry.radius ).values(), + mtv: d.mult( -overlap ).values(), + pos: d.mult( -bodyA.geometry.radius/overlap ).vadd( tmp ).values(), overlap: -overlap }; } diff --git a/src/behaviors/body-impulse-response.js b/src/behaviors/body-impulse-response.js index 9c3c2640..5f945e60 100644 --- a/src/behaviors/body-impulse-response.js +++ b/src/behaviors/body-impulse-response.js @@ -98,9 +98,7 @@ Physics.behavior('body-impulse-response', function( parent ){ // collision point from B's center ,rB = scratch.vector().clone( point ) .vadd( bodyA.state.pos ) - .vadd( tmp.clone( bodyA.offset ).rotate( -bodyA.state.angular.pos ) ) .vsub( bodyB.state.pos ) - .vsub( tmp.clone( bodyB.offset ).rotate( -bodyB.state.angular.pos ) ) ,angVelA = bodyA.state.angular.vel ,angVelB = bodyB.state.angular.vel // relative velocity towards B at collision point diff --git a/src/core/body.js b/src/core/body.js index 20f5466a..e023597a 100644 --- a/src/core/body.js +++ b/src/core/body.js @@ -458,6 +458,13 @@ return this; }, + getGlobalOffset: function( out ){ + + out = out || new Physics.vector(); + out.clone( this.offset ).rotate( this.state.angular.pos ); + return out; + }, + /** related to: Physics.aabb * Body#aabb() -> Object * + (Object): The aabb of this body @@ -467,13 +474,17 @@ aabb: function(){ var angle = this.state.angular.pos + ,scratch = Physics.scratchpad() + ,v = scratch.vector() ,aabb = this.geometry.aabb( angle ) ; - aabb.x += this.state.pos._[0] + this.offset._[0]; - aabb.y += this.state.pos._[1] + this.offset._[1]; + this.getGlobalOffset( v ); + + aabb.x += this.state.pos._[0] + v._[0]; + aabb.y += this.state.pos._[1] + v._[1]; - return aabb; + return scratch.done( aabb ); }, /** diff --git a/src/renderers/debug.js b/src/renderers/debug.js index e94fb89e..c8443589 100644 --- a/src/renderers/debug.js +++ b/src/renderers/debug.js @@ -191,7 +191,7 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ drawContact: function( c, ctx ){ var scratch = Physics.scratchpad() - ,from = scratch.vector().clone( c.pos ).vadd( c.bodyA.state.pos ).vadd( c.bodyA.offset ) + ,from = scratch.vector().clone( c.pos ).vadd( c.bodyA.state.pos ) ,to = scratch.vector().clone( from ).vsub( scratch.vector().clone( c.mtv ) ) ,opts = this.options ; @@ -354,15 +354,16 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ ctx = ctx || this.ctx; // interpolate positions - x = pos.x + os.x + offset.x + v.x * t; - y = pos.y + os.y + offset.y + v.y * t; + x = pos.x + offset.x + v.x * t; + y = pos.y + offset.y + v.y * t; ang = body.state.angular.pos + body.state.angular.vel * t; ctx.save(); ctx.translate( x, y ); ctx.rotate( ang ); + this.drawCircle( 0, 0, 2, 'red' ); + ctx.translate( os.x, os.y ); ctx.drawImage(view, -view.width/2, -view.height/2, view.width, view.height); - this.drawCircle( -os.x, -os.y, 2, 'red' ); ctx.restore(); if ( this.options.drawAABB ){ @@ -375,8 +376,9 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ // draw the non-interpolated body position body._debugView = body._debugView || this.createView(body.geometry, this.options.realBodyStyle); ctx.save(); - ctx.translate(pos.x + os.x + offset.x, pos.y + os.y + offset.y); + ctx.translate(pos.x + offset.x, pos.y + offset.y); ctx.rotate(body.state.angular.pos); + ctx.translate( os.x, os.y ); ctx.drawImage(body._debugView, -body._debugView.width * 0.5, -body._debugView.height * 0.5); ctx.restore(); } diff --git a/test/contact-sandbox.html b/test/contact-sandbox.html index 99ab2d09..f3aead10 100644 --- a/test/contact-sandbox.html +++ b/test/contact-sandbox.html @@ -150,6 +150,7 @@ height: 40, mass: 1.4, offset: Physics.vector(10, 2), + angle: Math.PI/2, //cof: 0, //vy: -0.2, // vx: 0.1, @@ -163,6 +164,8 @@ width: 40, height: 40, mass: 1.4, + offset: Physics.vector(-10, 2), + // angle: Math.PI/2, //cof: 0, //vy: -0.2, // vx: 0.1, From 07e20e8c083c31f37c0c19cb234c4005735b76e8 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Tue, 25 Nov 2014 22:37:49 -0500 Subject: [PATCH 064/110] renderer fixes for offset --- src/core/body.js | 9 ++++++++- src/renderers/canvas.js | 5 +++-- src/renderers/debug.js | 5 +++-- src/renderers/dom.js | 7 ++++--- src/renderers/pixi-renderer.js | 9 +++++---- test/pixi-sandbox.html | 9 ++++++--- 6 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/core/body.js b/src/core/body.js index e023597a..19e91073 100644 --- a/src/core/body.js +++ b/src/core/body.js @@ -210,7 +210,7 @@ /** * Body#offset * - * The vector offsetting the true position of the body. + * The vector offsetting the body's shape from its center of mass. **/ /** @@ -458,6 +458,13 @@ return this; }, + /** related to: Body#offset + * Body#getGlobalOffset( [out] ) -> Physics.vector + * - out (Physics.vector): A vector to use to put the result into. One is created if `out` isn't specified. + * + (Physics.vector): The offset in global coordinates + * + * Get the body offset vector (from the center of mass) for the body's shape in global coordinates. + **/ getGlobalOffset: function( out ){ out = out || new Physics.vector(); diff --git a/src/renderers/canvas.js b/src/renderers/canvas.js index b0898d41..6d49f36e 100644 --- a/src/renderers/canvas.js +++ b/src/renderers/canvas.js @@ -677,13 +677,14 @@ Physics.renderer('canvas', function( proto ){ ctx = ctx || this.ctx; // interpolate positions - x = pos.x + os.x + offset.x + v.x * t; - y = pos.y + ox.y + offset.y + v.y * t; + x = pos._[0] + offset.x + v._[0] * t; + y = pos._[1] + offset.y + v._[1] * t; ang = body.state.angular.pos + body.state.angular.vel * t; ctx.save(); ctx.translate( x, y ); ctx.rotate( ang ); + ctx.translate( os._[0], os._[1] ); ctx.drawImage(view, -view.width/2, -view.height/2, view.width, view.height); ctx.restore(); }, diff --git a/src/renderers/debug.js b/src/renderers/debug.js index c8443589..ac9becc7 100644 --- a/src/renderers/debug.js +++ b/src/renderers/debug.js @@ -354,8 +354,8 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ ctx = ctx || this.ctx; // interpolate positions - x = pos.x + offset.x + v.x * t; - y = pos.y + offset.y + v.y * t; + x = pos._[0] + offset.x + v._[0] * t; + y = pos._[1] + offset.y + v._[1] * t; ang = body.state.angular.pos + body.state.angular.vel * t; ctx.save(); @@ -390,6 +390,7 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ ctx.globalCompositeOperation = 'color'; ctx.translate( x, y ); ctx.rotate( ang ); + ctx.translate( os.x, os.y ); ctx.drawImage(body._sleepView, -view.width/2, -view.height/2, view.width, view.height); // ctx.globalCompositeOperation = ''; ctx.restore(); diff --git a/src/renderers/dom.js b/src/renderers/dom.js index 7b56a3b0..c3506581 100644 --- a/src/renderers/dom.js +++ b/src/renderers/dom.js @@ -234,6 +234,7 @@ Physics.renderer('dom', function( proto ){ var pos = body.state.pos ,v = body.state.vel + ,os = body.offset ,x ,y ,ang @@ -241,10 +242,10 @@ Physics.renderer('dom', function( proto ){ ; // interpolate positions - x = pos.x + v.x * t; - y = pos.y + v.y * t; + x = pos._[0] + v._[0] * t; + y = pos._[1] + v._[1] * t; ang = body.state.angular.pos + body.state.angular.vel * t; - view.style[cssTransform] = 'translate('+x+'px,'+y+'px) rotate('+ ang +'rad)'; + view.style[cssTransform] = 'translate('+x+'px,'+y+'px) rotate('+ ang +'rad) translate('+os._[0]+'px,'+os._[1]+'px)'; } }; }); diff --git a/src/renderers/pixi-renderer.js b/src/renderers/pixi-renderer.js index 0c8c8ddd..c525f94f 100644 --- a/src/renderers/pixi-renderer.js +++ b/src/renderers/pixi-renderer.js @@ -185,6 +185,7 @@ Physics.renderer('pixi', function( parent ){ drawBody: function( body, view ){ var pos = body.state.pos ,v = body.state.vel + ,os = body.offset ,t = this._interpolateTime || 0 ,x ,y @@ -192,12 +193,12 @@ Physics.renderer('pixi', function( parent ){ ; // interpolate positions - x = pos.x + v.x * t; - y = pos.y + v.y * t; + x = pos._[0] + v._[0] * t; + y = pos._[1] + v._[1] * t; ang = body.state.angular.pos + body.state.angular.vel * t; - view.position.x = x; - view.position.y = y; + view.position.set( x, y ); + view.pivot.set( -os._[0], -os._[1] ); view.rotation = ang; }, diff --git a/test/pixi-sandbox.html b/test/pixi-sandbox.html index b385f76b..84ce1e77 100644 --- a/test/pixi-sandbox.html +++ b/test/pixi-sandbox.html @@ -14,6 +14,8 @@ } + [class^="pjs"] {background: black;} + .pjs-meta { position: absolute; top: 1em; @@ -37,7 +39,7 @@ var a = (max - min); return a * Math.random() + min; } - + function setup(world) { var renderer = Physics.renderer('pixi', { @@ -207,6 +209,7 @@ y: viewHeight - 100,//+100, radius: 20, mass: 1.4, + offset: Physics.vector(10, 2), // cof: 0.1, //vy: -0.2, // vx: 0.1, @@ -258,8 +261,8 @@ Physics({ timestep: 6, maxIPF: 24 }, [ setup - // ,addBodies - ,lotsOfCircles + ,addBodies + // ,lotsOfCircles // ,lotsOfRects //,initEvents ]); From e8b5e51e4ba1666f5e6fb8dcddf9dbb176623f7a Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Tue, 25 Nov 2014 22:43:11 -0500 Subject: [PATCH 065/110] revert to set polygon verts relative to centroid --- src/bodies/convex-polygon.js | 3 --- src/geometries/convex-polygon.js | 14 ++++++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/bodies/convex-polygon.js b/src/bodies/convex-polygon.js index c58d810e..1332bf9c 100644 --- a/src/bodies/convex-polygon.js +++ b/src/bodies/convex-polygon.js @@ -50,9 +50,6 @@ Physics.body('convex-polygon', function( parent ){ vertices: options.vertices }); - // offset the centroid to match the polygon centroid - this.offset.vadd( Physics.geometry.getPolygonCentroid( options.vertices ) ); - this.recalc(); }, diff --git a/src/geometries/convex-polygon.js b/src/geometries/convex-polygon.js index 4b1b5ea6..3caceb60 100644 --- a/src/geometries/convex-polygon.js +++ b/src/geometries/convex-polygon.js @@ -60,22 +60,28 @@ Physics.geometry('convex-polygon', function( parent ){ **/ setVertices: function( hull ){ - var verts = this.vertices = [] + var scratch = Physics.scratchpad() + ,transl = scratch.transform() + ,verts = this.vertices = [] ; if ( !Physics.geometry.isPolygonConvex( hull ) ){ throw ERROR_NOT_CONVEX; } - // add the vertex as a vector to this.vertices + transl.setRotation( 0 ); + transl.setTranslation( Physics.geometry.getPolygonCentroid( hull ).negate() ); + + // translate each vertex so that the centroid is at the origin + // then add the vertex as a vector to this.vertices for ( var i = 0, l = hull.length; i < l; ++i ){ - verts.push( new Physics.vector( hull[ i ] ) ); + verts.push( new Physics.vector( hull[ i ] ).translate( transl ) ); } this._area = Physics.geometry.getPolygonArea( verts ); this._aabb = false; - return this; + return scratch.done(this); }, // extended From 044cf394363060b9eb4deb6f23f58c4cea7df99b Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Wed, 26 Nov 2014 00:08:29 -0500 Subject: [PATCH 066/110] fix for edge collisions and offset --- src/behaviors/edge-collision-detection.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/behaviors/edge-collision-detection.js b/src/behaviors/edge-collision-detection.js index a36e2989..06fae66d 100644 --- a/src/behaviors/edge-collision-detection.js +++ b/src/behaviors/edge-collision-detection.js @@ -27,6 +27,7 @@ Physics.behavior('edge-collision-detection', function( parent ){ var overlap ,aabb = body.aabb() ,scratch = Physics.scratchpad() + ,offset = body.getGlobalOffset( scratch.vector() ) ,trans = scratch.transform() ,dir = scratch.vector() ,result = scratch.vector() @@ -53,7 +54,7 @@ Physics.behavior('edge-collision-detection', function( parent ){ x: overlap, y: 0 }, - pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).values() + pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).vadd( offset ).values() }; collisions.push(collision); @@ -78,7 +79,7 @@ Physics.behavior('edge-collision-detection', function( parent ){ x: 0, y: overlap }, - pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).values() + pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).vadd( offset ).values() }; collisions.push(collision); @@ -103,7 +104,7 @@ Physics.behavior('edge-collision-detection', function( parent ){ x: -overlap, y: 0 }, - pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).values() + pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).vadd( offset ).values() }; collisions.push(collision); @@ -128,7 +129,7 @@ Physics.behavior('edge-collision-detection', function( parent ){ x: 0, y: -overlap }, - pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).values() + pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).vadd( offset ).values() }; collisions.push(collision); From 6507d0bda2a5fe096b8c7859583dbb0e649d12a6 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Wed, 26 Nov 2014 17:36:13 -0500 Subject: [PATCH 067/110] compound bodies wip --- src/bodies/compound.js | 98 ++++++++++++++++++++++--- src/core/body.js | 18 ++++- src/geometries/compound.js | 145 ++++++++++++++++++++++++++++--------- src/math/aabb.js | 26 +++++++ src/renderers/canvas.js | 31 +++++++- 5 files changed, 269 insertions(+), 49 deletions(-) diff --git a/src/bodies/compound.js b/src/bodies/compound.js index 1a06880b..2c59019f 100644 --- a/src/bodies/compound.js +++ b/src/bodies/compound.js @@ -42,41 +42,121 @@ Physics.body('compound', function( parent ){ // call parent init method parent.init.call(this, options); + this.mass = 0; + this.moi = 0; + this.children = []; this.geometry = Physics.geometry('compound'); this.addChildren( options.children ); }, - addChild: function( body ){ + // extended + connect: function( world ){ + // sanity check + if ( this.mass <= 0 ){ + throw 'Can not add empty compound body to world.'; + } + }, - this.children.push( body ); - this.geometry.addChild( body.state.pos.x, body.state.pos.y, body.geometry ); - this.recalc(); + addChild: function( body ){ + this.addChildren([ body ]); return this; }, - addChildren: function( arr ){ + addChildren: function( bodies ){ + + var self = this + ,scratch = Physics.scratchpad() + ,com = scratch.vector().zero() + ,b + ,pos + ,i + ,l = bodies && bodies.length + ,M = 0 + ; + + if ( !l ){ + return scratch.done( this ); + } + + for ( i = 0; i < l; i++ ){ + b = bodies[ i ]; + // remove body from world if applicable + if ( b._world ){ + b._world.remove( b ); + } + // add child + this.children.push( b ); + // add child to geometry + this.geometry.addChild( b.geometry, b.state.pos ); + // calc com contribution + pos = b.state.pos; + com.add( pos._[0] * b.mass, pos._[1] * b.mass ); + M += b.mass; + } + + // add mass + this.mass += M; + // com adjustment (assuming com is currently at (0,0) body coords) + com.mult( 1 / this.mass ); - this.geometry.addChildren( arr ); + // all bodies need to move + for ( i = 0, l = this.children.length; i < l; i++ ){ + b = this.children[ i ]; + b.state.pos.vadd( com ); + } + + // shift everything back + this.offset.vsub( com ); + + // refresh view on next render + + if ( this._world ){ + this._world.one('render', function(){ + self.view = null; + }); + } this.recalc(); - return this; + return scratch.done( this ); }, + /** + * CompoundBody#clear() -> this + * + * Remove all children. + **/ clear: function(){ + + this._aabb = null; + this.moi = 0; + this.mass = 0; + this.offset.zero(); this.children = []; this.geometry.clear(); - this.recalc(); return this; }, // extended recalc: function(){ + parent.recalc.call(this); // moment of inertia - this.moi = Physics.geometry.getPolygonMOI( this.geometry.vertices ); + var b + ,moi = 0 + ; + + for ( var i = 0, l = this.children.length; i < l; i++ ) { + b = this.children[ i ]; + b.recalc(); + // parallel axis theorem + moi += b.moi + b.mass * b.state.pos.normSq(); + } + + this.moi = moi; + return this; } }; }); diff --git a/src/core/body.js b/src/core/body.js index 9c27dbff..cb7bb915 100644 --- a/src/core/body.js +++ b/src/core/body.js @@ -516,17 +516,27 @@ * Get center of mass position from list of bodies. **/ Physics.body.getCOM = function( bodies, com ){ - + // @TODO add a test for this fn var b ,pos ,i - ,l - ,M + ,l = bodies && bodies.length + ,M = 0 ; com = com || new Physics.vector(); - for ( i = 0, l = bodies.length; i < l; i++ ){ + if ( !l ){ + return com.zero(); + } + + if ( l === 1 ){ + return com.clone( bodies[0].state.pos ); + } + + com.zero(); + + for ( i = 0; i < l; i++ ){ b = bodies[ i ]; pos = b.state.pos; com.add( pos._[0] * b.mass, pos._[1] * b.mass ); diff --git a/src/geometries/compound.js b/src/geometries/compound.js index 5e339bb1..0a6c008e 100644 --- a/src/geometries/compound.js +++ b/src/geometries/compound.js @@ -26,8 +26,6 @@ **/ Physics.geometry('compound', function( parent ){ - var ERROR_NOT_CONVEX = 'Error: The vertices specified do not match that of a _convex_ polygon.'; - var defaults = { }; @@ -45,6 +43,54 @@ Physics.geometry('compound', function( parent ){ this.options.defaults( defaults ); this.options( options ); + this.children = []; + }, + + /** + * CompoundGeometry#fromBodies( bodies ) -> this + * - bodies (Array): List of bodies. + * + * Create a compound geometry from a list of bodies. + **/ + fromBodies: function( bodies ){ + + for ( var i = 0, b, l = bodies.length; i < l; i++ ) { + b = bodies[ i ]; + this.addChild( b.geometry, b.state.pos ); + } + + return this; + }, + + /** + * CompoundGeometry#addChild( geometry, pos ) -> this + * - geometry (Geometry): The child to add. + * - pos (Physics.vector): The position to add the child at. + * + * Add a child at relative position. + **/ + addChild: function( geometry, pos ){ + + this._aabb = null; + this.children.push({ + g: geometry + ,pos: new Physics.vector( pos ) + }); + + return this; + }, + + /** + * CompoundGeometry#clear() -> this + * + * Remove all children. + **/ + clear: function(){ + + this._aabb = null; + this.children = []; + + return this; }, // extended @@ -54,62 +100,91 @@ Physics.geometry('compound', function( parent ){ return Physics.aabb.clone( this._aabb ); } - var scratch = Physics.scratchpad() - ,p = scratch.vector() - ,trans = scratch.transform().setRotation( angle || 0 ) - ,xaxis = scratch.vector().set( 1, 0 ).rotateInv( trans ) - ,yaxis = scratch.vector().set( 0, 1 ).rotateInv( trans ) - ,xmax = this.getFarthestHullPoint( xaxis, p ).proj( xaxis ) - ,xmin = - this.getFarthestHullPoint( xaxis.negate(), p ).proj( xaxis ) - ,ymax = this.getFarthestHullPoint( yaxis, p ).proj( yaxis ) - ,ymin = - this.getFarthestHullPoint( yaxis.negate(), p ).proj( yaxis ) + var b ,aabb + ,ch + ,ret + ,scratch = Physics.scratchpad() + ,pos = Physics.vector() ; - aabb = Physics.aabb( xmin, ymin, xmax, ymax ); + for ( var i = 0, l = this.children.length; i < l; i++ ) { + ch = this.children[ i ]; + aabb = ch.g.aabb( angle ); + pos.clone( ch.pos ); + if ( angle ){ + pos.rotate( angle ); + } + aabb.x += pos._[0]; + aabb.y += pos._[1]; + ret = ret ? Physics.aabb.union(ret, aabb, true) : aabb; + } - if (!angle){ + if ( !angle ){ // if we don't have an angle specified (or it's zero) // then we can cache this result - this._aabb = Physics.aabb.clone( aabb ); + this._aabb = Physics.aabb.clone( ret ); } - scratch.done(); - return aabb; + return scratch.done( ret ); }, // extended - getFarthestHullPoint: function( dir, result, data ){ - - var verts = this.vertices - ,val - ,prev - ,l = verts.length - ,i = 2 - ,idx + getFarthestHullPoint: function( dir, result ){ + + var ch + ,i + ,l = this.children.length + ,scratch = Physics.scratchpad() + ,v = scratch.vector() + ,len = 0 + ,maxlen = 0 ; result = result || new Physics.vector(); - return result; + // find the one with the largest projection along dir + for ( i = 0; i < l; i++ ) { + ch = this.children[ i ]; + ch.g.getFarthestHullPoint( dir, v ); + len = v.vadd( ch.pos ).proj( dir ); + + if ( len > maxlen ){ + maxlen = len; + result.swap( v ); + } + } + + return scratch.done( result ); }, // extended getFarthestCorePoint: function( dir, result, margin ){ - var norm + var ch + ,i + ,l = this.children.length ,scratch = Physics.scratchpad() - ,next = scratch.vector() - ,prev = scratch.vector() - ,verts = this.vertices - ,l = verts.length - ,mag - ,sign = this._area > 0 - ,data = {} + ,v = scratch.vector() + ,len = 0 + ,maxlen = 0 ; - scratch.done(); - return result; + result = result || new Physics.vector(); + + // find the one with the largest projection along dir + for ( i = 0; i < l; i++ ) { + ch = this.children[ i ]; + ch.g.getFarthestCorePoint( dir, v, margin ); + len = v.vadd( ch.pos ).proj( dir ); + + if ( len > maxlen ){ + maxlen = len; + result.swap( v ); + } + } + + return scratch.done( result ); } }; }); diff --git a/src/math/aabb.js b/src/math/aabb.js index 4ccdfd10..2a0a42a2 100644 --- a/src/math/aabb.js +++ b/src/math/aabb.js @@ -99,6 +99,32 @@ }; }; + /** + * Physics.aabb.union( aabb1, aabb2[, modify] ) -> Object + * - aabb1 (Object): The first aabb (returned if modify is `true`) + * - aabb2 (Object): The second aabb + * + (Object): The union of two aabbs. If modify is `true`, then the first aabb will be modified and returned. + * + * Get the union of two aabbs. + **/ + Physics.aabb.union = function( aabb1, aabb2, modify ){ + + var ret = modify === true ? aabb1 : {} + ,maxX = Math.max( aabb1.x + aabb1.hw, aabb2.x + aabb2.hw ) + ,maxY = Math.max( aabb1.y + aabb1.hh, aabb2.y + aabb2.hh ) + ,minX = Math.min( aabb1.x - aabb1.hw, aabb2.x - aabb2.hw ) + ,minY = Math.min( aabb1.y - aabb1.hh, aabb2.y - aabb2.hh ) + ; + + ret.hw = Math.abs(maxX - minX) * 0.5; + ret.hh = Math.abs(maxY - minY) * 0.5; + ret.x = (maxX + minX) * 0.5; + ret.y = (maxY + minY) * 0.5; + + return ret; + }; + + /** * Physics.aabb.overlap( aabb1, aabb2 ) -> Boolean * - aabb1 (Object): The first aabb diff --git a/src/renderers/canvas.js b/src/renderers/canvas.js index 6d49f36e..61480bca 100644 --- a/src/renderers/canvas.js +++ b/src/renderers/canvas.js @@ -630,6 +630,35 @@ Physics.renderer('canvas', function( proto ){ } else if (name === 'rectangle'){ this.drawRect(0, 0, geometry.width, geometry.height, styles, hiddenCtx); + + } else if (name === 'compound'){ + + for ( var i = 0, l = geometry.children.length, ch; i < l; i++ ){ + ch = geometry.children[ i ]; + name = ch.g.name; + // translate + hiddenCtx.translate(ch.pos.x, ch.pos.y); + + if (name === 'circle'){ + + this.drawCircle(0, 0, ch.g.radius, styles, hiddenCtx); + + } else if (name === 'convex-polygon'){ + + this.drawPolygon(ch.g.vertices, styles, hiddenCtx); + + } else if (name === 'rectangle'){ + + this.drawRect(0, 0, ch.g.width, ch.g.height, styles, hiddenCtx); + } else { + + // assume it's a point + this.drawCircle(0, 0, 1, styles, hiddenCtx); + } + // untranslate + hiddenCtx.translate(-ch.pos.x, -ch.pos.y); + } + } else { // assume it's a point @@ -641,7 +670,7 @@ Physics.renderer('canvas', function( proto ){ hiddenCtx.beginPath(); this.setStyle( styles.angleIndicator, hiddenCtx ); hiddenCtx.moveTo(0, 0); - hiddenCtx.lineTo(hw, 0); + hiddenCtx.lineTo(hw - 2 * Math.abs(aabb.x), 0); hiddenCtx.closePath(); hiddenCtx.stroke(); } From 6c5c3a51c9174e4c996e8bb8d3456040a88ec6db Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Thu, 27 Nov 2014 14:19:13 -0500 Subject: [PATCH 068/110] feature re #37 methods transform world/body coords --- src/core/body.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/core/body.js b/src/core/body.js index cb7bb915..0b431f43 100644 --- a/src/core/body.js +++ b/src/core/body.js @@ -494,6 +494,28 @@ return scratch.done( aabb ); }, + /** + * Body#toBodyCoords( v ) -> Physics.vector + * - v (Physics.vector): The vector to transform + * + (Physics.vector): The transformed vector + * + * Transform a vector into coordinates relative to this body. + **/ + toBodyCoords: function( v ){ + return v.vsub( this.state.pos ).rotate( -this.state.angular.pos ); + }, + + /** + * Body#toWorldCoords( v ) -> Physics.vector + * - v (Physics.vector): The vector to transform + * + (Physics.vector): The transformed vector + * + * Transform a vector from body coordinates into world coordinates. + **/ + toWorldCoords: function( v ){ + return v.rotate( this.state.angular.pos ).vadd( this.state.pos ); + }, + /** * Body#recalc() -> this * From ab1ddd589844e5152185999d88f6acbd3e9ac78b Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Thu, 27 Nov 2014 14:25:06 -0500 Subject: [PATCH 069/110] added code to work with rotations in composites --- src/bodies/compound.js | 2 +- src/geometries/compound.js | 16 +++-- src/renderers/canvas.js | 139 ++++++++++++++++++++----------------- test/contact-sandbox.html | 25 ++++++- 4 files changed, 111 insertions(+), 71 deletions(-) diff --git a/src/bodies/compound.js b/src/bodies/compound.js index 2c59019f..807bdcce 100644 --- a/src/bodies/compound.js +++ b/src/bodies/compound.js @@ -89,7 +89,7 @@ Physics.body('compound', function( parent ){ // add child this.children.push( b ); // add child to geometry - this.geometry.addChild( b.geometry, b.state.pos ); + this.geometry.addChild( b.geometry, b.state.pos, b.state.angular.pos ); // calc com contribution pos = b.state.pos; com.add( pos._[0] * b.mass, pos._[1] * b.mass ); diff --git a/src/geometries/compound.js b/src/geometries/compound.js index 0a6c008e..d9c3ac4f 100644 --- a/src/geometries/compound.js +++ b/src/geometries/compound.js @@ -66,15 +66,17 @@ Physics.geometry('compound', function( parent ){ * CompoundGeometry#addChild( geometry, pos ) -> this * - geometry (Geometry): The child to add. * - pos (Physics.vector): The position to add the child at. + * - angle (Number): The rotation angle * * Add a child at relative position. **/ - addChild: function( geometry, pos ){ + addChild: function( geometry, pos, angle ){ this._aabb = null; this.children.push({ g: geometry ,pos: new Physics.vector( pos ) + ,angle: angle }); return this; @@ -108,9 +110,11 @@ Physics.geometry('compound', function( parent ){ ,pos = Physics.vector() ; + angle = angle || 0; + for ( var i = 0, l = this.children.length; i < l; i++ ) { ch = this.children[ i ]; - aabb = ch.g.aabb( angle ); + aabb = ch.g.aabb( angle + ch.angle ); pos.clone( ch.pos ); if ( angle ){ pos.rotate( angle ); @@ -146,8 +150,8 @@ Physics.geometry('compound', function( parent ){ // find the one with the largest projection along dir for ( i = 0; i < l; i++ ) { ch = this.children[ i ]; - ch.g.getFarthestHullPoint( dir, v ); - len = v.vadd( ch.pos ).proj( dir ); + ch.g.getFarthestHullPoint( dir.rotate(-ch.angle), v ); + len = v.rotate(ch.angle).vadd( ch.pos ).proj( dir.rotate(ch.angle) ); if ( len > maxlen ){ maxlen = len; @@ -175,8 +179,8 @@ Physics.geometry('compound', function( parent ){ // find the one with the largest projection along dir for ( i = 0; i < l; i++ ) { ch = this.children[ i ]; - ch.g.getFarthestCorePoint( dir, v, margin ); - len = v.vadd( ch.pos ).proj( dir ); + ch.g.getFarthestCorePoint(dir.rotate(-ch.angle), v, margin ); + len = v.rotate(ch.angle).vadd( ch.pos ).proj( dir.rotate(ch.angle) ); if ( len > maxlen ){ maxlen = len; diff --git a/src/renderers/canvas.js b/src/renderers/canvas.js index 61480bca..f01841ca 100644 --- a/src/renderers/canvas.js +++ b/src/renderers/canvas.js @@ -580,102 +580,115 @@ Physics.renderer('canvas', function( proto ){ ctx.fill(); }, - // extended - createView: function( geometry, styles ){ + /** + * CanvasRenderer#draw( geometry[, styles, ctx, offset] ) -> this + * - geometry (Geometry): The shape to draw + * - styles (Object): The styles configuration + * - ctx (Canvas2DContext): The canvas context + * - offset (Vectorish): The offset from center + * + * Draw a geometry to a context. + **/ + draw: function( geometry, styles, ctx, offset ){ - var view - ,aabb = geometry.aabb() - ,hw = aabb.hw + Math.abs(aabb.x) - ,hh = aabb.hh + Math.abs(aabb.y) - ,x = hw + 1 - ,y = hh + 1 - ,hiddenCtx = this.hiddenCtx - ,hiddenCanvas = this.hiddenCanvas - ,name = geometry.name + var name = geometry.name + ,x = +(offset && offset.x) + ,y = +(offset && offset.y) + ,w = geometry.aabb().hw ; + ctx = ctx || this.ctx; styles = styles || this.options.styles[ name ] || this.options.styles.circle || {}; - // must want an image - if ( styles.src ){ - view = new Image(); - view.src = styles.src; - if ( styles.width ){ - view.width = styles.width; - } - if ( styles.height ){ - view.height = styles.height; - } - return view; - } - - x += styles.lineWidth | 0; - y += styles.lineWidth | 0; - - // clear - hiddenCanvas.width = 2 * hw + 2 + (2 * styles.lineWidth|0); - hiddenCanvas.height = 2 * hh + 2 + (2 * styles.lineWidth|0); - - hiddenCtx.save(); - hiddenCtx.translate(x, y); + ctx.save(); + ctx.translate(x, y); if (name === 'circle'){ - this.drawCircle(0, 0, geometry.radius, styles, hiddenCtx); + this.drawCircle(0, 0, geometry.radius, styles, ctx); } else if (name === 'convex-polygon'){ - this.drawPolygon(geometry.vertices, styles, hiddenCtx); + this.drawPolygon(geometry.vertices, styles, ctx); } else if (name === 'rectangle'){ - this.drawRect(0, 0, geometry.width, geometry.height, styles, hiddenCtx); + this.drawRect(0, 0, geometry.width, geometry.height, styles, ctx); } else if (name === 'compound'){ for ( var i = 0, l = geometry.children.length, ch; i < l; i++ ){ ch = geometry.children[ i ]; - name = ch.g.name; + // translate - hiddenCtx.translate(ch.pos.x, ch.pos.y); - - if (name === 'circle'){ - - this.drawCircle(0, 0, ch.g.radius, styles, hiddenCtx); + ctx.translate(ch.pos.x, ch.pos.y); + // rotate + ctx.rotate(ch.angle); - } else if (name === 'convex-polygon'){ - - this.drawPolygon(ch.g.vertices, styles, hiddenCtx); - - } else if (name === 'rectangle'){ - - this.drawRect(0, 0, ch.g.width, ch.g.height, styles, hiddenCtx); - } else { + this.draw( ch.g, styles, ctx ); - // assume it's a point - this.drawCircle(0, 0, 1, styles, hiddenCtx); - } + // unrotate + ctx.rotate(-ch.angle); // untranslate - hiddenCtx.translate(-ch.pos.x, -ch.pos.y); + ctx.translate(-ch.pos.x, -ch.pos.y); } } else { // assume it's a point - this.drawCircle(0, 0, 1, styles, hiddenCtx); + this.drawCircle(0, 0, 1, styles, ctx); } - if (styles.angleIndicator){ + if (name !== 'compound' && styles.angleIndicator){ - hiddenCtx.beginPath(); - this.setStyle( styles.angleIndicator, hiddenCtx ); - hiddenCtx.moveTo(0, 0); - hiddenCtx.lineTo(hw - 2 * Math.abs(aabb.x), 0); - hiddenCtx.closePath(); - hiddenCtx.stroke(); + ctx.beginPath(); + this.setStyle( styles.angleIndicator, ctx ); + ctx.moveTo(0, 0); + ctx.lineTo(w, 0); + ctx.closePath(); + ctx.stroke(); } - hiddenCtx.restore(); + ctx.restore(); + + return this; + }, + + // extended + createView: function( geometry, styles ){ + + var view + ,aabb = geometry.aabb() + ,hw = aabb.hw + Math.abs(aabb.x) + ,hh = aabb.hh + Math.abs(aabb.y) + ,offset = { x: hw + 1, y: hh + 1 } + ,hiddenCtx = this.hiddenCtx + ,hiddenCanvas = this.hiddenCanvas + ; + + styles = styles || this.options.styles[ name ] || this.options.styles.circle || {}; + + // must want an image + if ( styles.src ){ + view = new Image(); + view.src = styles.src; + if ( styles.width ){ + view.width = styles.width; + } + if ( styles.height ){ + view.height = styles.height; + } + return view; + } + + offset.x += styles.lineWidth | 0; + offset.y += styles.lineWidth | 0; + + // clear and resize + hiddenCanvas.width = 2 * hw + 2 + (2 * styles.lineWidth|0); + hiddenCanvas.height = 2 * hh + 2 + (2 * styles.lineWidth|0); + + this.draw( geometry, styles, hiddenCtx, offset ); view = new Image( hiddenCanvas.width, hiddenCanvas.height ); view.src = hiddenCanvas.toDataURL('image/png'); diff --git a/test/contact-sandbox.html b/test/contact-sandbox.html index f3aead10..c998ef5f 100644 --- a/test/contact-sandbox.html +++ b/test/contact-sandbox.html @@ -140,7 +140,7 @@ } - function addBodies( world ){ + function addBodies( world, P ){ world.add( Physics.body('circle', { x: viewWidth/2, @@ -212,6 +212,29 @@ }) ); } + + var thing = P.body('compound', { + x: viewWidth/2 + 200 + ,y: viewHeight/2 + ,children: [ + P.body('circle', { + x: 20 + ,y: 0 + ,radius: 20 + ,mass: 2 + }) + ,P.body('rectangle', { + x: -30 + ,y: 0 + ,width: 60 + ,height: 60 + ,angle: Math.PI/4 + ,mass: 3 + }) + ] + }); + + world.add( thing ); } From d176ceb9c29926083881bcb234ce86413c469f5f Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Thu, 27 Nov 2014 22:03:01 -0500 Subject: [PATCH 070/110] fixed collisions with compound bodies --- src/behaviors/body-collision-detection.js | 92 ++++++++++++++++++++++- src/bodies/compound.js | 21 +++++- test/contact-sandbox.html | 51 +++++++------ 3 files changed, 138 insertions(+), 26 deletions(-) diff --git a/src/behaviors/body-collision-detection.js b/src/behaviors/body-collision-detection.js index 0c90688e..fea1436b 100644 --- a/src/behaviors/body-collision-detection.js +++ b/src/behaviors/body-collision-detection.js @@ -15,7 +15,7 @@ * bodyB: // the second body * norm: // the normal vector (Vectorish) * mtv: // the minimum transit vector. (the direction and length needed to extract bodyB from bodyA) - * pos: // the collision point + * pos: // the collision point relative to bodyA * overlap: // the amount bodyA overlaps bodyB * } * ``` @@ -232,6 +232,68 @@ Physics.behavior('body-collision-detection', function( parent ){ return checkCircles( bodyA, bodyB ); + } else if ( bodyA.geometry.name === 'compound' || bodyB.geometry.name === 'compound' ){ + // compound bodies are special. We can't use gjk because + // they could have concavities. so we do the pieces individually + var test = (bodyA.geometry.name === 'compound') + ,compound = test ? bodyA : bodyB + ,other = test ? bodyB : bodyA + ,cols + ,ch + ,ret = [] + ,scratch = Physics.scratchpad() + ,vec = scratch.vector() + ,oldPos = scratch.vector() + ,otherAABB = other.aabb() + ,i + ,l + ; + + for ( i = 0, l = compound.children.length; i < l; i++ ){ + + ch = compound.children[ i ]; + // move body to fake position + oldPos.clone( ch.state.pos ); + ch.offset.vadd( oldPos.vadd( compound.offset ).rotate( -ch.state.angular.pos ) ); + ch.state.pos.clone( compound.state.pos ); + ch.state.angular.pos += compound.state.angular.pos; + + // check it if the aabbs overlap + if ( Physics.aabb.overlap(otherAABB, ch.aabb()) ){ + + cols = checkPair( other, ch ); + + if ( cols instanceof Array ){ + for ( var j = 0, c, ll = cols.length; j < ll; j++ ){ + c = cols[j]; + // set body to be the compound body + if ( c.bodyA === ch ){ + c.bodyA = compound; + } else { + c.bodyB = compound; + } + ret.push( c ); + } + + } else if ( cols ) { + // set body to be the compound body + if ( cols.bodyA === ch ){ + cols.bodyA = compound; + } else { + cols.bodyB = compound; + } + ret.push( cols ); + } + } + + // transform it back + ch.state.angular.pos -= compound.state.angular.pos; + ch.offset.vsub( oldPos ); + ch.state.pos.clone( oldPos.rotate( ch.state.angular.pos ).vsub( compound.offset ) ); + } + + return scratch.done( ret ); + } else { return checkGJK( bodyA, bodyB ); @@ -314,7 +376,19 @@ Physics.behavior('body-collision-detection', function( parent ){ ){ ret = checkPair( pair.bodyA, pair.bodyB ); - if ( ret ){ + if ( ret instanceof Array ){ + + for ( var j = 0, r, ll = ret.length; j < ll; j++ ){ + r = ret[j]; + if ( r ){ + hash = pairHash( pair.bodyA.uid, pair.bodyB.uid ); + contactList[ hash ] = true; + r.collidedPreviously = prevContacts[ hash ]; + collisions.push( r ); + } + } + + } else if ( ret ){ hash = pairHash( pair.bodyA.uid, pair.bodyB.uid ); contactList[ hash ] = true; ret.collidedPreviously = prevContacts[ hash ]; @@ -364,7 +438,19 @@ Physics.behavior('body-collision-detection', function( parent ){ ret = checkPair( bodyA, bodyB ); - if ( ret ){ + if ( ret instanceof Array ){ + + for ( var j = 0, r, ll = ret.length; j < ll; j++ ){ + r = ret[j]; + if ( r ){ + hash = pairHash( pair.bodyA.uid, pair.bodyB.uid ); + contactList[ hash ] = true; + r.collidedPreviously = prevContacts[ hash ]; + collisions.push( r ); + } + } + + } else if ( ret ){ hash = pairHash( bodyA.uid, bodyB.uid ); contactList[ hash ] = true; ret.collidedPreviously = prevContacts[ hash ]; diff --git a/src/bodies/compound.js b/src/bodies/compound.js index 807bdcce..b48bb978 100644 --- a/src/bodies/compound.js +++ b/src/bodies/compound.js @@ -89,7 +89,7 @@ Physics.body('compound', function( parent ){ // add child this.children.push( b ); // add child to geometry - this.geometry.addChild( b.geometry, b.state.pos, b.state.angular.pos ); + this.geometry.addChild( b.geometry, new Physics.vector(b.state.pos).vadd(b.offset), b.state.angular.pos ); // calc com contribution pos = b.state.pos; com.add( pos._[0] * b.mass, pos._[1] * b.mass ); @@ -104,7 +104,12 @@ Physics.body('compound', function( parent ){ // all bodies need to move for ( i = 0, l = this.children.length; i < l; i++ ){ b = this.children[ i ]; - b.state.pos.vadd( com ); + b.state.pos.vsub( com ); + } + + for ( i = 0, l = this.geometry.children.length; i < l; i++ ){ + b = this.geometry.children[ i ]; + b.pos.vsub( com ); } // shift everything back @@ -139,6 +144,18 @@ Physics.body('compound', function( parent ){ return this; }, + refreshGeometry: function(){ + + this.geometry.clear(); + + for ( var i = 0, l = this.children.length; i < l; i++ ) { + b = this.children[ i ]; + this.geometry.addChild( b.geometry, new Physics.vector(b.state.pos).vadd(b.offset), b.state.angular.pos ); + } + + return this; + }, + // extended recalc: function(){ diff --git a/test/contact-sandbox.html b/test/contact-sandbox.html index c998ef5f..4e981573 100644 --- a/test/contact-sandbox.html +++ b/test/contact-sandbox.html @@ -140,6 +140,32 @@ } + function makeThing(x, y){ + return Physics.body('compound', { + x: x + ,y: y + ,offset: Physics.vector( -60, 0 ) + ,children: [ + Physics.body('circle', { + x: 20 + ,y: 0 + ,radius: 30 + ,mass: 1 + // ,offset: Physics.vector( -50, 0 ) + }) + ,Physics.body('rectangle', { + x: -30 + ,y: 0 + ,width: 60 + ,height: 60 + ,angle: Math.PI/4 + ,mass: 1 + // ,offset: Physics.vector( -60, 0 ) + }) + ] + }); + } + function addBodies( world, P ){ world.add( Physics.body('circle', { @@ -191,6 +217,10 @@ ); } + + world.add( makeThing(400, 600) ); + world.add( makeThing(400, 500) ); + var rectangles = [ { x: 100, y: 60, w: 50, h: 20, mass: 1 } ,{ x: 300, y: 60, w: 100, h: 80, mass: 2 } @@ -213,28 +243,7 @@ ); } - var thing = P.body('compound', { - x: viewWidth/2 + 200 - ,y: viewHeight/2 - ,children: [ - P.body('circle', { - x: 20 - ,y: 0 - ,radius: 20 - ,mass: 2 - }) - ,P.body('rectangle', { - x: -30 - ,y: 0 - ,width: 60 - ,height: 60 - ,angle: Math.PI/4 - ,mass: 3 - }) - ] - }); - world.add( thing ); } From b81acbed3e5ba975295c0db14a28ad82865f1488 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Thu, 27 Nov 2014 22:24:08 -0500 Subject: [PATCH 071/110] minor fix for compound bodies --- src/bodies/compound.js | 13 +------------ test/contact-sandbox.html | 6 +++--- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/bodies/compound.js b/src/bodies/compound.js index b48bb978..909025f5 100644 --- a/src/bodies/compound.js +++ b/src/bodies/compound.js @@ -101,18 +101,7 @@ Physics.body('compound', function( parent ){ // com adjustment (assuming com is currently at (0,0) body coords) com.mult( 1 / this.mass ); - // all bodies need to move - for ( i = 0, l = this.children.length; i < l; i++ ){ - b = this.children[ i ]; - b.state.pos.vsub( com ); - } - - for ( i = 0, l = this.geometry.children.length; i < l; i++ ){ - b = this.geometry.children[ i ]; - b.pos.vsub( com ); - } - - // shift everything back + // shift the center of mass this.offset.vsub( com ); // refresh view on next render diff --git a/test/contact-sandbox.html b/test/contact-sandbox.html index 4e981573..ca8c8a3a 100644 --- a/test/contact-sandbox.html +++ b/test/contact-sandbox.html @@ -144,14 +144,14 @@ return Physics.body('compound', { x: x ,y: y - ,offset: Physics.vector( -60, 0 ) + // ,offset: Physics.vector( -60, 0 ) ,children: [ Physics.body('circle', { - x: 20 + x: 50 ,y: 0 ,radius: 30 ,mass: 1 - // ,offset: Physics.vector( -50, 0 ) + ,offset: Physics.vector( 0, 10 ) }) ,Physics.body('rectangle', { x: -30 From 640316f891c597d14052eda34ea8419ecd2f9e44 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Fri, 28 Nov 2014 12:38:07 -0500 Subject: [PATCH 072/110] fix to body positioning --- src/bodies/compound.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/bodies/compound.js b/src/bodies/compound.js index 909025f5..f72c5363 100644 --- a/src/bodies/compound.js +++ b/src/bodies/compound.js @@ -89,7 +89,13 @@ Physics.body('compound', function( parent ){ // add child this.children.push( b ); // add child to geometry - this.geometry.addChild( b.geometry, new Physics.vector(b.state.pos).vadd(b.offset), b.state.angular.pos ); + this.geometry.addChild( + b.geometry, + new Physics.vector(b.offset) + .rotate(b.state.angular.pos) + .vadd(b.state.pos), + b.state.angular.pos + ); // calc com contribution pos = b.state.pos; com.add( pos._[0] * b.mass, pos._[1] * b.mass ); From 70edb25880c017277b28421597b57930ac67e50a Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Fri, 28 Nov 2014 15:18:42 -0500 Subject: [PATCH 073/110] jshint fixes --- src/behaviors/body-collision-detection.js | 8 ++++---- src/bodies/compound.js | 4 ++-- test/r.js.spec.helper.js | 2 +- test/requirejs.spec.helper.js | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/behaviors/body-collision-detection.js b/src/behaviors/body-collision-detection.js index fea1436b..c296fc1c 100644 --- a/src/behaviors/body-collision-detection.js +++ b/src/behaviors/body-collision-detection.js @@ -285,7 +285,7 @@ Physics.behavior('body-collision-detection', function( parent ){ ret.push( cols ); } } - + // transform it back ch.state.angular.pos -= compound.state.angular.pos; ch.offset.vsub( oldPos ); @@ -440,10 +440,10 @@ Physics.behavior('body-collision-detection', function( parent ){ if ( ret instanceof Array ){ - for ( var j = 0, r, ll = ret.length; j < ll; j++ ){ - r = ret[j]; + for ( var k = 0, r, ll = ret.length; k < ll; k++ ){ + r = ret[k]; if ( r ){ - hash = pairHash( pair.bodyA.uid, pair.bodyB.uid ); + hash = pairHash( bodyA.uid, bodyB.uid ); contactList[ hash ] = true; r.collidedPreviously = prevContacts[ hash ]; collisions.push( r ); diff --git a/src/bodies/compound.js b/src/bodies/compound.js index f72c5363..cfdfe558 100644 --- a/src/bodies/compound.js +++ b/src/bodies/compound.js @@ -93,7 +93,7 @@ Physics.body('compound', function( parent ){ b.geometry, new Physics.vector(b.offset) .rotate(b.state.angular.pos) - .vadd(b.state.pos), + .vadd(b.state.pos), b.state.angular.pos ); // calc com contribution @@ -143,7 +143,7 @@ Physics.body('compound', function( parent ){ this.geometry.clear(); - for ( var i = 0, l = this.children.length; i < l; i++ ) { + for ( var i = 0, b, l = this.children.length; i < l; i++ ) { b = this.children[ i ]; this.geometry.addChild( b.geometry, new Physics.vector(b.state.pos).vadd(b.offset), b.state.angular.pos ); } diff --git a/test/r.js.spec.helper.js b/test/r.js.spec.helper.js index 230f9df2..691c2103 100644 --- a/test/r.js.spec.helper.js +++ b/test/r.js.spec.helper.js @@ -1 +1 @@ -require(["physicsjs/geometries/circle","physicsjs/geometries/convex-polygon","physicsjs/geometries/rectangle","physicsjs/bodies/circle","physicsjs/bodies/convex-polygon","physicsjs/bodies/rectangle","physicsjs/behaviors/attractor","physicsjs/behaviors/body-collision-detection","physicsjs/behaviors/body-impulse-response","physicsjs/behaviors/constant-acceleration","physicsjs/behaviors/edge-collision-detection","physicsjs/behaviors/interactive","physicsjs/behaviors/newtonian","physicsjs/behaviors/sweep-prune","physicsjs/behaviors/verlet-constraints","physicsjs/integrators/improved-euler","physicsjs/integrators/velocity-verlet-alt","physicsjs/integrators/velocity-verlet","physicsjs/renderers/canvas","physicsjs/renderers/debug","physicsjs/renderers/dom","physicsjs/renderers/pixi-renderer"]); \ No newline at end of file +require(["physicsjs/geometries/circle","physicsjs/geometries/compound","physicsjs/geometries/convex-polygon","physicsjs/geometries/rectangle","physicsjs/bodies/circle","physicsjs/bodies/compound","physicsjs/bodies/convex-polygon","physicsjs/bodies/rectangle","physicsjs/behaviors/attractor","physicsjs/behaviors/body-collision-detection","physicsjs/behaviors/body-impulse-response","physicsjs/behaviors/constant-acceleration","physicsjs/behaviors/edge-collision-detection","physicsjs/behaviors/interactive","physicsjs/behaviors/newtonian","physicsjs/behaviors/sweep-prune","physicsjs/behaviors/verlet-constraints","physicsjs/integrators/improved-euler","physicsjs/integrators/velocity-verlet-alt","physicsjs/integrators/velocity-verlet","physicsjs/renderers/canvas","physicsjs/renderers/debug","physicsjs/renderers/dom","physicsjs/renderers/pixi-renderer"]); \ No newline at end of file diff --git a/test/requirejs.spec.helper.js b/test/requirejs.spec.helper.js index d87e724a..2777923c 100644 --- a/test/requirejs.spec.helper.js +++ b/test/requirejs.spec.helper.js @@ -1 +1 @@ -var cfg = {"modules":["physicsjs/geometries/circle","physicsjs/geometries/convex-polygon","physicsjs/geometries/rectangle","physicsjs/bodies/circle","physicsjs/bodies/convex-polygon","physicsjs/bodies/rectangle","physicsjs/behaviors/attractor","physicsjs/behaviors/body-collision-detection","physicsjs/behaviors/body-impulse-response","physicsjs/behaviors/constant-acceleration","physicsjs/behaviors/edge-collision-detection","physicsjs/behaviors/interactive","physicsjs/behaviors/newtonian","physicsjs/behaviors/sweep-prune","physicsjs/behaviors/verlet-constraints","physicsjs/integrators/improved-euler","physicsjs/integrators/velocity-verlet-alt","physicsjs/integrators/velocity-verlet","physicsjs/renderers/canvas","physicsjs/renderers/debug","physicsjs/renderers/dom","physicsjs/renderers/pixi-renderer"]}; \ No newline at end of file +var cfg = {"modules":["physicsjs/geometries/circle","physicsjs/geometries/compound","physicsjs/geometries/convex-polygon","physicsjs/geometries/rectangle","physicsjs/bodies/circle","physicsjs/bodies/compound","physicsjs/bodies/convex-polygon","physicsjs/bodies/rectangle","physicsjs/behaviors/attractor","physicsjs/behaviors/body-collision-detection","physicsjs/behaviors/body-impulse-response","physicsjs/behaviors/constant-acceleration","physicsjs/behaviors/edge-collision-detection","physicsjs/behaviors/interactive","physicsjs/behaviors/newtonian","physicsjs/behaviors/sweep-prune","physicsjs/behaviors/verlet-constraints","physicsjs/integrators/improved-euler","physicsjs/integrators/velocity-verlet-alt","physicsjs/integrators/velocity-verlet","physicsjs/renderers/canvas","physicsjs/renderers/debug","physicsjs/renderers/dom","physicsjs/renderers/pixi-renderer"]}; \ No newline at end of file From 3e0ce442956b5eeb9f2725318059f5ef9e4a4739 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Sat, 29 Nov 2014 15:47:59 -0500 Subject: [PATCH 074/110] fixes #132. pixi renderer handles body removal --- src/renderers/pixi-renderer.js | 52 ++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/renderers/pixi-renderer.js b/src/renderers/pixi-renderer.js index c525f94f..2876772a 100644 --- a/src/renderers/pixi-renderer.js +++ b/src/renderers/pixi-renderer.js @@ -147,6 +147,58 @@ Physics.renderer('pixi', function( parent ){ } }, + // extended + connect: function( world ){ + + world.on( 'add:body', this.attach, this ); + world.on( 'remove:body', this.detach, this ); + }, + + // extended + disconnect: function( world ){ + + world.off( 'add:body', this.attach, this ); + world.off( 'remove:body', this.detach, this ); + }, + + /** + * PixiRenderer#detach( data ) -> this + * - data (PIXI.Graphics|Object): Graphics object or event data (`data.body`) + * + * Event callback to detach a child from the stage + **/ + detach: function( data ){ + + // interpred data as either dom node or event data + var el = (data instanceof PIXI.Graphics && data) || (data.body && data.body.view); + + if ( el ){ + // remove view from dom + this.stage.removeChild( el ); + } + + return this; + }, + + /** + * PixiRenderer#attach( data ) -> this + * - data (PIXI.Graphics|Object): Graphics object or event data (`data.body`) + * + * Event callback to attach a child to the stage + **/ + attach: function( data ){ + + // interpred data as either dom node or event data + var el = (data instanceof PIXI.Graphics && data) || (data.body && data.body.view); + + if ( el ){ + // attach to viewport + this.stage.addChild( el ); + } + + return this; + }, + /** * PixiRenderer#loadSpriteSheets( assetsToLoad, callback ) -> this * - assetsToLoad (Array): Array of spritesheets to load From 9f0b53c7958cf46b63a002e56f654cb7a3005b78 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Sat, 29 Nov 2014 16:06:57 -0500 Subject: [PATCH 075/110] compound body rendering for all renderers --- src/bodies/compound.js | 27 ++++++++++++++++++----- src/core/body.js | 4 +++- src/geometries/compound.js | 39 ++++++++-------------------------- src/renderers/debug.js | 6 +++++- src/renderers/dom.js | 19 ++++++++++++++++- src/renderers/pixi-renderer.js | 19 +++++++++++++---- test/pixi-sandbox.html | 32 ++++++++++++++++++++++++++-- 7 files changed, 102 insertions(+), 44 deletions(-) diff --git a/src/bodies/compound.js b/src/bodies/compound.js index cfdfe558..61d0ae11 100644 --- a/src/bodies/compound.js +++ b/src/bodies/compound.js @@ -6,20 +6,21 @@ * * Physics.body('compound') * - * Body for convex polygons. The position of the body is the centroid of the polygon. + * Not a body in itself. It's a container to group other bodies. The position of the body is the center of mass. + * It must have at least one child before being added to the world. * * Additional config options: * - * - vertices: Array of [[Vectorish]] objects representing the polygon vertices in clockwise (or counterclockwise) order. + * - children: Array of [[Body]] objects. * * Example: * * ```javascript * var thing = Physics.body('compound', { - * // place the centroid of the body at (300, 200) + * // place the center of mass at (300, 200) * x: 300, * y: 200, - * // the centroid is automatically calculated and used to position the shape + * // the center of mass is automatically calculated and used to position the shape * children: [ * body1, * body2, @@ -58,12 +59,24 @@ Physics.body('compound', function( parent ){ } }, + /** + * CompoundBody#addChild( body ) -> this + * - body (Body): The child to add + * + * Add a body as a child. + **/ addChild: function( body ){ this.addChildren([ body ]); return this; }, + /** + * CompoundBody#addChildren( bodies ) -> this + * - bodies (Array): The list of children to add + * + * Add an array of children to the compound. + **/ addChildren: function( bodies ){ var self = this @@ -111,7 +124,6 @@ Physics.body('compound', function( parent ){ this.offset.vsub( com ); // refresh view on next render - if ( this._world ){ this._world.one('render', function(){ self.view = null; @@ -139,6 +151,11 @@ Physics.body('compound', function( parent ){ return this; }, + /** + * CompoundBody#refreshGeometry() -> this + * + * If the children's positions change, `refreshGeometry()` should be called to fix the shape. + **/ refreshGeometry: function(){ this.geometry.clear(); diff --git a/src/core/body.js b/src/core/body.js index 0b431f43..05a7aacc 100644 --- a/src/core/body.js +++ b/src/core/body.js @@ -77,7 +77,9 @@ // what is its coefficient of friction with another surface with COF = 1? cof: 0.8, // what is the view object (mixed) that should be used when rendering? - view: null + view: null, + // the vector offsetting the geometry from its center of mass + offset: Physics.vector(0,0) } ``` * diff --git a/src/geometries/compound.js b/src/geometries/compound.js index d9c3ac4f..0a4bc0ed 100644 --- a/src/geometries/compound.js +++ b/src/geometries/compound.js @@ -5,23 +5,11 @@ * * Geometry for compound shapes. * - * Additional config options: - * - * - children: children - * * Example: * * ```javascript - * var thing = Physics.geometry('compound', { - * // the centroid is automatically calculated and used to position the shape - * vertices: [ - * { x: 0, y: -30 }, - * { x: -29, y: -9 }, - * { x: -18, y: 24 }, - * { x: 18, y: 24 }, - * { x: 29, y: -9 } - * ] - * }); + * var thing = Physics.geometry('compound'); + * thing.addChild( child, pos, rotation ); * ``` **/ Physics.geometry('compound', function( parent ){ @@ -46,22 +34,6 @@ Physics.geometry('compound', function( parent ){ this.children = []; }, - /** - * CompoundGeometry#fromBodies( bodies ) -> this - * - bodies (Array): List of bodies. - * - * Create a compound geometry from a list of bodies. - **/ - fromBodies: function( bodies ){ - - for ( var i = 0, b, l = bodies.length; i < l; i++ ) { - b = bodies[ i ]; - this.addChild( b.geometry, b.state.pos ); - } - - return this; - }, - /** * CompoundGeometry#addChild( geometry, pos ) -> this * - geometry (Geometry): The child to add. @@ -114,11 +86,14 @@ Physics.geometry('compound', function( parent ){ for ( var i = 0, l = this.children.length; i < l; i++ ) { ch = this.children[ i ]; + // the aabb rotated by overall angle and the child rotation aabb = ch.g.aabb( angle + ch.angle ); pos.clone( ch.pos ); if ( angle ){ + // get the child's position rotated if needed pos.rotate( angle ); } + // move the aabb to the child's position aabb.x += pos._[0]; aabb.y += pos._[1]; ret = ret ? Physics.aabb.union(ret, aabb, true) : aabb; @@ -134,6 +109,8 @@ Physics.geometry('compound', function( parent ){ }, // extended + // NOTE: unlike other geometries this can't be used in the + // GJK algorithm because the shape isn't garanteed to be convex getFarthestHullPoint: function( dir, result ){ var ch @@ -163,6 +140,8 @@ Physics.geometry('compound', function( parent ){ }, // extended + // NOTE: unlike other geometries this can't be used in the + // GJK algorithm because the shape isn't garanteed to be convex getFarthestCorePoint: function( dir, result, margin ){ var ch diff --git a/src/renderers/debug.js b/src/renderers/debug.js index ac9becc7..3f633e7e 100644 --- a/src/renderers/debug.js +++ b/src/renderers/debug.js @@ -134,6 +134,7 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ } }, + // extended connect: function( world ){ world.on('sweep-prune:intervals', this.storeIntervals, this ); @@ -143,6 +144,7 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ this.updateGui(); }, + // extended disconnect: function( world ){ world.off('sweep-prune:intervals', this.storeIntervals, this ); @@ -202,11 +204,11 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ scratch.done(); }, + // init the dat.gui settings initGui: function(){ var self = this ,gui = this.gui = new window.dat.GUI({ autoPlace: false }) - ,el = document.getElementById('my-gui-container') ,op = this.options ,getset ,f @@ -330,6 +332,7 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ this.el.parentNode.appendChild( gui.domElement ); }, + // update the dat.gui parameters updateGui: function(){ var gui = this.gui; // Iterate over all controllers @@ -338,6 +341,7 @@ Physics.renderer('debug', 'canvas', function( parent, proto ){ } }, + // extended drawBody: function( body, view, ctx, offset ){ var pos = body.state.pos diff --git a/src/renderers/dom.js b/src/renderers/dom.js index c3506581..31b7e1b7 100644 --- a/src/renderers/dom.js +++ b/src/renderers/dom.js @@ -151,6 +151,7 @@ Physics.renderer('dom', function( proto ){ createView: function( geometry ){ var el = newEl() + ,chel ,fn = geometry.name + 'Properties' ; @@ -159,7 +160,23 @@ Physics.renderer('dom', function( proto ){ el.style.top = '0px'; el.style.left = '0px'; - if (this[ fn ]){ + if ( geometry.name === 'compound' ){ + + for ( var i = 0, l = geometry.children.length, ch; i < l; i++ ){ + ch = geometry.children[ i ]; + chel = newEl(); + chel.className = classpfx + geometry.name + ' ' + classpfx + 'child'; + chel.style.position = 'absolute'; + chel.style.top = '0px'; + chel.style.left = '0px'; + if ( this[ ch.g.name + 'Properties' ] ){ + this[ ch.g.name + 'Properties' ](chel, ch.g); + } + chel.style[cssTransform] = 'translate('+ch.pos._[0]+'px,'+ch.pos._[1]+'px) rotate('+ ch.angle +'rad)'; + el.appendChild( chel ); + } + + } else if ( this[ fn ] ){ this[ fn ](el, geometry); } diff --git a/src/renderers/pixi-renderer.js b/src/renderers/pixi-renderer.js index 2876772a..d3220d5f 100644 --- a/src/renderers/pixi-renderer.js +++ b/src/renderers/pixi-renderer.js @@ -409,7 +409,7 @@ Physics.renderer('pixi', function( parent ){ }, // extended - createView: function( geometry, styles ){ + createView: function( geometry, styles, parent ){ var view = null ,aabb = geometry.aabb() @@ -418,6 +418,7 @@ Physics.renderer('pixi', function( parent ){ ,name = geometry.name ; + parent = parent || this.stage; styles = styles || this.options.styles[ name ] || this.options.styles.circle || {}; if (name === 'circle'){ @@ -431,21 +432,31 @@ Physics.renderer('pixi', function( parent ){ } else if (name === 'rectangle'){ view = this.createRect(-geometry.width/2, -geometry.height/2, geometry.width, geometry.height, styles); + } else if (name === 'compound'){ + + view = new PIXI.Graphics(); + + for ( var i = 0, l = geometry.children.length, ch, chview; i < l; i++ ){ + ch = geometry.children[ i ]; + chview = this.createView( ch.g, styles, view ); + chview.position.set( ch.pos.x, ch.pos.y ); + chview.rotation = ch.angle; + } } else { // assume it's a point view = this.createCircle(0, 0, 1, styles); } - if ( styles.angleIndicator && styles.angleIndicator !== 'transparent' ){ + if ( name !== 'compound' && styles.angleIndicator && styles.angleIndicator !== 'transparent' ){ view.lineStyle( styles.lineWidth, styles.angleIndicator ); view.moveTo( 0, 0 ); view.lineTo( hw, 0 ); + view.cacheAsBitmap = true; } - view.cacheAsBitmap = true; - this.stage.addChild(view); + parent.addChild(view); return view; }, diff --git a/test/pixi-sandbox.html b/test/pixi-sandbox.html index 84ce1e77..63576325 100644 --- a/test/pixi-sandbox.html +++ b/test/pixi-sandbox.html @@ -203,8 +203,8 @@ // vx: 0.1, restitution: 1 })); - - world.add( Physics.body('circle', { +var b; + world.add( b=Physics.body('circle', { x: viewWidth/2, y: viewHeight - 100,//+100, radius: 20, @@ -216,6 +216,10 @@ restitution: 0.9 })); + setTimeout(function(){ + world.remove(b) + }, 6000) + var polygons = [ {x:410, y:220, v:[{x: 0, y: 80}, {x: 80, y: 0}, {x: 0, y: -80},{x: -30, y: -30}, {x: -30, y: 30}] }, {x:290, y:320, v:[{x: 0, y: 80}, {x: 80, y: 0}, {x: 0, y: -80},{x: -30, y: -30}, {x: -30, y: 30}], angle: Math.PI } @@ -235,6 +239,30 @@ ); } + world.add( Physics.body('compound', { + x: viewWidth/3 + ,y: viewHeight/2 + // ,offset: Physics.vector( -60, 0 ) + ,children: [ + Physics.body('circle', { + x: 50 + ,y: 0 + ,radius: 30 + ,mass: 1 + ,offset: Physics.vector( 0, 10 ) + }) + ,Physics.body('rectangle', { + x: -30 + ,y: 0 + ,width: 60 + ,height: 60 + ,angle: Math.PI/4 + ,mass: 1 + // ,offset: Physics.vector( -60, 0 ) + }) + ] + }) ); + var rectangles = [ { x: 100, y: 60, w: 50, h: 20, mass: 1 } ,{ x: 300, y: 60, w: 100, h: 80, mass: 2 } From 5c111a5e5085b2758c96300387e35e2a86f0a76b Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Sat, 29 Nov 2014 16:34:09 -0500 Subject: [PATCH 076/110] minor collision detection fix --- src/behaviors/body-collision-detection.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/behaviors/body-collision-detection.js b/src/behaviors/body-collision-detection.js index c296fc1c..d4ce5316 100644 --- a/src/behaviors/body-collision-detection.js +++ b/src/behaviors/body-collision-detection.js @@ -131,7 +131,10 @@ Physics.behavior('body-collision-detection', function( parent ){ support.marginA = 0; support.marginB = 0; - while ( result.overlap && (support.marginA < dimA || support.marginB < dimB) ){ + // while there's still an overlap (or we don't have a positive distance) + // and the support margins aren't bigger than the shapes... + // search for the distance data + while ( (result.overlap || result.distance === 0) && (support.marginA < dimA || support.marginB < dimB) ){ if ( support.marginA < dimA ){ support.marginA += 1; } From 3114a21d544e423bd8574e86aa29f722d950103d Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Mon, 1 Dec 2014 21:07:02 -0500 Subject: [PATCH 077/110] minor fix --- src/math/vector.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/math/vector.js b/src/math/vector.js index 4479b64a..f599491a 100644 --- a/src/math/vector.js +++ b/src/math/vector.js @@ -545,8 +545,8 @@ cosA = Math.cos( t ); if ( o ){ - x = (o.x || o._[ 0 ]) | 0; - y = (o.y || o._[ 1 ]) | 0; + x = o.x; + y = o.y; } } else { sinA = t.sinA; From 9165881ca4214ec1cff4becb26e9cc8d7384cbd1 Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Mon, 1 Dec 2014 22:01:52 -0500 Subject: [PATCH 078/110] small optimizations --- src/behaviors/body-collision-detection.js | 41 ++++++++++++++--------- src/util/scratchpad.js | 34 +++++++++---------- test/pixi-sandbox.html | 4 +-- 3 files changed, 45 insertions(+), 34 deletions(-) diff --git a/src/behaviors/body-collision-detection.js b/src/behaviors/body-collision-detection.js index d4ce5316..aebe3754 100644 --- a/src/behaviors/body-collision-detection.js +++ b/src/behaviors/body-collision-detection.js @@ -43,36 +43,40 @@ Physics.behavior('body-collision-detection', function( parent ){ ; if ( !fn ){ - fn = supportFnStack[ hash ] = function( searchDir ){ + fn = supportFnStack[ hash ] = function pairSupportFunction( searchDir ){ - var scratch = Physics.scratchpad() - ,tA = fn.tA + var tA = fn.tA ,tB = fn.tB - ,vA = scratch.vector() - ,vB = scratch.vector() - ,marginA = fn.marginA - ,marginB = fn.marginB + ,vA = fn.tmpv1 + ,vB = fn.tmpv2 ; if ( fn.useCore ){ - vA = bodyA.geometry.getFarthestCorePoint( searchDir.rotateInv( tA ), vA, marginA ).vadd( bodyA.offset ).transform( tA ); - vB = bodyB.geometry.getFarthestCorePoint( searchDir.rotate( tA ).rotateInv( tB ).negate(), vB, marginB ).vadd( bodyB.offset ).transform( tB ); + vA = bodyA.geometry.getFarthestCorePoint( searchDir.rotateInv( tA ), vA, fn.marginA ); + vB = bodyB.geometry.getFarthestCorePoint( searchDir.rotate( tA ).rotateInv( tB ).negate(), vB, fn.marginB ); } else { - vA = bodyA.geometry.getFarthestHullPoint( searchDir.rotateInv( tA ), vA ).vadd( bodyA.offset ).transform( tA ); - vB = bodyB.geometry.getFarthestHullPoint( searchDir.rotate( tA ).rotateInv( tB ).negate(), vB ).vadd( bodyB.offset ).transform( tB ); + vA = bodyA.geometry.getFarthestHullPoint( searchDir.rotateInv( tA ), vA ); + vB = bodyB.geometry.getFarthestHullPoint( searchDir.rotate( tA ).rotateInv( tB ).negate(), vB ); } + vA.vadd( bodyA.offset ).transform( tA ); + vB.vadd( bodyB.offset ).transform( tB ); searchDir.negate().rotate( tB ); - return scratch.done({ + return { a: vA.values(), b: vB.values(), pt: vA.vsub( vB ).values() - }); + }; }; + // transforms for coordinate transformations fn.tA = new Physics.transform(); fn.tB = new Physics.transform(); + + // temp vectors (used too frequently to justify scratchpad) + fn.tmpv1 = new Physics.vector(); + fn.tmpv2 = new Physics.vector(); } fn.useCore = false; @@ -102,6 +106,7 @@ Physics.behavior('body-collision-detection', function( parent ){ ,overlap ,result ,support + ,inc ,collision = false ,aabbA = bodyA.aabb() ,dimA = Math.min( aabbA.hw, aabbA.hh ) @@ -126,6 +131,12 @@ Physics.behavior('body-collision-detection', function( parent ){ bodyB: bodyB }; + // figure out how much the bodies moved relative to each other + tmp.clone( bodyA.state.pos ).vsub( bodyA.state.old.pos ).vsub( bodyB.state.pos ).vadd( bodyB.state.old.pos ); + inc = Math.abs(tmp.proj( d )); + // let's increment the margin by half this value each iteration + inc = Math.max( 0.5 * inc, 1 ); + // first get the min distance of between core objects support.useCore = true; support.marginA = 0; @@ -136,10 +147,10 @@ Physics.behavior('body-collision-detection', function( parent ){ // search for the distance data while ( (result.overlap || result.distance === 0) && (support.marginA < dimA || support.marginB < dimB) ){ if ( support.marginA < dimA ){ - support.marginA += 1; + support.marginA += inc; } if ( support.marginB < dimB ){ - support.marginB += 1; + support.marginB += inc; } result = Physics.gjk(support, d); diff --git a/src/util/scratchpad.js b/src/util/scratchpad.js index b959a6da..20ae3bcd 100644 --- a/src/util/scratchpad.js +++ b/src/util/scratchpad.js @@ -16,7 +16,7 @@ Physics.scratchpad = (function(){ var scratches = []; var numScratches = 0; var Scratch, Scratchpad; - + var regIndex = 0; @@ -24,7 +24,7 @@ Physics.scratchpad = (function(){ * class Scratch * * A scratchpad session. - * + * * This class keeps track of temporary objects used * in this session and releases them when finished (call to `.done()`). * @@ -39,7 +39,7 @@ Physics.scratchpad = (function(){ // private variables this._active = false; this._indexArr = []; - + if (++numScratches >= Scratchpad.maxScratches){ throw SCRATCH_MAX_REACHED; } @@ -52,9 +52,9 @@ Physics.scratchpad = (function(){ * - val (Mixed): No effect on this method, just passed on to the return value so you can do things like: return scratch.done( myReturnVal ); * + (Mixed): Whatever you specified as `val` - * + * * Declare that your work is finished. - * + * * Release temp objects for use elsewhere. Must be called when immediate work is done. * * You can wrap the return value in scratch.done() so that you don't forget to call it. @@ -70,10 +70,10 @@ Physics.scratchpad = (function(){ this._active = false; var s; for ( var i = 0; i < regIndex; ++i ){ - + this[ i ] = 0; } - + // add it back to the scratch stack for future use scratches.push( this ); return val; @@ -88,13 +88,13 @@ Physics.scratchpad = (function(){ * - fn (Function): Some function you'd like to wrap in a scratch session. First argument is the scratch instance. * + (Function): The wrapped function (if `fn` arg specified) that can be reused like the original minus the first (scratch) parameter. * + (Scratch): The scratch session. - * + * * Get a new scratch session to work from or wrap a function in a scratch session. - * + * * Call `.done()` on it when finished. * * Example: - * + * * ```javascript * // get a scratch session manually * var myAlg = function( scratch, arg1, arg2, ... ){ @@ -111,7 +111,7 @@ Physics.scratchpad = (function(){ * ``` * * Example: - * + * * ```javascript * // wrap a function in a scratch session * var myAlg = Physics.scratchpad(function( scratch, arg1, arg2, ... ){ @@ -144,13 +144,13 @@ Physics.scratchpad = (function(){ * Physics.scratchpad.fn( fn ) -> Function * - fn (Function): Some function you'd like to wrap in a scratch session. First argument is the scratch instance. See [[Physics.scratchpad]]. * + (Function): The wrapped function that can be reused like the original minus the first (scratch) parameter. - * + * * Wrap a function in a scratch session. * * Same as calling `Physics.scratchpad( fn )` with a function specified. **/ Scratchpad.fn = function( fn ){ - + var args = []; for ( var i = 0, l = fn.length; i < l; i++ ){ args.push( i ); @@ -173,7 +173,7 @@ Physics.scratchpad = (function(){ * Physics.scratchpad.register( name, constructor ) * - name (String): Name of the object class * - constructor (Function): The object constructor - * + * * Register a new object to be included in scratchpads. * * Example: @@ -196,7 +196,7 @@ Physics.scratchpad = (function(){ } // create a new function on the prototype - proto[ name ] = function(){ + Scratch.prototype[ name ] = function(){ // get the stack (or initialize it) var stack = this[ stackname ] || (this[ stackname ] = []) @@ -219,7 +219,7 @@ Physics.scratchpad = (function(){ } // return or create new instance - return stack[ stackIndex ] || + return stack[ stackIndex ] || (stack[ stackIndex ] = useFactory ? constructor() : new constructor() ); }; @@ -231,4 +231,4 @@ Physics.scratchpad = (function(){ return Scratchpad; -})(); \ No newline at end of file +})(); diff --git a/test/pixi-sandbox.html b/test/pixi-sandbox.html index 63576325..ce2fae78 100644 --- a/test/pixi-sandbox.html +++ b/test/pixi-sandbox.html @@ -289,9 +289,9 @@ Physics({ timestep: 6, maxIPF: 24 }, [ setup - ,addBodies + // ,addBodies // ,lotsOfCircles - // ,lotsOfRects + ,lotsOfRects //,initEvents ]); From 95da7b2f1e66ba40559846ae0bbf293044a4850e Mon Sep 17 00:00:00 2001 From: Jasper Palfree Date: Mon, 1 Dec 2014 22:36:43 -0500 Subject: [PATCH 079/110] feature: renderers now automatically resize --- src/core/renderer.js | 37 ++++++++++++++++++++++++++++++++-- src/renderers/canvas.js | 8 +++++--- src/renderers/dom.js | 8 ++++++++ src/renderers/pixi-renderer.js | 7 +++++++ test/collision-sandbox.html | 10 +++------ test/comparison-sandbox.html | 19 +---------------- test/contact-sandbox.html | 4 +--- test/integrator-sandbox.html | 1 + test/pixi-sandbox.html | 9 ++------- 9 files changed, 63 insertions(+), 40 deletions(-) diff --git a/src/core/renderer.js b/src/core/renderer.js index 16310699..1c3f481d 100644 --- a/src/core/renderer.js +++ b/src/core/renderer.js @@ -9,7 +9,9 @@ // width of viewport width: 600, // height of viewport - height: 600 + height: 600, + // automatically resize the renderer + autoResize: true }; /** related to: Physics.util.decorator @@ -29,6 +31,8 @@ width: 600, // height of viewport height: 600 + // automatically resize the renderer + autoResize: true } ``` * @@ -53,14 +57,43 @@ **/ init: function( options ){ - var el = typeof options.el === 'string' ? document.getElementById(options.el) : options.el + var self = this + ,el = typeof options.el === 'string' ? document.getElementById(options.el) : options.el ; this.options = Physics.util.options(defaults); this.options( options ); this.el = el ? el : document.body; + this.container = el && el.parentNode ? el.parentNode : document.body; this.drawMeta = Physics.util.throttle( Physics.util.bind(this.drawMeta, this), this.options.metaRefresh ); + + window.addEventListener('resize', Physics.util.throttle(function(){ + if ( self.options.autoResize ){ + self.resize(); + } + }), 100); + }, + + /** + * Renderer#resize( [width, height] ) -> this + * - width (Number): The width in px + * - height (Number): The height in px + * + * Set the dimensions of the renderer. + * + * If no dimensions are specified it will auto resize. + **/ + resize: function( width, height ){ + + if ( width === undefined && height === undefined ){ + width = this.container.offsetWidth; + height = this.container.offsetHeight; + } + + this.width = width || 0; + this.height = height || 0; + // should be implemented in renderers }, /** diff --git a/src/renderers/canvas.js b/src/renderers/canvas.js index f01841ca..783df3c0 100644 --- a/src/renderers/canvas.js +++ b/src/renderers/canvas.js @@ -129,6 +129,7 @@ Physics.renderer('canvas', function( proto ){ this.el = viewport; } + this.container = this.el.parentNode; this.ctx = viewport.getContext('2d'); this.els = {}; @@ -427,13 +428,14 @@ Physics.renderer('canvas', function( proto ){ resize: function( width, height ){ var layer; + proto.resize.call( this, width, height ); for ( var id in this._layers ){ layer = this._layers[ id ]; if ( layer.options.autoResize ){ - layer.el.width = width; - layer.el.height = height; + layer.el.width = this.width; + layer.el.height = this.height; } } @@ -619,7 +621,7 @@ Physics.renderer('canvas', function( proto ){ for ( var i = 0, l = geometry.children.length, ch; i < l; i++ ){ ch = geometry.children[ i ]; - + // translate ctx.translate(ch.pos.x, ch.pos.y); // rotate diff --git a/src/renderers/dom.js b/src/renderers/dom.js index 31b7e1b7..2effdc13 100644 --- a/src/renderers/dom.js +++ b/src/renderers/dom.js @@ -96,6 +96,14 @@ Physics.renderer('dom', function( proto ){ } }, + // extended + resize: function( width, height ){ + + proto.resize.call( this, width, height ); + this.el.style.width = this.width + px; + this.el.style.height = this.height + px; + }, + /** internal * DomRenderer#pointProperties( el, geometry ) * - el (HTMLElement): The element diff --git a/src/renderers/pixi-renderer.js b/src/renderers/pixi-renderer.js index d3220d5f..547b2560 100644 --- a/src/renderers/pixi-renderer.js +++ b/src/renderers/pixi-renderer.js @@ -147,6 +147,13 @@ Physics.renderer('pixi', function( parent ){ } }, + // extended + resize: function( width, height ){ + + parent.resize.call( this, width, height ); + this.renderer.resize( this.width, this.height ); + }, + // extended connect: function( world ){ diff --git a/test/collision-sandbox.html b/test/collision-sandbox.html index ad837567..ba7f423e 100644 --- a/test/collision-sandbox.html +++ b/test/collision-sandbox.html @@ -3,10 +3,12 @@