From df2a0485943983b53275db7ab045e4b497dd4f32 Mon Sep 17 00:00:00 2001 From: Mario Carballo Zama Date: Thu, 6 Feb 2014 00:20:40 -0600 Subject: [PATCH 01/99] Added description for pad len parameter --- src/utils/Utils.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/Utils.js b/src/utils/Utils.js index ac501e69..ccd6905a 100644 --- a/src/utils/Utils.js +++ b/src/utils/Utils.js @@ -38,9 +38,9 @@ Phaser.Utils = { * dir = 1 (left), 2 (right), 3 (both) * @method Phaser.Utils.pad * @param {string} str - The target string. - * @param {number} len - Description. - * @param {number} pad - the string to pad it out with (defaults to a space). - * @param {number} [dir=3] the direction dir = 1 (left), 2 (right), 3 (both). + * @param {number} len - The number of characters to be added. + * @param {number} pad - The string to pad it out with (defaults to a space). + * @param {number} [dir=3] The direction dir = 1 (left), 2 (right), 3 (both). * @return {string} The padded string */ pad: function (str, len, pad, dir) { From 9e5e30bb123bdcf39284bbb2d2f435b990eaf351 Mon Sep 17 00:00:00 2001 From: photonstorm Date: Thu, 6 Feb 2014 19:36:33 +0000 Subject: [PATCH 02/99] Fixed TypeScript defs on lines 1741-1748 (thanks wombatbuddy) --- README.md | 1 + build/phaser.d.ts | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6e10445f..d7a46322 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ Bug Fixes: * Explicitly paused Timer continues if you un-focus and focus the browser window (thanks georgiee) * Added TimerEvent.pendingDelete and checks in Timer.update, so that removing an event in a callback no longer throws an exception (thanks georgiee) +* Fixed TypeScript defs on lines 1741-1748 (thanks wombatbuddy) You can view the Change Log for all previous versions at https://github.com/photonstorm/phaser/changelog.md diff --git a/build/phaser.d.ts b/build/phaser.d.ts index 03393e79..cc1d3087 100644 --- a/build/phaser.d.ts +++ b/build/phaser.d.ts @@ -1738,14 +1738,14 @@ declare module Phaser { updateMotion(body: Phaser.Physics.Arcade.Body): Phaser.Point; overlap(object1: any, object2: any, overlapCallback?: Function, processCallback?: Function, callbackContext?: any): boolean; collide(object1: any, object2: any, collideCallback?: Function, processCallback?: Function, callbackContext?: any): boolean; - collideHandler(object1: any, object2: any, collideCallback?: Function, processCallback?: Function, callbackContext?: any, overlapOnly: boolean): boolean; - collideSpriteVsSprite(sprite1: Phaser.Sprite, sprite2: Phaser.Sprite, collideCallback?: Function, processCallback?: Function, callbackContext?: any, overlapOnly: boolean): boolean; - collideSpriteVsGroup(sprite1: Phaser.Sprite, group: Phaser.Group, collideCallback?: Function, processCallback?: Function, callbackContext?: any, overlapOnly: boolean): boolean; - collideGroupVsSelf(group: Phaser.Group, collideCallback?: Function, processCallback?: Function, callbackContext?: any, overlapOnly: boolean): boolean; - collideGroupVsGroup(group: Phaser.Group, group2: Phaser.Group, collideCallback?: Function, processCallback?: Function, callbackContext?: any, overlapOnly: boolean): boolean; - collideSpriteVsTilemapLayer(sprite: Phaser.Sprite, tilemapLayer: Phaser.TilemapLayer, collideCallback?: Function, processCallback?: Function, callbackContext?: any): boolean; - collideGroupVsTilemapLayer(group: Phaser.Group, tilemapLayer: Phaser.TilemapLayer, collideCallback?: Function, processCallback?: Function, callbackContext?: any): boolean; - separate(body: Phaser.Physics.Arcade.Body, body2: Phaser.Physics.Arcade.Body, processCallback?: Function, callbackContext?: any, overlapOnly: boolean): boolean; + collideHandler(object1: any, object2: any, collideCallback: Function, processCallback: Function, callbackContext: any, overlapOnly: boolean): boolean; + collideSpriteVsSprite(sprite1: Phaser.Sprite, sprite2: Phaser.Sprite, collideCallback: Function, processCallback: Function, callbackContext: any, overlapOnly: boolean): boolean; + collideSpriteVsGroup(sprite1: Phaser.Sprite, group: Phaser.Group, collideCallback: Function, processCallback: Function, callbackContext: any, overlapOnly: boolean): boolean; + collideGroupVsSelf(group: Phaser.Group, collideCallback: Function, processCallback: Function, callbackContext: any, overlapOnly: boolean): boolean; + collideGroupVsGroup(group: Phaser.Group, group2: Phaser.Group, collideCallback: Function, processCallback: Function, callbackContext: any, overlapOnly: boolean): boolean; + collideSpriteVsTilemapLayer(sprite: Phaser.Sprite, tilemapLayer: Phaser.TilemapLayer, collideCallback: Function, processCallback: Function, callbackContext: any): boolean; + collideGroupVsTilemapLayer(group: Phaser.Group, tilemapLayer: Phaser.TilemapLayer, collideCallback: Function, processCallback: Function, callbackContext: any): boolean; + separate(body: Phaser.Physics.Arcade.Body, body2: Phaser.Physics.Arcade.Body, processCallback: Function, callbackContext: any, overlapOnly: boolean): boolean; intersects(a: Phaser.Physics.Arcade.Body, b: Phaser.Physics.Arcade.Body): boolean; tileIntersects(body: Phaser.Physics.Arcade.Body, tile: Phaser.Tile): boolean; separateTiles(body: Phaser.Physics.Arcade.Body, tile: Array): boolean; From e11cae5373f302b6e54ee870cb3eba35532059ef Mon Sep 17 00:00:00 2001 From: photonstorm Date: Fri, 7 Feb 2014 02:49:59 +0000 Subject: [PATCH 03/99] Added the SAT class to the TypeScript defs file. Also this fixes #369. --- build/phaser.d.ts | 75 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/build/phaser.d.ts b/build/phaser.d.ts index cc1d3087..ff10a516 100644 --- a/build/phaser.d.ts +++ b/build/phaser.d.ts @@ -1,3 +1,76 @@ +declare class SAT { + + flattenPointsOn(points: Array, normal: SAT.Vector, result: Array): Array; + isSeparatingAxis(aPos: SAT.Vector, bPos: SAT.Vector, aPoints: Array, bPoints: Array, axis: SAT.Vector, response: SAT.Response): boolean; + vornoiRegion(line: SAT.Vector, point: SAT.Vector): number; + testCircleCircle(a: SAT.Circle, b: SAT.Circle, response: SAT.Response): boolean; + testPolygonCircle(a: SAT.Polygon, b: SAT.Circle, response: SAT.Response): boolean; + testCirclePolygon(a: SAT.Circle, b: SAT.Polygon, response: SAT.Response): boolean; + testPolygonPolygon(a: SAT.Polygon, b: SAT.Polygon, response: SAT.Response): boolean; + +} + +declare module SAT { + + class Vector { + constructor(x: number, y: number); + x: number; + y: number; + copy(other: SAT.Vector): SAT.Vector; + perp(): SAT.Vector; + rotate(angle: number): SAT.Vector; + rotatePrecalc(sin: number, cos: number): SAT.Vector; + reverse(): SAT.Vector; + normalize(): SAT.Vector; + add(other: SAT.Vector): SAT.Vector; + sub(other: SAT.Vector): SAT.Vector; + scale(x: number, y: number): SAT.Vector; + project(other: SAT.Vector): SAT.Vector; + projectN(other: SAT.Vector): SAT.Vector; + reflect(axis: SAT.Vector): SAT.Vector; + reflectN(axis: SAT.Vector): SAT.Vector; + dot(other: SAT.Vector): SAT.Vector; + len2(): SAT.Vector; + len(): SAT.Vector; + } + + class Circle { + constructor(pos: SAT.Vector, radius: number); + pos: SAT.Vector; + r: number; + } + + class Polygon { + constructor(pos: SAT.Vector, points: Array); + pos: SAT.Vector; + points: Array; + recalc(): SAT.Polygon; + rotate(angle: number): SAT.Polygon; + scale(x: number, y: number): SAT.Polygon; + translate(x: number, y: number): SAT.Polygon; + } + + class Box { + constructor(pos: SAT.Vector, w: number, h: number); + pos: SAT.Vector; + w: number; + h: number; + toPolygon(): SAT.Polygon; + } + + class Response { + constructor(); + a: any; + b: any; + overlapN: SAT.Vector; + overlapV: SAT.Vector; + clear(): SAT.Response; + aInB: boolean; + bInA: boolean; + overlap: number; + } +} + declare class Phaser { static VERSION: string; static DEV_VERSION: string; @@ -1748,7 +1821,7 @@ declare module Phaser { separate(body: Phaser.Physics.Arcade.Body, body2: Phaser.Physics.Arcade.Body, processCallback: Function, callbackContext: any, overlapOnly: boolean): boolean; intersects(a: Phaser.Physics.Arcade.Body, b: Phaser.Physics.Arcade.Body): boolean; tileIntersects(body: Phaser.Physics.Arcade.Body, tile: Phaser.Tile): boolean; - separateTiles(body: Phaser.Physics.Arcade.Body, tile: Array): boolean; + separateTiles(body: Phaser.Physics.Arcade.Body, tile: Array): boolean; separateTile(body: Phaser.Physics.Arcade.Body, tile: Phaser.Tile): boolean; processTileSeparation(body: Phaser.Physics.Arcade.Body): boolean; moveToObject(displayObject: Phaser.Sprite, destination: Phaser.Sprite, speed?: number, maxTime?: number): number; From e8b432f5182d35edbc3b052cce8d21fc4895033a Mon Sep 17 00:00:00 2001 From: photonstorm Date: Sat, 8 Feb 2014 13:45:18 +0000 Subject: [PATCH 04/99] Fixed bug where changing State would cause the camera to not reset if it was following an object. World.reset now calls Camera.reset which sends the camera back to 0,0 and un-follows any object it may have been tracking. --- README.md | 2 ++ src/core/Camera.js | 13 +++++++++++++ src/core/World.js | 3 +-- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 24d9d041..4d496847 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,7 @@ Updates: * Game.add.renderTexture now has the addToCache parameter. If set the texture will be stored in Game.Cache and can be retrieved with Cache.getTexture(key). * Game.add.bitmapData now has the addToCache parameter. If set the texture will be stored in Game.Cache and can be retrieved with Cache.getBitmapData(key). * The InputManager now sets the canvas style cursor to 'inherit' instead of 'default'. +* World.reset now calls Camera.reset which sends the camera back to 0,0 and un-follows any object it may have been tracking. Bug Fixes: @@ -101,6 +102,7 @@ Bug Fixes: * Fixed TypeScript defs on lines 1741-1748 (thanks wombatbuddy) * Previously if you used Sprite.crop() it would crop all Sprites using the same base image. It now takes a local copy of the texture data and crops just that. * Tilemap had the wrong @method signatures so most were missing from the docs. +* Fixed bug where changing State would cause the camera to not reset if it was following an object. You can view the Change Log for all previous versions at https://github.com/photonstorm/phaser/changelog.md diff --git a/src/core/Camera.js b/src/core/Camera.js index 8d9baa0e..d87d65ba 100644 --- a/src/core/Camera.js +++ b/src/core/Camera.js @@ -330,6 +330,19 @@ Phaser.Camera.prototype = { this.view.width = width; this.view.height = height; + }, + + /** + * Resets the camera back to 0,0 and un-follows any object it may have been tracking. + * + * @method Phaser.Camera#reset + */ + reset: function () { + + this.target = null; + this.camera.x = 0; + this.camera.y = 0; + } }; diff --git a/src/core/World.js b/src/core/World.js index 601ec6f6..943d1f99 100644 --- a/src/core/World.js +++ b/src/core/World.js @@ -178,8 +178,7 @@ Phaser.World.prototype.setBounds = function (x, y, width, height) { */ Phaser.World.prototype.destroy = function () { - this.camera.x = 0; - this.camera.y = 0; + this.camera.reset(); this.game.input.reset(true); From 4aa945f991a205792aa3b87e364b91cdf83a918c Mon Sep 17 00:00:00 2001 From: photonstorm Date: Sun, 9 Feb 2014 03:48:31 +0000 Subject: [PATCH 05/99] Removed PixiPatch as it's no longer needed. Re-worked all of the Sprite autoCull and inWorld checks and cached the bounds. Fixed the Body calculations so physics is working again. --- examples/physics/ship trail.js | 2 +- examples/wip/pixi1.js | 24 +- src/PixiPatch.js | 280 -------------------- src/core/Camera.js | 4 +- src/core/Stage.js | 2 +- src/gameobjects/Sprite.js | 108 ++++---- src/physics/arcade/Body.js | 4 +- src/pixi/Pixi.js | 10 +- src/pixi/display/DisplayObject.js | 4 - src/pixi/display/Sprite.js | 10 +- src/pixi/display/SpriteBatch.js | 10 +- src/pixi/display/Stage.js | 3 - src/pixi/renderers/canvas/CanvasRenderer.js | 21 +- 13 files changed, 104 insertions(+), 378 deletions(-) delete mode 100644 src/PixiPatch.js diff --git a/examples/physics/ship trail.js b/examples/physics/ship trail.js index 7e048a6d..96f26c82 100644 --- a/examples/physics/ship trail.js +++ b/examples/physics/ship trail.js @@ -22,7 +22,7 @@ function create() { var bg = game.add.sprite(0, 0, bmd); bg.body.moves = false; - game.enableStep(); + // game.enableStep(); game.physics.gravity.y = 100; diff --git a/examples/wip/pixi1.js b/examples/wip/pixi1.js index cf625586..a426e891 100644 --- a/examples/wip/pixi1.js +++ b/examples/wip/pixi1.js @@ -1,5 +1,5 @@ -var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create, update: update, render: render }); +var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create, update: update, render: render }, false); function preload() { @@ -16,22 +16,20 @@ function create() { // game.stage.backgroundColor = '#ff5500'; - game.stage._stage.setBackgroundColor(0xff5500); + sprite = game.add.sprite(0.5, 0, 'pic'); - sprite = game.add.sprite(0, 0, 'pic'); + // g = game.add.group(null, 'billy'); - g = game.add.group(null, 'billy'); + // sprite2 = g.create(0, 0, 'pic'); - sprite2 = g.create(0, 0, 'pic'); + // g.y = 200; + // g.rotation = 0.1; - g.y = 200; - g.rotation = 0.1; + sprite2 = game.add.sprite(0, 300, 'pic'); - // sprite2 = game.add.sprite(0, 300, 'pic'); + // game.input.onDown.add(tint, this); - game.input.onDown.add(tint, this); - - game.add.tween(sprite).to({y: 500}, 3000, Phaser.Easing.Linear.None, true); + // game.add.tween(sprite).to({y: 500}, 3000, Phaser.Easing.Linear.None, true); p = new PIXI.Point(43, 45); @@ -39,7 +37,7 @@ function create() { function tint() { - sprite.tint = Math.random() * 0xFFFFFF; + // sprite.tint = Math.random() * 0xFFFFFF; // sprite2.tint = Math.random() * 0xFFFFFF; } @@ -51,6 +49,6 @@ function update() { function render() { - game.debug.renderText(sprite.position.y, 32, 32); + // game.debug.renderText(sprite.position.y, 32, 32); } diff --git a/src/PixiPatch.js b/src/PixiPatch.js deleted file mode 100644 index a71923ca..00000000 --- a/src/PixiPatch.js +++ /dev/null @@ -1,280 +0,0 @@ -/** -* We're replacing a couple of Pixi's methods here to fix or add some vital functionality: -* -* 1) Added support for Trimmed sprite sheets -* 2) Skip display objects with an alpha of zero -* 3) Avoid Style Recalculation from the incorrect bgcolor value -* 4) Added support for Canvas unit rounding via Phaser.CANVAS_PX_ROUND boolean (disabled by default). -* -* Hopefully we can remove this once Pixi has been updated to support these things. -*/ - -/** - * Renders the stage to its canvas view - * - * @method render - * @param stage {Stage} the Stage element to be rendered - */ -PIXI.CanvasRenderer.prototype.render = function(stage) -{ - PIXI.texturesToUpdate.length = 0; - PIXI.texturesToDestroy.length = 0; - - PIXI.visibleCount++; - stage.updateTransform(); - - this.context.setTransform(1, 0, 0, 1, 0, 0); - - if (Phaser.CANVAS_CLEAR_RECT) - { - this.context.clearRect(0, 0, this.width, this.height) - } - - this.renderDisplayObject(stage, false); - - // Remove frame updates - if (PIXI.Texture.frameUpdates.length > 0) - { - PIXI.Texture.frameUpdates.length = 0; - } - -} - -// @param {boolean} [renderHidden=false] - If true displayObjects that have their visible property set to false will still be rendered. - -PIXI.CanvasRenderer.prototype.renderDisplayObject = function(displayObject, renderHidden) -{ - // Once the display object hits this we can break the loop - var testObject = displayObject.last._iNext; - displayObject = displayObject.first; - - do - { - if (!displayObject.visible && !renderHidden) - { - displayObject = displayObject.last._iNext; - continue; - } - - if (!displayObject.renderable || displayObject.alpha === 0) - { - displayObject = displayObject._iNext; - continue; - } - - if (displayObject instanceof PIXI.Sprite) - { - if (displayObject.texture.frame) - { - this.context.globalAlpha = displayObject.worldAlpha; - - if (Phaser.CANVAS_PX_ROUND) - { - this.context.setTransform( - displayObject.worldTransform[0], - displayObject.worldTransform[3], - displayObject.worldTransform[1], - displayObject.worldTransform[4], - Math.floor(displayObject.worldTransform[2]), - Math.floor(displayObject.worldTransform[5])); - } - else - { - this.context.setTransform( - displayObject.worldTransform[0], - displayObject.worldTransform[3], - displayObject.worldTransform[1], - displayObject.worldTransform[4], - displayObject.worldTransform[2], - displayObject.worldTransform[5]); - } - - if (displayObject.texture.trimmed) - { - this.context.transform(1, 0, 0, 1, displayObject.texture.trim.x, displayObject.texture.trim.y); - } - - //if smoothingEnabled is supported and we need to change the smoothing property for this texture - if (this.smoothProperty && this.scaleMode !== displayObject.texture.baseTexture.scaleMode) - { - this.scaleMode = displayObject.texture.baseTexture.scaleMode; - this.context[this.smoothProperty] = (this.scaleMode === PIXI.BaseTexture.SCALE_MODE.LINEAR); - } - - this.context.drawImage( - displayObject.texture.baseTexture.source, - displayObject.texture.frame.x, - displayObject.texture.frame.y, - displayObject.texture.frame.width, - displayObject.texture.frame.height, - Math.floor((displayObject.anchor.x) * -displayObject.texture.frame.width), - Math.floor((displayObject.anchor.y) * -displayObject.texture.frame.height), - displayObject.texture.frame.width, - displayObject.texture.frame.height); - } - } - else if (displayObject instanceof PIXI.Strip) - { - this.context.setTransform(displayObject.worldTransform[0], displayObject.worldTransform[3], displayObject.worldTransform[1], displayObject.worldTransform[4], displayObject.worldTransform[2], displayObject.worldTransform[5]) - this.renderStrip(displayObject); - } - else if (displayObject instanceof PIXI.TilingSprite) - { - this.context.setTransform(displayObject.worldTransform[0], displayObject.worldTransform[3], displayObject.worldTransform[1], displayObject.worldTransform[4], displayObject.worldTransform[2], displayObject.worldTransform[5]) - this.renderTilingSprite(displayObject); - } - else if (displayObject instanceof PIXI.CustomRenderable) - { - displayObject.renderCanvas(this); - } - else if (displayObject instanceof PIXI.Graphics) - { - this.context.setTransform(displayObject.worldTransform[0], displayObject.worldTransform[3], displayObject.worldTransform[1], displayObject.worldTransform[4], displayObject.worldTransform[2], displayObject.worldTransform[5]) - PIXI.CanvasGraphics.renderGraphics(displayObject, this.context); - } - else if (displayObject instanceof PIXI.FilterBlock) - { - if (displayObject.open) - { - this.context.save(); - - var cacheAlpha = displayObject.mask.alpha; - var maskTransform = displayObject.mask.worldTransform; - - this.context.setTransform(maskTransform[0], maskTransform[3], maskTransform[1], maskTransform[4], maskTransform[2], maskTransform[5]) - - displayObject.mask.worldAlpha = 0.5; - - this.context.worldAlpha = 0; - - PIXI.CanvasGraphics.renderGraphicsMask(displayObject.mask, this.context); - this.context.clip(); - - displayObject.mask.worldAlpha = cacheAlpha; - } - else - { - this.context.restore(); - } - } - // count++ - displayObject = displayObject._iNext; - } - while(displayObject != testObject) - -} - -PIXI.WebGLBatch.prototype.update = function() -{ - // var gl = this.gl; - // var worldTransform, width, height, aX, aY, w0, w1, h0, h1, index, index2, index3 - - var worldTransform, width, height, aX, aY, w0, w1, h0, h1, index; - - var a, b, c, d, tx, ty; - - var indexRun = 0; - - var displayObject = this.head; - - while(displayObject) - { - if(displayObject.vcount === PIXI.visibleCount) - { - width = displayObject.texture.frame.width; - height = displayObject.texture.frame.height; - - // TODO trim?? - aX = displayObject.anchor.x;// - displayObject.texture.trim.x - aY = displayObject.anchor.y; //- displayObject.texture.trim.y - w0 = width * (1-aX); - w1 = width * -aX; - - h0 = height * (1-aY); - h1 = height * -aY; - - index = indexRun * 8; - - worldTransform = displayObject.worldTransform; - - a = worldTransform[0]; - b = worldTransform[3]; - c = worldTransform[1]; - d = worldTransform[4]; - tx = worldTransform[2]; - ty = worldTransform[5]; - - if (displayObject.texture.trimmed) - { - tx += displayObject.texture.trim.x; - ty += displayObject.texture.trim.y; - } - - this.verticies[index + 0 ] = a * w1 + c * h1 + tx; - this.verticies[index + 1 ] = d * h1 + b * w1 + ty; - - this.verticies[index + 2 ] = a * w0 + c * h1 + tx; - this.verticies[index + 3 ] = d * h1 + b * w0 + ty; - - this.verticies[index + 4 ] = a * w0 + c * h0 + tx; - this.verticies[index + 5 ] = d * h0 + b * w0 + ty; - - this.verticies[index + 6] = a * w1 + c * h0 + tx; - this.verticies[index + 7] = d * h0 + b * w1 + ty; - - if(displayObject.updateFrame || displayObject.texture.updateFrame) - { - this.dirtyUVS = true; - - var texture = displayObject.texture; - - var frame = texture.frame; - var tw = texture.baseTexture.width; - var th = texture.baseTexture.height; - - this.uvs[index + 0] = frame.x / tw; - this.uvs[index +1] = frame.y / th; - - this.uvs[index +2] = (frame.x + frame.width) / tw; - this.uvs[index +3] = frame.y / th; - - this.uvs[index +4] = (frame.x + frame.width) / tw; - this.uvs[index +5] = (frame.y + frame.height) / th; - - this.uvs[index +6] = frame.x / tw; - this.uvs[index +7] = (frame.y + frame.height) / th; - - displayObject.updateFrame = false; - } - - // TODO this probably could do with some optimisation.... - if(displayObject.cacheAlpha != displayObject.worldAlpha) - { - displayObject.cacheAlpha = displayObject.worldAlpha; - - var colorIndex = indexRun * 4; - this.colors[colorIndex] = this.colors[colorIndex + 1] = this.colors[colorIndex + 2] = this.colors[colorIndex + 3] = displayObject.worldAlpha; - this.dirtyColors = true; - } - } - else - { - index = indexRun * 8; - - this.verticies[index + 0 ] = 0; - this.verticies[index + 1 ] = 0; - - this.verticies[index + 2 ] = 0; - this.verticies[index + 3 ] = 0; - - this.verticies[index + 4 ] = 0; - this.verticies[index + 5 ] = 0; - - this.verticies[index + 6] = 0; - this.verticies[index + 7] = 0; - } - - indexRun++; - displayObject = displayObject.__next; - } -} diff --git a/src/core/Camera.js b/src/core/Camera.js index d87d65ba..da95dd59 100644 --- a/src/core/Camera.js +++ b/src/core/Camera.js @@ -340,8 +340,8 @@ Phaser.Camera.prototype = { reset: function () { this.target = null; - this.camera.x = 0; - this.camera.y = 0; + this.view.x = 0; + this.view.y = 0; } diff --git a/src/core/Stage.js b/src/core/Stage.js index ce3fa01f..3312f78b 100644 --- a/src/core/Stage.js +++ b/src/core/Stage.js @@ -175,7 +175,7 @@ Phaser.Stage.prototype = { Phaser.Canvas.setUserSelect(this.canvas, 'none'); Phaser.Canvas.setTouchAction(this.canvas, 'none'); - this.backgroundColor = '#000'; + this.backgroundColor = '#000000'; document.addEventListener('visibilitychange', this._onChange, false); document.addEventListener('webkitvisibilitychange', this._onChange, false); diff --git a/src/gameobjects/Sprite.js b/src/gameobjects/Sprite.js index 83b22fb1..9c35196d 100644 --- a/src/gameobjects/Sprite.js +++ b/src/gameobjects/Sprite.js @@ -130,11 +130,20 @@ Phaser.Sprite = function (game, x, y, key, frame) { this.lifespan = 0; /** - * @property {boolean} outOfBoundsKill - If true the Sprite is killed as soon as Sprite.inWorld is false. + * If true the Sprite checks if it is still within the world each frame, when it leaves the world it dispatches Sprite.events.onOutOfBounds + * and optionally kills the sprite (if Sprite.outOfBoundsKill is true). By default this is disabled because the Sprite has to calculate its + * bounds every frame to support it, and not all games need it. Enable it by setting the value to true. + * @property {boolean} checkWorldBounds + * @default + */ + this.checkWorldBounds = false; + + /** + * @property {boolean} outOfBoundsKill - If true Sprite.kill is called as soon as Sprite.inWorld returns false, as long as Sprite.checkWorldBounds is true. * @default */ this.outOfBoundsKill = false; - + /** * @property {boolean} debug - Handy flag to use with Game.enableStep * @default @@ -142,16 +151,23 @@ Phaser.Sprite = function (game, x, y, key, frame) { this.debug = false; /** - * @property {boolean} _outOfBoundsFired - Internal flag. + * A small internal cache: + * 0 = previous position.x + * 1 = previous position.y + * 2 = previous rotation + * 3 = renderID + * 4 = fresh? (0 = no, 1 = yes) + * 5 = outOfBoundsFired (0 = no, 1 = yes) + * @property {array} _cache * @private */ - this._outOfBoundsFired = false; + this._cache = [0, 0, 0, 0, 1, 0]; /** - * @property {array} _cache - A small cache for previous step values. 0 = x, 1 = y, 2 = rotation, 3 = renderID, 4 = fresh? (0 = no, 1 = yes) + * @property {Phaser.Rectangle} _bounds - Internal cache var. * @private */ - this._cache = [0, 0, 0, 0, 1]; + this._bounds = new Phaser.Rectangle(); }; @@ -163,19 +179,18 @@ Phaser.Sprite.prototype.constructor = Phaser.Sprite; * * @method Phaser.Sprite#preUpdate * @memberof Phaser.Sprite +* @return {boolean} True if the Sprite was rendered, otherwise false. */ Phaser.Sprite.prototype.preUpdate = function() { - /* if (this._cache[4] === 1) { - console.log('sprite cache fresh'); this.world.setTo(this.parent.position.x + this.position.x, this.parent.position.y + this.position.y); - this.worldTransform[2] = this.world.x; - this.worldTransform[5] = this.world.y; - // this._cache[0] = this.world.x; - // this._cache[1] = this.world.y; - // this._cache[2] = this.rotation; + this.worldTransform.tx = this.world.x; + this.worldTransform.ty = this.world.y; + this._cache[0] = this.world.x; + this._cache[1] = this.world.y; + this._cache[2] = this.rotation; this._cache[4] = 0; if (this.body) @@ -186,9 +201,8 @@ Phaser.Sprite.prototype.preUpdate = function() { this.body.preY = this.body.y; } - return; + return false; } - */ this._cache[0] = this.world.x; this._cache[1] = this.world.y; @@ -196,7 +210,8 @@ Phaser.Sprite.prototype.preUpdate = function() { if (!this.exists || !this.parent.exists) { - this.renderOrderID = -1; + // Reset the renderOrderID + this._cache[3] = -1; return false; } @@ -211,13 +226,40 @@ Phaser.Sprite.prototype.preUpdate = function() { } } + // Cache the bounds if we need it + if (this.autoCull || this.checkWorldBounds) + { + this._bounds.copyFrom(this.getBounds()); + } + if (this.autoCull) { // Won't get rendered but will still get its transform updated - this.renderable = this.game.world.camera.screenView.intersects(this.getBounds()); + this.renderable = this.game.world.camera.screenView.intersects(this._bounds); } - this.world.setTo(this.game.camera.x + this.worldTransform[2], this.game.camera.y + this.worldTransform[5]); + if (this.checkWorldBounds) + { + // The Sprite is already out of the world bounds, so let's check to see if it has come back again + if (this._cache[5] === 1 && this.game.world.bounds.intersects(this._bounds)) + { + this._cache[5] = 0; + } + else if (this._cache[5] === 0 && !this.game.world.bounds.intersects(this._bounds)) + { + // The Sprite WAS in the screen, but has now left. + this._cache[5] = 1; + this.events.onOutOfBounds.dispatch(this); + + if (this.outOfBoundsKill) + { + this.kill(); + return false; + } + } + } + + this.world.setTo(this.game.camera.x + this.worldTransform.tx, this.game.camera.y + this.worldTransform.ty); if (this.visible) { @@ -226,36 +268,6 @@ Phaser.Sprite.prototype.preUpdate = function() { this.animations.update(); - if (!this.inWorld && Phaser.Rectangle.intersects(this.getBounds(), this.game.world.bounds, this.inWorldThreshold)) - { - // It's back again, reset the OOB check - this._outOfBoundsFired = false; - } - else - { - // Sprite WAS in the screen, has it now left? - this.inWorld = Phaser.Rectangle.intersects(this.getBounds(), this.game.world.bounds, this.inWorldThreshold); - - if (this.inWorld === false) - { - this.events.onOutOfBounds.dispatch(this); - this._outOfBoundsFired = true; - - if (this.outOfBoundsKill) - { - this.kill(); - } - } - } - - this._cache.cameraVisible = Phaser.Rectangle.intersects(this.game.world.camera.screenView, this.getBounds(), 0); - - if (this.autoCull) - { - // Won't get rendered but will still get its transform updated - this.renderable = this._cache.cameraVisible; - } - if (this.body) { this.body.preUpdate(); diff --git a/src/physics/arcade/Body.js b/src/physics/arcade/Body.js index 57c23358..e3c782fd 100644 --- a/src/physics/arcade/Body.js +++ b/src/physics/arcade/Body.js @@ -73,7 +73,7 @@ Phaser.Physics.Arcade.Body = function (sprite) { * @property {number} deltaCap - The maximum a delta is allowed to reach before its capped. * @default */ - this.deltaCap = 2; + this.deltaCap = 10; /** * @property {Phaser.Point} gravity - The gravity applied to the motion of the Body. This works in addition to any gravity set on the world. @@ -387,8 +387,6 @@ Phaser.Physics.Arcade.Body.prototype = { this.x = (this.sprite.world.x - (this.sprite.anchor.x * this.sprite.width)) + this.offset.x; this.y = (this.sprite.world.y - (this.sprite.anchor.y * this.sprite.height)) + this.offset.y; - // console.log('body pre', this.preX, this.preY, 'now', this.x, this.y); - // This covers any motion that happens during this frame, not since the last frame this.preX = this.x; this.preY = this.y; diff --git a/src/pixi/Pixi.js b/src/pixi/Pixi.js index 3a4ebf3d..79b8c715 100644 --- a/src/pixi/Pixi.js +++ b/src/pixi/Pixi.js @@ -44,15 +44,15 @@ PIXI.scaleModes = { // Canvas specific controls PIXI.canvas = { - // If the Stage is transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - // Setting this to false forces Pixi to update the view.style.backgroundColor instead. + // If the Stage is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + // Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. FILL_RECT: true, - // If the Stage is transparent Pixi will use clearRect to clear the canvas unless you set this to false. - // You often don't need clearRect if you've got a large background image fully covering your canvas. + // 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. CLEAR_RECT: true, - // If true Pixi will round all x/y values for rendering only, stopping pixel interpolation. Handy for crisp pixel art. + // If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. Handy for crisp pixel art and speed on legacy devices. PX_ROUND: false } diff --git a/src/pixi/display/DisplayObject.js b/src/pixi/display/DisplayObject.js index c1c82429..523ec4bf 100644 --- a/src/pixi/display/DisplayObject.js +++ b/src/pixi/display/DisplayObject.js @@ -196,8 +196,6 @@ PIXI.DisplayObject = function() */ this._mask = null; - this.x = this.position.x; - this.y = this.position.y; /* * MOUSE Callbacks @@ -395,8 +393,6 @@ PIXI.DisplayObject.prototype.updateTransform = function() // TODO OPTIMIZE THIS!! with dirty if(this.rotation !== this.rotationCache) { - if(isNaN(parseFloat(this.rotation))) - throw new Error('DisplayObject rotation values must be numeric.'); this.rotationCache = this.rotation; this._sr = Math.sin(this.rotation); diff --git a/src/pixi/display/Sprite.js b/src/pixi/display/Sprite.js index 35b4235b..1d065377 100644 --- a/src/pixi/display/Sprite.js +++ b/src/pixi/display/Sprite.js @@ -345,7 +345,15 @@ PIXI.Sprite.prototype._renderCanvas = function(renderSession) // allow for trimming - context.setTransform(transform.a, transform.c, transform.b, transform.d, transform.tx, transform.ty); + if (PIXI.canvas.PX_ROUND) + { + context.setTransform(transform.a, transform.c, transform.b, transform.d, Math.floor(transform.tx), Math.floor(transform.ty)); + } + else + { + context.setTransform(transform.a, transform.c, transform.b, transform.d, transform.tx, transform.ty); + } + //if smoothingEnabled is supported and we need to change the smoothing property for this texture if(renderSession.smoothProperty && renderSession.scaleMode !== this.texture.baseTexture.scaleMode) { diff --git a/src/pixi/display/SpriteBatch.js b/src/pixi/display/SpriteBatch.js index 7271cf16..ed3b9211 100644 --- a/src/pixi/display/SpriteBatch.js +++ b/src/pixi/display/SpriteBatch.js @@ -91,7 +91,15 @@ PIXI.SpriteBatch.prototype._renderCanvas = function(renderSession) // alow for trimming - context.setTransform(transform.a, transform.c, transform.b, transform.d, transform.tx, transform.ty); + if (PIXI.canvas.PX_ROUND) + { + context.setTransform(transform.a, transform.c, transform.b, transform.d, Math.floor(transform.tx), Math.floor(transform.ty)); + } + else + { + context.setTransform(transform.a, transform.c, transform.b, transform.d, transform.tx, transform.ty); + } + context.save(); for (var i = 0; i < this.children.length; i++) { diff --git a/src/pixi/display/Stage.js b/src/pixi/display/Stage.js index 8871806e..50cea165 100644 --- a/src/pixi/display/Stage.js +++ b/src/pixi/display/Stage.js @@ -63,8 +63,6 @@ PIXI.Stage = function(backgroundColor) //optimize hit detection a bit this.stage.hitArea = new PIXI.Rectangle(0,0,100000, 100000); - this.resetBackgroundColor = false; - this.setBackgroundColor(backgroundColor); }; @@ -123,7 +121,6 @@ PIXI.Stage.prototype.setBackgroundColor = function(backgroundColor) var hex = this.backgroundColor.toString(16); hex = '000000'.substr(0, 6 - hex.length) + hex; this.backgroundColorString = '#' + hex; - this.resetBackgroundColor = true; }; /** diff --git a/src/pixi/renderers/canvas/CanvasRenderer.js b/src/pixi/renderers/canvas/CanvasRenderer.js index 5ec8ce48..9c93ee6f 100644 --- a/src/pixi/renderers/canvas/CanvasRenderer.js +++ b/src/pixi/renderers/canvas/CanvasRenderer.js @@ -165,27 +165,16 @@ PIXI.CanvasRenderer.prototype.render = function(stage) this.context.setTransform(1,0,0,1,0,0); this.context.globalAlpha = 1; - // Update the background color / cls - if (!this.transparent) + if (!this.transparent && PIXI.canvas.FILL_RECT) { - if (PIXI.canvas.FILL_RECT) - { - this.context.fillStyle = stage.backgroundColorString; - this.context.fillRect(0, 0, this.width, this.height); - } - else if (stage.resetBackgroundColor) - { - this.view.style.backgroundColor = stage.backgroundColorString; - stage.resetBackgroundColor = false; - console.log('bgcolor reset', this.view.style.backgroundColor); - } + this.context.fillStyle = stage.backgroundColorString; + this.context.fillRect(0, 0, this.width, this.height); } - - if (PIXI.canvas.CLEAR_RECT && !PIXI.canvas.FILL_RECT) + else if (this.transparent && PIXI.canvas.CLEAR_RECT) { this.context.clearRect(0, 0, this.width, this.height); } - + this.renderDisplayObject(stage); // run interaction! From bca64c2adbaa18ed540be961787715943346b39e Mon Sep 17 00:00:00 2001 From: photonstorm Date: Sun, 9 Feb 2014 13:36:02 +0000 Subject: [PATCH 06/99] Huge update to Phaser.Text. Much more lean, but loads of great new options added including drop shadows, gradient fills, fonts with spaces in the name, etc. --- README.md | 5 + examples/wip/destroy.js | 49 ++ examples/wip/pixi1.js | 6 +- examples/wip/text.js | 60 ++ src/core/World.js | 2 +- src/gameobjects/Text.js | 686 +++++++++++++++----- src/pixi/Pixi.js | 15 - src/pixi/display/Sprite.js | 2 +- src/pixi/display/SpriteBatch.js | 2 +- src/pixi/renderers/canvas/CanvasRenderer.js | 25 +- 10 files changed, 682 insertions(+), 170 deletions(-) create mode 100644 examples/wip/destroy.js create mode 100644 examples/wip/text.js diff --git a/README.md b/README.md index 4d496847..ce08fcb7 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ Significant API changes: * PixiPatch no longer needed, all features that it patched are now native in Pixi :) * Removed: Sprite.offset, center, topLeft, topRight, bottomRight, bottomLeft and bounds as no longer needed internally. Use Sprite.getBounds() to derive them. * Button now extends Phaser.Image not Phaser.Sprite, all the same functionality as before remains, just no animations or physics body. +* Text.content has been replaced with Text.text. New features: @@ -80,6 +81,10 @@ New features: * You can now use the hitArea property on Sprites and Image objects. hitArea can be a geometry object (Rectangle, Circle, Polygon, Ellipse) and is used in pointerOver checks. * InputManager.getLocalPosition(displayObject, pointer, output) will return the local coordinates of the specified displayObject and pointer. * InputManager.hitTest will test for pointer hits against a Sprite/Image, its hitArea (if set) or any of its children. +* Text has lots of new methods to help style it: Text.fill, Text.align, Text.stroke, etc. +* Text now works happily with font names with spaces in them. +* Text.setShadow applies a drop shadow to the Text being rendered. Control the x, y, color and blur. +* Text.lineSpacing allows you to control the spacing between each line that is rendered. New Examples: diff --git a/examples/wip/destroy.js b/examples/wip/destroy.js new file mode 100644 index 00000000..698e0a4b --- /dev/null +++ b/examples/wip/destroy.js @@ -0,0 +1,49 @@ + +var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create, update: update, render: render }, false); + +function preload() { + + game.load.image('pic', 'assets/pics/backscroll.png'); + +} + +var sprite; +var sprite2; +var g; +var p; + +function create() { + + game.stage.backgroundColor = '#ff5500'; + + game.renderer.useFillRect = false; + + sprite = game.add.sprite(0.5, 0, 'pic'); + sprite2 = game.add.sprite(0, 300, 'pic'); + + game.input.onDown.add(tint, this); + + // game.add.tween(sprite).to({y: 500}, 3000, Phaser.Easing.Linear.None, true); + + // p = new PIXI.Point(43, 45); + +} + +function tint() { + + sprite.destroy(); + // sprite.tint = Math.random() * 0xFFFFFF; + // sprite2.tint = Math.random() * 0xFFFFFF; + +} + +function update() { + + +} + +function render() { + + // game.debug.renderText(sprite.position.y, 32, 32); + +} diff --git a/examples/wip/pixi1.js b/examples/wip/pixi1.js index a426e891..46403d4e 100644 --- a/examples/wip/pixi1.js +++ b/examples/wip/pixi1.js @@ -14,7 +14,9 @@ var p; function create() { - // game.stage.backgroundColor = '#ff5500'; + game.stage.backgroundColor = '#ff5500'; + + game.renderer.useFillRect = false; sprite = game.add.sprite(0.5, 0, 'pic'); @@ -29,7 +31,7 @@ function create() { // game.input.onDown.add(tint, this); - // game.add.tween(sprite).to({y: 500}, 3000, Phaser.Easing.Linear.None, true); + game.add.tween(sprite).to({y: 500}, 3000, Phaser.Easing.Linear.None, true); p = new PIXI.Point(43, 45); diff --git a/examples/wip/text.js b/examples/wip/text.js new file mode 100644 index 00000000..c5597b35 --- /dev/null +++ b/examples/wip/text.js @@ -0,0 +1,60 @@ + +var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create, update: update, render: render }, false); +// var game = new Phaser.Game(800, 600, Phaser.WEBGL, 'phaser-example', { preload: preload, create: create, update: update, render: render }, false); + +function preload() { + + game.load.image('pic', 'assets/pics/backscroll.png'); + +} + +var text; + +function create() { + + game.stage.backgroundColor = '#c48844'; + + text = game.add.text(game.world.centerX, game.world.centerY, "- phaser -\nwith a sprinkle of\npixi dust"); + + text.anchor.setTo(0.5); + + text.font = 'Art of Fighting 2'; + // text.font = 'Arial'; + text.fontSize = 40; + // text.fontWeight = 'bold italic'; + + // x0, y0 - x1, y1 + var grd = text.context.createLinearGradient(0, 0, 0, text.canvas.height); + + grd.addColorStop(0, '#8ED6FF'); + grd.addColorStop(1, '#004CB3'); + + // text.fill = '#ff0044'; + // text.lineSpacing = 16; + text.fill = grd; + text.align = 'center'; + text.stroke = '#ff00ff'; + text.strokeThickness = 2; + + text.setShadow(5, 5, 'rgba(0,0,0,0.5)', 5); + + game.input.onDown.add(change, this); + +} + +function change() { + + text.tint = Math.random() * 0xFFFFFF; + +} + +function update() { + + +} + +function render() { + + // game.debug.renderText(sprite.position.y, 32, 32); + +} diff --git a/src/core/World.js b/src/core/World.js index 943d1f99..e6ec490c 100644 --- a/src/core/World.js +++ b/src/core/World.js @@ -64,7 +64,7 @@ Phaser.World.prototype.boot = function () { /** * This is called automatically after the plugins preUpdate and before the State.update. -* Most objects have preUpdate methods and it's where initial movement, drawing and calculations are done. +* Most objects have preUpdate methods and it's where initial movement and positioning is done. * * @method Phaser.World#preUpdate */ diff --git a/src/gameobjects/Text.js b/src/gameobjects/Text.js index fbd1b6bd..41b7724f 100644 --- a/src/gameobjects/Text.js +++ b/src/gameobjects/Text.js @@ -32,17 +32,6 @@ Phaser.Text = function (game, x, y, text, style) { */ this.exists = true; - /** - * @property {boolean} alive - This is a handy little var your game can use to determine if an object is alive or not, it doesn't effect rendering. - * @default - */ - this.alive = true; - - /** - * @property {Phaser.Group} group - The parent Group of this Text object. - */ - this.group = null; - /** * @property {string} name - The user defined name given to this object. * @default @@ -56,39 +45,9 @@ Phaser.Text = function (game, x, y, text, style) { this.type = Phaser.TEXT; /** - * @property {string} _text - Internal value. - * @private + * @property {Phaser.Point} world - The world coordinates of this Sprite. This differs from the x/y coordinates which are relative to the Sprites container. */ - this._text = text; - - /** - * @property {string} _style - Internal value. - * @private - */ - this._style = style; - - PIXI.Text.call(this, text, style); - - /** - * @property {Phaser.Point} position - The position of this Text object in world space. - */ - this.position.x = this.x = x; - this.position.y = this.y = y; - - /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the textures origin is the top left - * Setting than anchor to 0.5,0.5 means the textures origin is centered - * Setting the anchor to 1,1 would mean the textures origin points will be the bottom right - * - * @property {Phaser.Point} anchor - The anchor around which rotation and scaling takes place. - */ - this.anchor = new Phaser.Point(); - - /** - * @property {Phaser.Point} scale - The scale of the object when rendered. By default it's set to 1 (no scale). You can modify it via scale.x or scale.y or scale.setTo(x, y). A value of 1 means no change to the scale, 0.5 means "half the size", 2 means "twice the size", etc. - */ - this.scale = new Phaser.Point(1, 1); + this.world = new Phaser.Point(x, y); /** * An object that is fixed to the camera ignores the position of any ancestors in the display list and uses its x/y coordinates as offsets from the top left of the camera. @@ -98,44 +57,38 @@ Phaser.Text = function (game, x, y, text, style) { this.fixedToCamera = false; /** - * @property {Phaser.Point} cameraOffset - If this object is fixed to the camera then use this Point to specify how far away from the Camera x/y it's rendered. - */ - this.cameraOffset = new Phaser.Point(x, y); - - /** - * @property {object} _cache - A mini cache for storing all of the calculated values. + * @property {string} _text - Internal cache var. * @private */ - this._cache = { - - dirty: false, - - // Transform cache - a00: 1, - a01: 0, - a02: x, - a10: 0, - a11: 1, - a12: y, - id: 1, - - // The previous calculated position - x: -1, - y: -1, - - // The actual scale values based on the worldTransform - scaleX: 1, - scaleY: 1 - - }; - - this._cache.x = this.x; - this._cache.y = this.y; + this._text = text; /** - * @property {boolean} renderable - A renderable object will be rendered to the context each frame. + * @property {string} _font - Internal cache var. + * @private */ - this.renderable = true; + this._font = ''; + + /** + * @property {number} _fontSize - Internal cache var. + * @private + */ + this._fontSize = 32; + + /** + * @property {string} _fontWeight - Internal cache var. + * @private + */ + this._fontWeight = 'normal'; + + /** + * @property {number} lineSpacing - Additional spacing (in pixels) between each line of text if multi-line. + * @private + */ + this._lineSpacing = 0; + + PIXI.Text.call(this, text, style); + + this.position.set(x, y); }; @@ -143,34 +96,35 @@ Phaser.Text.prototype = Object.create(PIXI.Text.prototype); Phaser.Text.prototype.constructor = Phaser.Text; /** -* Automatically called by World.update. -* @method Phaser.Text.prototype.update +* Automatically called by World.preUpdate. +* @method Phaser.Text.prototype.preUpdate */ -Phaser.Text.prototype.update = function() { +Phaser.Text.prototype.preUpdate = function() { - if (!this.exists) + if (!this.exists || !this.parent.exists) { - return; + // Reset the renderOrderID + return false; } - if (this.fixedToCamera) + this.world.setTo(this.game.camera.x + this.worldTransform.tx, this.game.camera.y + this.worldTransform.ty); + +} + +/** +* Automatically called by World.postUpdate. +* @method Phaser.Text.prototype.postUpdate +*/ +Phaser.Text.prototype.postUpdate = function() { + + if (this.exists) { - this.x = this.game.camera.view.x + this.cameraOffset.x; - this.y = this.game.camera.view.y + this.cameraOffset.y; + if (this.fixedToCamera) + { + // this.position.x = this.game.camera.view.x + this.x; + // this.position.y = this.game.camera.view.y + this.y; + } } - - this._cache.dirty = false; - - this._cache.x = this.x; - this._cache.y = this.y; - - if (this.position.x != this._cache.x || this.position.y != this._cache.y) - { - this.position.x = this._cache.x; - this.position.y = this._cache.y; - this._cache.dirty = true; - } - } /** @@ -178,11 +132,18 @@ Phaser.Text.prototype.update = function() { */ Phaser.Text.prototype.destroy = function() { - if (this.group) + if (this.filters) { - this.group.remove(this); + this.filters = null; } + if (this.parent) + { + this.parent.remove(this); + } + + this.texture.destroy(); + if (this.canvas.parentNode) { this.canvas.parentNode.removeChild(this.canvas); @@ -194,11 +155,186 @@ Phaser.Text.prototype.destroy = function() { } this.exists = false; + this.visible = false; - this.group = null; + this.game = null; } +/** +* @method Phaser.Text.prototype.setShadow +* @param {number} [x=0] - The shadowOffsetX value in pixels. This is how far offset horizontally the shadow effect will be. +* @param {number} [y=0] - The shadowOffsetY value in pixels. This is how far offset vertically the shadow effect will be. +* @param {string} [color='rgba(0,0,0,0)'] - The color of the shadow, as given in CSS rgba format. Set the alpha component to 0 to disable the shadow. +* @param {number} [blur=0] - The shadowBlur value. Make the shadow softer by applying a Gaussian blur to it. A number from 0 (no blur) up to approx. 10 (depending on scene). +*/ +Phaser.Text.prototype.setShadow = function(x, y, color, blur) { + + this.style.shadowOffsetX = x || 0; + this.style.shadowOffsetY = y || 0; + this.style.shadowColor = color || 'rgba(0,0,0,0)'; + this.style.shadowBlur = blur || 0; + this.dirty = true; + +} + +/** +* Set the style of the text by passing a single style object to it. +* +* @method Phaser.Text.prototype.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 +*/ +Phaser.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.shadowOffsetX = style.shadowOffsetX || 0; + style.shadowOffsetY = style.shadowOffsetY || 0; + style.shadowColor = style.shadowColor || 'rgba(0,0,0,0)'; + style.shadowBlur = style.shadowBlur || 0; + + this.style = style; + this.dirty = true; + +}; + +/** +* Renders text. This replaces the Pixi.Text.updateText function as we need a few extra bits in here. +* +* @method Phaser.Text.prototype.updateText +* @private +*/ +Phaser.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.runWordWrap(this.text); + + //split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + //calculate text width + var lineWidths = []; + var maxLineWidth = 0; + for (var i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width; + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + this.canvas.width = maxLineWidth + this.style.strokeThickness; + + //calculate text height + var lineHeight = this.determineFontHeight('font: ' + this.style.font + ';') + this.style.strokeThickness + this._lineSpacing + this.style.shadowOffsetY; + this.canvas.height = lineHeight * lines.length; + + if(navigator.isCocoonJS) this.context.clearRect(0,0,this.canvas.width,this.canvas.height); + + //set canvas text styles + this.context.fillStyle = this.style.fill; + this.context.font = this.style.font; + + this.context.strokeStyle = this.style.stroke; + this.context.lineWidth = this.style.strokeThickness; + + this.context.shadowOffsetX = this.style.shadowOffsetX; + this.context.shadowOffsetY = this.style.shadowOffsetY; + this.context.shadowColor = this.style.shadowColor; + this.context.shadowBlur = this.style.shadowBlur; + + this.context.textBaseline = 'top'; + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + var linePosition = new PIXI.Point(this.style.strokeThickness / 2, this.style.strokeThickness / 2 + i * lineHeight); + + if(this.style.align === 'right') + { + linePosition.x += maxLineWidth - lineWidths[i]; + } + else if(this.style.align === 'center') + { + linePosition.x += (maxLineWidth - lineWidths[i]) / 2; + } + + linePosition.y += this._lineSpacing; + + if(this.style.stroke && this.style.strokeThickness) + { + this.context.strokeText(lines[i], linePosition.x, linePosition.y); + } + + if(this.style.fill) + { + this.context.fillText(lines[i], linePosition.x, linePosition.y); + } + } + + this.updateTexture(); +}; + +/** +* Greedy wrapping algorithm that will wrap words as the line grows longer than its horizontal bounds. +* +* @method Phaser.Text.prototype.runWordWrap +* @private +*/ +Phaser.Text.prototype.runWordWrap = function(text) { + + var result = ''; + var lines = text.split('\n'); + + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = this.style.wordWrapWidth; + var words = lines[i].split(' '); + + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + + if (wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j] + ' '; + spaceLeft = this.style.wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += words[j] + ' '; + } + } + result += '\n'; + } + + return result; + +}; + /** * Indicates the rotation of the Text, in degrees, from its original orientation. Values from 0 to 180 represent clockwise rotation; values from 0 to -180 represent counterclockwise rotation. * Values outside this range are added to or subtracted from 360 to obtain a value within the range. For example, the statement player.angle = 450 is the same as player.angle = 90. @@ -219,45 +355,11 @@ Object.defineProperty(Phaser.Text.prototype, 'angle', { }); /** -* The x coordinate of this object in world space. -* @name Phaser.Text#x -* @property {number} x - The x coordinate of this object in world space. +* The text string to be displayed by this Text object, taking into account the style settings. +* @name Phaser.Text#text +* @property {string} text - The text string to be displayed by this Text object, taking into account the style settings. */ -Object.defineProperty(Phaser.Text.prototype, 'x', { - - get: function() { - return this.position.x; - }, - - set: function(value) { - this.position.x = value; - } - -}); - -/** -* The y coordinate of this object in world space. -* @name Phaser.Text#y -* @property {number} y - The y coordinate of this object in world space. -*/ -Object.defineProperty(Phaser.Text.prototype, 'y', { - - get: function() { - return this.position.y; - }, - - set: function(value) { - this.position.y = value; - } - -}); - -/** -* The string to be rendered by this Text object. -* @name Phaser.Text#content -* @property {string} content - The string to be rendered by this Text object. -*/ -Object.defineProperty(Phaser.Text.prototype, 'content', { +Object.defineProperty(Phaser.Text.prototype, 'text', { get: function() { return this._text; @@ -265,11 +367,10 @@ Object.defineProperty(Phaser.Text.prototype, 'content', { set: function(value) { - // Let's not update unless needed, this way we can safely update the text in a core loop without constant re-draws if (value !== this._text) { - this._text = value; - this.setText(value); + this._text = value.toString() || ' '; + this.dirty = true; } } @@ -277,23 +378,312 @@ Object.defineProperty(Phaser.Text.prototype, 'content', { }); /** -* The font the text will be rendered in. * @name Phaser.Text#font -* @property {string} font - The font the text will be rendered in. +* @property {string} font - The font the text will be rendered in, i.e. 'Arial'. Must be loaded in the browser before use. */ Object.defineProperty(Phaser.Text.prototype, 'font', { get: function() { - return this._style; + return this._font; }, set: function(value) { - // Let's not update unless needed, this way we can safely update the text in a core loop without constant re-draws - if (value !== this._style) + if (value !== this._font) { - this._style = value; - this.setStyle(value); + this._font = value.trim(); + this.style.font = this._fontWeight + ' ' + this._fontSize + "px '" + this._font + "'"; + this.dirty = true; + } + + } + +}); + +/** +* @name Phaser.Text#fontSize +* @property {number} fontSize - The size of the font in pixels. +*/ +Object.defineProperty(Phaser.Text.prototype, 'fontSize', { + + get: function() { + return this._fontSize; + }, + + set: function(value) { + + value = parseInt(value); + + if (value !== this._fontSize) + { + this._fontSize = value; + this.style.font = this._fontWeight + ' ' + this._fontSize + "px '" + this._font + "'"; + this.dirty = true; + } + + } + +}); + +/** +* @name Phaser.Text#fontWeight +* @property {number} fontWeight - The weight of the font: 'normal', 'bold', 'italic'. You can combine settings too, such as 'bold italic'. +*/ +Object.defineProperty(Phaser.Text.prototype, 'fontWeight', { + + get: function() { + return this._fontWeight; + }, + + set: function(value) { + + if (value !== this._fontWeight) + { + this._fontWeight = value; + this.style.font = this._fontWeight + ' ' + this._fontSize + "px '" + this._font + "'"; + this.dirty = true; + } + + } + +}); + +/** +* @name Phaser.Text#fill +* @property {object} fill - A canvas fillstyle that will be used on the text eg 'red', '#00FF00'. +*/ +Object.defineProperty(Phaser.Text.prototype, 'fill', { + + get: function() { + return this.style.fill; + }, + + set: function(value) { + + if (value !== this.style.fill) + { + this.style.fill = value; + this.dirty = true; + } + + } + +}); + +/** +* @name Phaser.Text#align +* @property {string} align - Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text. +*/ +Object.defineProperty(Phaser.Text.prototype, 'align', { + + get: function() { + return this.style.align; + }, + + set: function(value) { + + if (value !== this.style.align) + { + this.style.align = value; + this.dirty = true; + } + + } + +}); + +/** +* @name Phaser.Text#stroke +* @property {string} stroke - A canvas fillstyle that will be used on the text stroke eg 'blue', '#FCFF00'. +*/ +Object.defineProperty(Phaser.Text.prototype, 'stroke', { + + get: function() { + return this.style.stroke; + }, + + set: function(value) { + + if (value !== this.style.stroke) + { + this.style.stroke = value; + this.dirty = true; + } + + } + +}); + +/** +* @name Phaser.Text#strokeThickness +* @property {number} strokeThickness - A number that represents the thickness of the stroke. Default is 0 (no stroke) +*/ +Object.defineProperty(Phaser.Text.prototype, 'strokeThickness', { + + get: function() { + return this.style.strokeThickness; + }, + + set: function(value) { + + if (value !== this.style.strokeThickness) + { + this.style.strokeThickness = value; + this.dirty = true; + } + + } + +}); + +/** +* @name Phaser.Text#wordWrap +* @property {boolean} wordWrap - Indicates if word wrap should be used. +*/ +Object.defineProperty(Phaser.Text.prototype, 'wordWrap', { + + get: function() { + return this.style.wordWrap; + }, + + set: function(value) { + + if (value !== this.style.wordWrap) + { + this.style.wordWrap = value; + this.dirty = true; + } + + } + +}); + +/** +* @name Phaser.Text#wordWrapWidth +* @property {number} wordWrapWidth - The width at which text will wrap. +*/ +Object.defineProperty(Phaser.Text.prototype, 'wordWrapWidth', { + + get: function() { + return this.style.wordWrapWidth; + }, + + set: function(value) { + + if (value !== this.style.wordWrapWidth) + { + this.style.wordWrapWidth = value; + this.dirty = true; + } + + } + +}); + +/** +* @name Phaser.Text#lineSpacing +* @property {number} lineSpacing - Additional spacing (in pixels) between each line of text if multi-line. +*/ +Object.defineProperty(Phaser.Text.prototype, 'lineSpacing', { + + get: function() { + return this._lineSpacing; + }, + + set: function(value) { + + if (value !== this._lineSpacing) + { + this._lineSpacing = parseFloat(value); + this.dirty = true; + } + + } + +}); + +/** +* @name Phaser.Text#shadowOffsetX +* @property {number} shadowOffsetX - The shadowOffsetX value in pixels. This is how far offset horizontally the shadow effect will be. +*/ +Object.defineProperty(Phaser.Text.prototype, 'shadowOffsetX', { + + get: function() { + return this.style.shadowOffsetX; + }, + + set: function(value) { + + if (value !== this.style.shadowOffsetX) + { + this.style.shadowOffsetX = value; + this.dirty = true; + } + + } + +}); + +/** +* @name Phaser.Text#shadowOffsetY +* @property {number} shadowOffsetY - The shadowOffsetY value in pixels. This is how far offset vertically the shadow effect will be. +*/ +Object.defineProperty(Phaser.Text.prototype, 'shadowOffsetY', { + + get: function() { + return this.style.shadowOffsetY; + }, + + set: function(value) { + + if (value !== this.style.shadowOffsetY) + { + this.style.shadowOffsetY = value; + this.dirty = true; + } + + } + +}); + +/** +* @name Phaser.Text#shadowColor +* @property {string} shadowColor - The color of the shadow, as given in CSS rgba format. Set the alpha component to 0 to disable the shadow. +*/ +Object.defineProperty(Phaser.Text.prototype, 'shadowColor', { + + get: function() { + return this.style.shadowColor; + }, + + set: function(value) { + + if (value !== this.style.shadowColor) + { + this.style.shadowColor = value; + this.dirty = true; + } + + } + +}); + +/** +* @name Phaser.Text#shadowBlur +* @property {number} shadowBlur - The shadowBlur value. Make the shadow softer by applying a Gaussian blur to it. A number from 0 (no blur) up to approx. 10 (depending on scene). +*/ +Object.defineProperty(Phaser.Text.prototype, 'shadowBlur', { + + get: function() { + return this.style.shadowBlur; + }, + + set: function(value) { + + if (value !== this.style.shadowBlur) + { + this.style.shadowBlur = value; + this.dirty = true; } } diff --git a/src/pixi/Pixi.js b/src/pixi/Pixi.js index 79b8c715..768ef4e4 100644 --- a/src/pixi/Pixi.js +++ b/src/pixi/Pixi.js @@ -41,21 +41,6 @@ PIXI.scaleModes = { NEAREST:1 }; -// Canvas specific controls -PIXI.canvas = { - - // If the Stage is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - // Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - FILL_RECT: true, - - // 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. - CLEAR_RECT: true, - - // If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. Handy for crisp pixel art and speed on legacy devices. - PX_ROUND: false -} - // interaction frequency PIXI.INTERACTION_FREQUENCY = 30; PIXI.AUTO_PREVENT_DEFAULT = true; \ No newline at end of file diff --git a/src/pixi/display/Sprite.js b/src/pixi/display/Sprite.js index 1d065377..c95e1277 100644 --- a/src/pixi/display/Sprite.js +++ b/src/pixi/display/Sprite.js @@ -345,7 +345,7 @@ PIXI.Sprite.prototype._renderCanvas = function(renderSession) // allow for trimming - if (PIXI.canvas.PX_ROUND) + if (renderSession.roundPixels) { context.setTransform(transform.a, transform.c, transform.b, transform.d, Math.floor(transform.tx), Math.floor(transform.ty)); } diff --git a/src/pixi/display/SpriteBatch.js b/src/pixi/display/SpriteBatch.js index ed3b9211..d23013f2 100644 --- a/src/pixi/display/SpriteBatch.js +++ b/src/pixi/display/SpriteBatch.js @@ -91,7 +91,7 @@ PIXI.SpriteBatch.prototype._renderCanvas = function(renderSession) // alow for trimming - if (PIXI.canvas.PX_ROUND) + if (renderSession.roundPixels) { context.setTransform(transform.a, transform.c, transform.b, transform.d, Math.floor(transform.tx), Math.floor(transform.ty)); } diff --git a/src/pixi/renderers/canvas/CanvasRenderer.js b/src/pixi/renderers/canvas/CanvasRenderer.js index 9c93ee6f..d2e64d91 100644 --- a/src/pixi/renderers/canvas/CanvasRenderer.js +++ b/src/pixi/renderers/canvas/CanvasRenderer.js @@ -19,6 +19,27 @@ PIXI.CanvasRenderer = function(width, height, view, transparent) this.type = PIXI.CANVAS_RENDERER; + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the Stage is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the Stage is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @property clearBeforeRender + * @type Boolean + * @default + */ + this.clearBeforeRender = true; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @property roundPixels + * @type Boolean + * @default + */ + this.roundPixels = false; /** * Whether the render view is transparent @@ -165,12 +186,12 @@ PIXI.CanvasRenderer.prototype.render = function(stage) this.context.setTransform(1,0,0,1,0,0); this.context.globalAlpha = 1; - if (!this.transparent && PIXI.canvas.FILL_RECT) + if (!this.transparent && this.clearBeforeRender) { this.context.fillStyle = stage.backgroundColorString; this.context.fillRect(0, 0, this.width, this.height); } - else if (this.transparent && PIXI.canvas.CLEAR_RECT) + else if (this.transparent && this.clearBeforeRender) { this.context.clearRect(0, 0, this.width, this.height); } From 27bca6a8c2d74cff5e7da6573b0876566f95d648 Mon Sep 17 00:00:00 2001 From: clark-stevenson Date: Sun, 9 Feb 2014 16:15:10 +0000 Subject: [PATCH 07/99] Update phaser.d.ts atlasJSONArray, atlasJSONHash should have optional parameters when it comes to URL/Object? Please disregard this if this is incorrect. --- build/phaser.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/phaser.d.ts b/build/phaser.d.ts index ff10a516..0841c972 100644 --- a/build/phaser.d.ts +++ b/build/phaser.d.ts @@ -1645,9 +1645,9 @@ declare module Phaser { tilemap(key: string, tilesetURL: string, mapDataURL?: string, mapData?: Object, format?: string): void; tileset(key: string, url: string, tileWidth: number, tileHeight: number, tileMargin?: number, tileSpacing?: number, rows?: number, columns?: number, limit?: number): void; bitmapFont(key: string, textureURL: string, xmlURL?: string, xmlData?: Object): void; - atlasJSONArray(key: string, textureURL: string, atlasURL: string, atlasData: Object): void; - atlasJSONHash(key: string, textureURL: string, atlasURL: string, atlasData: Object): void; - atlasXML(key: string, textureURL: string, atlasURL: string, atlasData: Object): void; + atlasJSONArray(key: string, textureURL: string, atlasURL?: string, atlasData?: Object): void; + atlasJSONHash(key: string, textureURL: string, atlasURL?: string, atlasData?: Object): void; + atlasXML(key: string, textureURL: string, atlasURL?: string, atlasData?: Object): void; atlas(key: string, textureURL: string, atlasURL?: string, atlasData?: Object, format?: number): void; removeFile(key: string): void; removeAll(): void; From e9ab2d30b6059ba24324bfc12604e935c43ec265 Mon Sep 17 00:00:00 2001 From: clark-stevenson Date: Sun, 9 Feb 2014 17:00:07 +0000 Subject: [PATCH 08/99] Update phaser.d.ts Sprite was missing height property --- build/phaser.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/build/phaser.d.ts b/build/phaser.d.ts index 0841c972..fb4991ce 100644 --- a/build/phaser.d.ts +++ b/build/phaser.d.ts @@ -845,6 +845,7 @@ declare module Phaser { visible: boolean; renderable: boolean; width: number; + height: number; health: number; damage(amount: number): Phaser.Sprite; } From e15bebd269a424b9720c748f23fd121a417c16c5 Mon Sep 17 00:00:00 2001 From: photonstorm Date: Sun, 9 Feb 2014 22:48:35 +0000 Subject: [PATCH 09/99] Text.lineSpacing allows you to control the spacing between each line that is rendered. Text.inputEnabled allows you to enable all input events over Text objects: dragging, clicking, etc - anything that works on a Sprite works on Text now too. --- README.md | 1 + examples/wip/text.js | 43 ++++++++++++++++++++++++++---------- src/gameobjects/Sprite.js | 2 +- src/gameobjects/Text.js | 46 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index ce08fcb7..7fa1b195 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ New features: * Text now works happily with font names with spaces in them. * Text.setShadow applies a drop shadow to the Text being rendered. Control the x, y, color and blur. * Text.lineSpacing allows you to control the spacing between each line that is rendered. +* Text.inputEnabled allows you to enable all input events over Text objects: dragging, clicking, etc - anything that works on a Sprite works on Text now too. New Examples: diff --git a/examples/wip/text.js b/examples/wip/text.js index c5597b35..2047a7cf 100644 --- a/examples/wip/text.js +++ b/examples/wip/text.js @@ -12,7 +12,7 @@ var text; function create() { - game.stage.backgroundColor = '#c48844'; + game.stage.backgroundColor = '#2d2d2d'; text = game.add.text(game.world.centerX, game.world.centerY, "- phaser -\nwith a sprinkle of\npixi dust"); @@ -20,25 +20,44 @@ function create() { text.font = 'Art of Fighting 2'; // text.font = 'Arial'; - text.fontSize = 40; + text.fontSize = 30; // text.fontWeight = 'bold italic'; // x0, y0 - x1, y1 - var grd = text.context.createLinearGradient(0, 0, 0, text.canvas.height); + // var grd = text.context.createLinearGradient(0, 0, 0, text.canvas.height); + // grd.addColorStop(0, '#8ED6FF'); + // grd.addColorStop(1, '#004CB3'); + // text.fill = grd; - grd.addColorStop(0, '#8ED6FF'); - grd.addColorStop(1, '#004CB3'); - - // text.fill = '#ff0044'; - // text.lineSpacing = 16; - text.fill = grd; + text.fill = '#ff0044'; + text.lineSpacing = 16; text.align = 'center'; - text.stroke = '#ff00ff'; + text.stroke = '#000000'; text.strokeThickness = 2; - text.setShadow(5, 5, 'rgba(0,0,0,0.5)', 5); + // text.setShadow(5, 5, 'rgba(0,0,0,0.5)', 5); + // text.wordWrap = true; + // test.wordWrapWidth = 50; - game.input.onDown.add(change, this); + // game.input.onDown.add(change, this); + + text.inputEnabled = true; + text.input.enableDrag(); + + text.events.onInputOver.add(over, this); + text.events.onInputOut.add(out, this); + +} + +function out() { + + text.fill = '#ff0044'; + +} + +function over() { + + text.fill = '#ff00ff'; } diff --git a/src/gameobjects/Sprite.js b/src/gameobjects/Sprite.js index 9c35196d..e99e207a 100644 --- a/src/gameobjects/Sprite.js +++ b/src/gameobjects/Sprite.js @@ -784,7 +784,7 @@ Object.defineProperty(Phaser.Sprite.prototype, "renderOrderID", { }); /** -* By default an Image won't process any input events at all. By setting inputEnabled to true the Phaser.InputHandler is +* By default a Sprite won't process any input events at all. By setting inputEnabled to true the Phaser.InputHandler is * activated for this object and it will then start to process click/touch events and more. * * @name Phaser.Sprite#inputEnabled diff --git a/src/gameobjects/Text.js b/src/gameobjects/Text.js index 41b7724f..219137ba 100644 --- a/src/gameobjects/Text.js +++ b/src/gameobjects/Text.js @@ -86,6 +86,16 @@ Phaser.Text = function (game, x, y, text, style) { */ this._lineSpacing = 0; + /** + * @property {Phaser.Events} events - The Events you can subscribe to that are dispatched when certain things happen on this Sprite or its components. + */ + this.events = new Phaser.Events(this); + + /** + * @property {Phaser.InputHandler|null} input - The Input Handler for this object. Needs to be enabled with image.inputEnabled = true before you can use it. + */ + this.input = null; + PIXI.Text.call(this, text, style); this.position.set(x, y); @@ -689,3 +699,39 @@ Object.defineProperty(Phaser.Text.prototype, 'shadowBlur', { } }); + +/** +* By default a Text object won't process any input events at all. By setting inputEnabled to true the Phaser.InputHandler is +* activated for this object and it will then start to process click/touch events and more. +* +* @name Phaser.Text#inputEnabled +* @property {boolean} inputEnabled - Set to true to allow this object to receive input events. +*/ +Object.defineProperty(Phaser.Text.prototype, "inputEnabled", { + + get: function () { + + return (this.input && this.input.enabled); + + }, + + set: function (value) { + + if (value) + { + if (this.input === null) + { + this.input = new Phaser.InputHandler(this); + this.input.start(); + } + } + else + { + if (this.input && this.input.enabled) + { + this.input.stop(); + } + } + } + +}); From f9f2f2a9aec1f7559079926ae8dc838dce5b115c Mon Sep 17 00:00:00 2001 From: photonstorm Date: Mon, 10 Feb 2014 01:18:53 +0000 Subject: [PATCH 10/99] Converted the Pixi.Ellipse class. --- Gruntfile.js | 2 +- build/config.php | 9 +- examples/wip/text.js | 12 +- src/gameobjects/Text.js | 6 +- src/geom/Ellipse.js | 294 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 313 insertions(+), 10 deletions(-) create mode 100644 src/geom/Ellipse.js diff --git a/Gruntfile.js b/Gruntfile.js index f228334f..5911ddd2 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -151,7 +151,7 @@ module.exports = function (grunt) { uglify: { phaser: { options: { - banner: '/*! Phaser v<%= pkg.version %> | (c) 2013 Photon Storm Ltd. */\n' + banner: '/*! Phaser v<%= pkg.version %> | (c) 2014 Photon Storm Ltd. */\n' }, src: ['<%= umd.phaser.dest %>'], dest: '<%= compile_dir %>/phaser.min.js' diff --git a/build/config.php b/build/config.php index 5e21cefd..b6a75deb 100644 --- a/build/config.php +++ b/build/config.php @@ -36,6 +36,10 @@ + + + + @@ -58,16 +62,15 @@ + - - @@ -92,8 +95,6 @@ - - diff --git a/examples/wip/text.js b/examples/wip/text.js index 2047a7cf..c32c2d18 100644 --- a/examples/wip/text.js +++ b/examples/wip/text.js @@ -9,12 +9,14 @@ function preload() { } var text; +var b; function create() { game.stage.backgroundColor = '#2d2d2d'; - text = game.add.text(game.world.centerX, game.world.centerY, "- phaser -\nwith a sprinkle of\npixi dust"); + // text = game.add.text(game.world.centerX, game.world.centerY, "- phaser -\nwith a sprinkle of\npixi dust"); + text = game.add.text(game.world.centerX, game.world.centerY, "- phaser - with a sprinkle of pixi dust"); text.anchor.setTo(0.5); @@ -30,14 +32,14 @@ function create() { // text.fill = grd; text.fill = '#ff0044'; - text.lineSpacing = 16; + // text.lineSpacing = 16; text.align = 'center'; text.stroke = '#000000'; text.strokeThickness = 2; // text.setShadow(5, 5, 'rgba(0,0,0,0.5)', 5); - // text.wordWrap = true; - // test.wordWrapWidth = 50; + text.wordWrap = true; + text.wordWrapWidth = 50; // game.input.onDown.add(change, this); @@ -68,12 +70,14 @@ function change() { } function update() { + b = text.getBounds(); } function render() { + game.debug.renderRectangle(b); // game.debug.renderText(sprite.position.y, 32, 32); } diff --git a/src/gameobjects/Text.js b/src/gameobjects/Text.js index 219137ba..3f0d181d 100644 --- a/src/gameobjects/Text.js +++ b/src/gameobjects/Text.js @@ -338,7 +338,11 @@ Phaser.Text.prototype.runWordWrap = function(text) { result += words[j] + ' '; } } - result += '\n'; + + if (i < lines.length-1) + { + result += '\n'; + } } return result; diff --git a/src/geom/Ellipse.js b/src/geom/Ellipse.js new file mode 100644 index 00000000..f50b22d6 --- /dev/null +++ b/src/geom/Ellipse.js @@ -0,0 +1,294 @@ +/** +* @author Richard Davey +* @author Chad Engler +* @copyright 2014 Photon Storm Ltd. +* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} +*/ + +/** +* Creates a Ellipse object. A curve on a plane surrounding two focal points. +* @class Ellipse +* @classdesc Phaser - Ellipse +* @constructor +* @param {number} [x=0] - The X coordinate of the upper-left corner of the framing rectangle of this ellipse. +* @param {number} [y=0] - The Y coordinate of the upper-left corner of the framing rectangle of this ellipse. +* @param {number} [width=0] - The overall width of this ellipse. +* @param {number} [height=0] - The overall height of this ellipse. +* @return {Phaser.Ellipse} This Ellipse object +*/ +Phaser.Ellipse = function (x, y, width, height) { + + x = x || 0; + y = y || 0; + width = width || 0; + height = height || 0; + + /** + * @property {number} x - The X coordinate of the upper-left corner of the framing rectangle of this ellipse. + */ + this.x = x; + + /** + * @property {number} y - The Y coordinate of the upper-left corner of the framing rectangle of this ellipse. + */ + this.y = y; + + /** + * @property {number} width - The overall width of this ellipse. + */ + this.width = width; + + /** + * @property {number} height - The overall height of this ellipse. + */ + this.height = height; + +}; + +Phaser.Ellipse.prototype = { + + /** + * Sets the members of the Ellipse to the specified values. + * @method Phaser.Ellipse#setTo + * @param {number} x - The X coordinate of the upper-left corner of the framing rectangle of this ellipse. + * @param {number} y - The Y coordinate of the upper-left corner of the framing rectangle of this ellipse. + * @param {number} width - The overall width of this ellipse. + * @param {number} height - The overall height of this ellipse. + * @return {Phaser.Ellipse} This Ellipse object. + */ + setTo: function (x, y, width, height) { + + this.x = x; + this.y = y; + this.width = width; + this.height = height; + + return this; + + }, + + /** + * Copies the x, y, width and height properties from any given object to this Ellipse. + * @method Phaser.Ellipse#copyFrom + * @param {any} source - The object to copy from. + * @return {Phaser.Ellipse} This Ellipse object. + */ + copyFrom: function (source) { + + return this.setTo(source.x, source.y, source.width, source.height); + + }, + + /** + * Copies the x, y and diameter properties from this Circle to any given object. + * @method Phaser.Ellipse#copyTo + * @param {any} dest - The object to copy to. + * @return {Object} This dest object. + */ + copyTo: function(dest) { + + dest.x = this.x; + dest.y = this.y; + dest.width = this.width; + dest.height = this.height; + + return dest; + + }, + + /** + * Returns a new Ellipse object with the same values for the x, y, width, and height properties as this Ellipse object. + * @method Phaser.Ellipse#clone + * @param {Phaser.Ellipse} out - Optional Ellipse object. If given the values will be set into the object, otherwise a brand new Ellipse object will be created and returned. + * @return {Phaser.Ellipse} The cloned Ellipse object. + */ + clone: function(out) { + + if (typeof out === "undefined") + { + out = new Phaser.Ellipse(this.x, this.y, this.width, this.height); + } + else + { + out.setTo(this.x, this.y, this.width, this.height); + } + + return out; + + }, + + /** + * Return true if the given x/y coordinates are within this Ellipse object. + * @method Phaser.Ellipse#contains + * @param {number} x - The X value of the coordinate to test. + * @param {number} y - The Y value of the coordinate to test. + * @return {boolean} True if the coordinates are within this ellipse, otherwise false. + */ + contains: function (x, y) { + + return Phaser.Ellipse.contains(this, x, y); + + }, + + /** + * Returns a string representation of this object. + * @method Phaser.Ellipse#toString + * @return {string} A string representation of the instance. + */ + toString: function () { + return "[{Phaser.Ellipse (x=" + this.x + " y=" + this.y + " width=" + this.width + " height=" + this.height + ")}]"; + } + +}; + +Phaser.Ellipse.prototype.constructor = Phaser.Ellipse; + +/** +* The left coordinate of the Ellipse. The same as the X coordinate. +* @name Phaser.Ellipse#left +* @propety {number} left - Gets or sets the value of the leftmost point of the ellipse. +*/ +Object.defineProperty(Phaser.Ellipse.prototype, "left", { + + get: function () { + return this.x; + }, + + set: function (value) { + + this.x = value; + + } + +}); + +/** +* The x coordinate of the rightmost point of the Ellipse. Changing the right property of an Ellipse object has no effect on the x property, but does adjust the width. +* @name Phaser.Ellipse#right +* @property {number} right - Gets or sets the value of the rightmost point of the ellipse. +*/ +Object.defineProperty(Phaser.Ellipse.prototype, "right", { + + get: function () { + return this.x + this.width; + }, + + set: function (value) { + + if (value < this.x) + { + this.width = 0; + } + else + { + this.width = this.x + width; + } + } + +}); + +/** +* The top of the Ellipse. The same as its y property. +* @name Phaser.Ellipse#top +* @property {number} top - Gets or sets the top of the ellipse. +*/ +Object.defineProperty(Phaser.Ellipse.prototype, "top", { + + get: function () { + return this.y; + }, + + set: function (value) { + this.y = value; + } + +}); + +/** +* The sum of the y and height properties. Changing the bottom property of an Ellipse doesn't adjust the y property, but does change the height. +* @name Phaser.Ellipse#bottom +* @property {number} bottom - Gets or sets the bottom of the ellipse. +*/ +Object.defineProperty(Phaser.Ellipse.prototype, "bottom", { + + get: function () { + return this.y + this.height; + }, + + set: function (value) { + + if (value < this.y) + { + this.height = 0; + } + else + { + this.height = this.y + value; + } + } + +}); + +/** +* Determines whether or not this Ellipse object is empty. Will return a value of true if the Ellipse objects dimensions are less than or equal to 0; otherwise false. +* If set to true it will reset all of the Ellipse objects properties to 0. An Ellipse object is empty if its width or height is less than or equal to 0. +* @name Phaser.Ellipse#empty +* @property {boolean} empty - Gets or sets the empty state of the ellipse. +*/ +Object.defineProperty(Phaser.Ellipse.prototype, "empty", { + + get: function () { + return (this.width === 0 || this.height === 0); + }, + + set: function (value) { + + if (value === true) + { + this.setTo(0, 0, 0, 0); + } + + } + +}); + +/** +* Return true if the given x/y coordinates are within the Ellipse object. +* @method Phaser.Ellipse.contains +* @param {Phaser.Ellipse} a - The Ellipse to be checked. +* @param {number} x - The X value of the coordinate to test. +* @param {number} y - The Y value of the coordinate to test. +* @return {boolean} True if the coordinates are within this ellipse, otherwise false. +*/ +Phaser.Ellipse.contains = function (a, x, y) { + + if (a.width <= 0 || a.height <= 0) + { + return false; + } + + // Normalize the coords to an ellipse with center 0,0 and a radius of 0.5 + var normx = ((x - a.x) / a.width) - 0.5; + var normy = ((y - a.y) / a.height) - 0.5; + + normx *= normx; + normy *= normy; + + return (normx + normy < 0.25); + +}; + +/** +* Returns the framing rectangle of the ellipse as a Phaser.Rectangle object. +* +* @method getBounds +* @return {Phaser.Rectangle} The framing rectangle +*/ +Phaser.Ellipse.prototype.getBounds = function() { + + return new Phaser.Rectangle(this.x, this.y, this.width, this.height); + +}; + +// Because PIXI uses its own Ellipse, we'll replace it with ours to avoid duplicating code or confusion. +PIXI.Ellipse = Phaser.Ellipse; From d44775c095e8b3270031bac51e34ff11a95f8637 Mon Sep 17 00:00:00 2001 From: photonstorm Date: Mon, 10 Feb 2014 01:37:50 +0000 Subject: [PATCH 11/99] Phaser.Ellipse added. A fully compatible port of the PIXI.Ellipse class, can be used in Sprite/Image hitArea tests. Phaser.Polygon added. A fully compatible port of the PIXI.Polygon class, can be used in Sprite/Image hitArea tests. --- README.md | 2 + build/config.php | 2 +- src/Phaser.js | 3 +- src/geom/Circle.js | 84 ++++++++++++++++++++++++++++++++--------- src/geom/Ellipse.js | 2 + src/geom/Polygon.js | 92 ++++++++++++++++++++++++++++++++++++++++++--- 6 files changed, 159 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 7fa1b195..6284fe7a 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,8 @@ New features: * Text.setShadow applies a drop shadow to the Text being rendered. Control the x, y, color and blur. * Text.lineSpacing allows you to control the spacing between each line that is rendered. * Text.inputEnabled allows you to enable all input events over Text objects: dragging, clicking, etc - anything that works on a Sprite works on Text now too. +* Phaser.Ellipse added. A fully compatible port of the PIXI.Ellipse class, can be used in Sprite/Image hitArea tests. +* Phaser.Polygon added. A fully compatible port of the PIXI.Polygon class, can be used in Sprite/Image hitArea tests. New Examples: diff --git a/build/config.php b/build/config.php index b6a75deb..bd122201 100644 --- a/build/config.php +++ b/build/config.php @@ -63,9 +63,9 @@ + - diff --git a/src/Phaser.js b/src/Phaser.js index ad2bf892..1ce9b3a1 100644 --- a/src/Phaser.js +++ b/src/Phaser.js @@ -20,7 +20,7 @@ var Phaser = Phaser || { SPRITE: 0, BUTTON: 1, - BULLET: 2, + IMAGE: 2, GRAPHICS: 3, TEXT: 4, TILESPRITE: 5, @@ -34,6 +34,7 @@ var Phaser = Phaser || { BITMAPDATA: 13, CANVAS_FILTER: 14, WEBGL_FILTER: 15, + ELLIPSE: 16, NONE: 0, LEFT: 1, diff --git a/src/geom/Circle.js b/src/geom/Circle.js index 7e21cdd6..c755f013 100644 --- a/src/geom/Circle.js +++ b/src/geom/Circle.js @@ -71,11 +71,14 @@ Phaser.Circle.prototype = { * @return {Circle} This circle object. */ setTo: function (x, y, diameter) { + this.x = x; this.y = y; this._diameter = diameter; this._radius = diameter * 0.5; + return this; + }, /** @@ -85,7 +88,9 @@ Phaser.Circle.prototype = { * @return {Circle} This Circle object. */ copyFrom: function (source) { + return this.setTo(source.x, source.y, source.diameter); + }, /** @@ -94,11 +99,14 @@ Phaser.Circle.prototype = { * @param {any} dest - The object to copy to. * @return {Object} This dest object. */ - copyTo: function(dest) { + copyTo: function (dest) { + dest.x = this.x; dest.y = this.y; dest.diameter = this._diameter; + return dest; + }, /** @@ -130,7 +138,7 @@ Phaser.Circle.prototype = { * @param {Phaser.Circle} out - Optional Circle object. If given the values will be set into the object, otherwise a brand new Circle object will be created and returned. * @return {Phaser.Circle} The cloned Circle object. */ - clone: function(out) { + clone: function (out) { if (typeof out === "undefined") { @@ -153,7 +161,9 @@ Phaser.Circle.prototype = { * @return {boolean} True if the coordinates are within this circle, otherwise false. */ contains: function (x, y) { + return Phaser.Circle.contains(this, x, y); + }, /** @@ -165,7 +175,9 @@ Phaser.Circle.prototype = { * @return {Phaser.Point} The Point object holding the result. */ circumferencePoint: function (angle, asDegrees, out) { + return Phaser.Circle.circumferencePoint(this, angle, asDegrees, out); + }, /** @@ -176,9 +188,12 @@ Phaser.Circle.prototype = { * @return {Circle} This Circle object. */ offset: function (dx, dy) { + this.x += dx; this.y += dy; + return this; + }, /** @@ -216,7 +231,9 @@ Object.defineProperty(Phaser.Circle.prototype, "diameter", { }, set: function (value) { - if (value > 0) { + + if (value > 0) + { this._diameter = value; this._radius = value * 0.5; } @@ -236,10 +253,13 @@ Object.defineProperty(Phaser.Circle.prototype, "radius", { }, set: function (value) { - if (value > 0) { + + if (value > 0) + { this._radius = value; this._diameter = value * 2; } + } }); @@ -256,12 +276,17 @@ Object.defineProperty(Phaser.Circle.prototype, "left", { }, set: function (value) { - if (value > this.x) { + + if (value > this.x) + { this._radius = 0; this._diameter = 0; - } else { + } + else + { this.radius = this.x - value; } + } }); @@ -278,12 +303,17 @@ Object.defineProperty(Phaser.Circle.prototype, "right", { }, set: function (value) { - if (value < this.x) { + + if (value < this.x) + { this._radius = 0; this._diameter = 0; - } else { + } + else + { this.radius = value - this.x; } + } }); @@ -300,12 +330,17 @@ Object.defineProperty(Phaser.Circle.prototype, "top", { }, set: function (value) { - if (value > this.y) { + + if (value > this.y) + { this._radius = 0; this._diameter = 0; - } else { + } + else + { this.radius = this.y - value; } + } }); @@ -323,12 +358,16 @@ Object.defineProperty(Phaser.Circle.prototype, "bottom", { set: function (value) { - if (value < this.y) { + if (value < this.y) + { this._radius = 0; this._diameter = 0; - } else { + } + else + { this.radius = value - this.y; } + } }); @@ -342,11 +381,16 @@ Object.defineProperty(Phaser.Circle.prototype, "bottom", { Object.defineProperty(Phaser.Circle.prototype, "area", { get: function () { - if (this._radius > 0) { + + if (this._radius > 0) + { return Math.PI * this._radius * this._radius; - } else { + } + else + { return 0; } + } }); @@ -436,7 +480,8 @@ Phaser.Circle.circumferencePoint = function (a, angle, asDegrees, out) { if (typeof asDegrees === "undefined") { asDegrees = false; } if (typeof out === "undefined") { out = new Phaser.Point(); } - if (asDegrees === true) { + if (asDegrees === true) + { angle = Phaser.Math.radToDeg(angle); } @@ -459,18 +504,21 @@ Phaser.Circle.intersectsRectangle = function (c, r) { var cx = Math.abs(c.x - r.x - r.halfWidth); var xDist = r.halfWidth + c.radius; - if (cx > xDist) { + if (cx > xDist) + { return false; } var cy = Math.abs(c.y - r.y - r.halfHeight); var yDist = r.halfHeight + c.radius; - if (cy > yDist) { + if (cy > yDist) + { return false; } - if (cx <= r.halfWidth || cy <= r.halfHeight) { + if (cx <= r.halfWidth || cy <= r.halfHeight) + { return true; } diff --git a/src/geom/Ellipse.js b/src/geom/Ellipse.js index f50b22d6..7a5cced9 100644 --- a/src/geom/Ellipse.js +++ b/src/geom/Ellipse.js @@ -18,6 +18,8 @@ */ Phaser.Ellipse = function (x, y, width, height) { + this.type = Phaser.ELLIPSE; + x = x || 0; y = y || 0; width = width || 0; diff --git a/src/geom/Polygon.js b/src/geom/Polygon.js index c81ad370..8303a17e 100644 --- a/src/geom/Polygon.js +++ b/src/geom/Polygon.js @@ -1,5 +1,6 @@ /** * @author Richard Davey +* @author Adrien Brault * @copyright 2014 Photon Storm Ltd. * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} */ @@ -7,8 +8,8 @@ /** * Creates a new Polygon. You have to provide a list of points. * 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. +* or the arguments passed can be all the points of the polygon e.g. `new Phaser.Polygon(new Phaser.Point(), new Phaser.Point(), ...)`, or the +* arguments passed can be flat x,y values e.g. `new Phaser.Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are numbers. * * @class Phaser.Polygon * @classdesc The polygon represents a list of orderded points in space @@ -17,14 +18,93 @@ */ Phaser.Polygon = function (points) { - PIXI.Polygon.call(this, points); - /** * @property {number} type - The base object type. */ this.type = Phaser.POLYGON; + //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, len = points.length; i < len; i += 2) + { + p.push(new Phaser.Point(points[i], points[i + 1])); + } + + points = p; + } + + /** + * @property {array|array} points - The array of Points. + */ + this.points = points; + }; -Phaser.Polygon.prototype = Object.create(PIXI.Polygon.prototype); -Phaser.Polygon.prototype.constructor = Phaser.Polygon; \ No newline at end of file +Phaser.Polygon.prototype = { + + /** + * Creates a clone of this polygon. + * + * @method Phaser.Polygon#clone + * @return {Phaser.Polygon} A copy of the polygon. + */ + clone: function () { + + var points = []; + + for (var i=0; i < this.points.length; i++) + { + points.push(this.points[i].clone()); + } + + return new Phaser.Polygon(points); + + }, + + /** + * Checks whether the x and y coordinates are contained within this polygon. + * + * @method Phaser.Polygon#contains + * @param {number} x - The X value of the coordinate to test. + * @param {number} y - The Y value of the coordinate to test. + * @return {boolean} True if the coordinates are within this polygon, otherwise false. + */ + contains: function (x, y) { + + var inside = false; + + // use some raycasting to test hits https://github.com/substack/point-in-polygon/blob/master/index.js + for (var i = 0, j = this.points.length - 1; i < this.points.length; j = i++) + { + var xi = this.points[i].x; + var yi = this.points[i].y; + var xj = this.points[j].x; + var yj = this.points[j].y; + + var intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = true; + } + } + + return inside; + + } + +}; + +Phaser.Polygon.prototype.constructor = Phaser.Polygon; + +// Because PIXI uses its own Polygon, we'll replace it with ours to avoid duplicating code or confusion. +PIXI.Polygon = Phaser.Polygon; From 0294a4735df0fe19e2c5a19c8a52ca74860e3fa5 Mon Sep 17 00:00:00 2001 From: photonstorm Date: Mon, 10 Feb 2014 01:49:58 +0000 Subject: [PATCH 12/99] Fixes issues reported in #389 --- README.md | 4 +++- src/gameobjects/BitmapData.js | 4 ++-- src/tilemap/Tile.js | 12 ++++++------ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6284fe7a..29201852 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Version 1.2 - "Shienar" - -in development- Significant API changes: -* Upgraded to Pixi.js 1.4.4 +* Upgraded to Pixi.js 1.5 * Group now extends PIXI.DisplayObjectContainer, rather than owning a _container property, which makes life a whole lot easier re: nesting. * Removed Sprite.group property. You can use Sprite.parent for all similar needs now. * PIXI.Point is now aliased to Phaser.Point - saves on code duplication and works exactly the same. @@ -111,6 +111,8 @@ Bug Fixes: * Previously if you used Sprite.crop() it would crop all Sprites using the same base image. It now takes a local copy of the texture data and crops just that. * Tilemap had the wrong @method signatures so most were missing from the docs. * Fixed bug where changing State would cause the camera to not reset if it was following an object. +* Tile had 2 properties (callback and callbackContext) that were never assigned, updated to use the proper names (thanks ratkingsimon) + You can view the Change Log for all previous versions at https://github.com/photonstorm/phaser/changelog.md diff --git a/src/gameobjects/BitmapData.js b/src/gameobjects/BitmapData.js index 3c783ab1..38104592 100644 --- a/src/gameobjects/BitmapData.js +++ b/src/gameobjects/BitmapData.js @@ -1034,7 +1034,7 @@ Phaser.BitmapData.prototype.lf = Phaser.BitmapData.prototype.beginLinearGradient * Shortcut to beginRadialGradientFill. * @method Phaser.BitmapData.prototype.rf */ -Phaser.BitmapData.prototype.rf = Phaser.BitmapData.prototype.beginRadialGradientFill; +// Phaser.BitmapData.prototype.rf = Phaser.BitmapData.prototype.beginRadialGradientFill; /** * Shortcut to beginBitmapFill. @@ -1046,7 +1046,7 @@ Phaser.BitmapData.prototype.rf = Phaser.BitmapData.prototype.beginRadialGradient * Shortcut to endFill. * @method Phaser.BitmapData.prototype.ef */ -Phaser.BitmapData.prototype.ef = Phaser.BitmapData.prototype.endFill; +// Phaser.BitmapData.prototype.ef = Phaser.BitmapData.prototype.endFill; /** * Shortcut to setStrokeStyle. diff --git a/src/tilemap/Tile.js b/src/tilemap/Tile.js index f9dc1b10..e7cf8444 100644 --- a/src/tilemap/Tile.js +++ b/src/tilemap/Tile.js @@ -120,16 +120,16 @@ Phaser.Tile = function (layer, index, x, y, width, height) { this.collideDown = false; /** - * @property {function} callback - Tile collision callback. + * @property {function} collisionCallback - Tile collision callback. * @default */ - this.callback = null; + this.collisionCallback = null; /** - * @property {object} callbackContext - The context in which the collision callback will be called. + * @property {object} collisionCallbackContext - The context in which the collision callback will be called. * @default */ - this.callbackContext = this; + this.collisionCallbackContext = this; }; @@ -141,12 +141,12 @@ Phaser.Tile.prototype = { * * @method Phaser.Tile#setCollisionCallback * @param {function} callback - Callback function. - * @param {object} context - Callback will be called with this context. + * @param {object} context - Callback will be called within this context. */ setCollisionCallback: function (callback, context) { - this.collisionCallbackContext = context; this.collisionCallback = callback; + this.collisionCallbackContext = context; }, From ae74cb02dd1dbb4d2e09545a55ce6279cda8cee3 Mon Sep 17 00:00:00 2001 From: photonstorm Date: Mon, 10 Feb 2014 02:14:59 +0000 Subject: [PATCH 13/99] Fixes #382 Error when using InputHandler#onInputUp & sprite destroys itself during the event. --- README.md | 2 +- examples/wip/destroy.js | 16 ++++++++++---- src/gameobjects/Sprite.js | 10 ++++----- src/input/InputHandler.js | 44 ++++++++++++++++++++++++++++++++++++--- src/input/Pointer.js | 6 +++--- 5 files changed, 62 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 29201852..b35e939c 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ Bug Fixes: * Tilemap had the wrong @method signatures so most were missing from the docs. * Fixed bug where changing State would cause the camera to not reset if it was following an object. * Tile had 2 properties (callback and callbackContext) that were never assigned, updated to use the proper names (thanks ratkingsimon) - +* Issue 382: Error when using InputHandler#onInputUp & sprite destroys itself during the event. You can view the Change Log for all previous versions at https://github.com/photonstorm/phaser/changelog.md diff --git a/examples/wip/destroy.js b/examples/wip/destroy.js index 698e0a4b..63088547 100644 --- a/examples/wip/destroy.js +++ b/examples/wip/destroy.js @@ -14,14 +14,16 @@ var p; function create() { - game.stage.backgroundColor = '#ff5500'; + // game.stage.backgroundColor = '#ff5500'; - game.renderer.useFillRect = false; + // game.renderer.useFillRect = false; sprite = game.add.sprite(0.5, 0, 'pic'); - sprite2 = game.add.sprite(0, 300, 'pic'); + // sprite2 = game.add.sprite(0, 300, 'pic'); - game.input.onDown.add(tint, this); + sprite.inputEnabled = true; + sprite.events.onInputDown.add(tint, this); + sprite.events.onInputUp.add(wibble, this); // game.add.tween(sprite).to({y: 500}, 3000, Phaser.Easing.Linear.None, true); @@ -37,6 +39,12 @@ function tint() { } +function wibble() { + + console.log(sprite); + +} + function update() { diff --git a/src/gameobjects/Sprite.js b/src/gameobjects/Sprite.js index e99e207a..7739fbab 100644 --- a/src/gameobjects/Sprite.js +++ b/src/gameobjects/Sprite.js @@ -491,11 +491,6 @@ Phaser.Sprite.prototype.destroy = function() { this.parent.remove(this); } - if (this.events) - { - this.events.destroy(); - } - if (this.input) { this.input.destroy(); @@ -511,6 +506,11 @@ Phaser.Sprite.prototype.destroy = function() { this.body.destroy(); } + if (this.events) + { + this.events.destroy(); + } + this.alive = false; this.exists = false; this.visible = false; diff --git a/src/input/InputHandler.js b/src/input/InputHandler.js index c36b07e3..3957a14f 100644 --- a/src/input/InputHandler.js +++ b/src/input/InputHandler.js @@ -145,11 +145,15 @@ Phaser.InputHandler = function (sprite) { this.consumePointerEvent = false; /** - * @property {Phaser.Point} _tempPoint - Description. + * @property {Phaser.Point} _tempPoint - Internal cache var. * @private */ this._tempPoint = new Phaser.Point(); + /** + * @property {array} _pointerData - Internal cache var. + * @private + */ this._pointerData = []; this._pointerData.push({ @@ -290,10 +294,12 @@ Phaser.InputHandler.prototype = { this.game.input.interactiveItems.remove(this); - this.stop(); - + this._pointerData.length = 0; + this.boundsRect = null; + this.boundsSprite = null; this.sprite = null; } + }, /** @@ -549,10 +555,17 @@ Phaser.InputHandler.prototype = { /** * Update. * @method Phaser.InputHandler#update + * @protected * @param {Phaser.Pointer} pointer */ update: function (pointer) { + if (this.sprite === null) + { + // Abort. We've been destroyed. + return; + } + if (this.enabled === false || this.sprite.visible === false || (this.sprite.group && this.sprite.group.visible === false)) { this._pointerOutHandler(pointer); @@ -587,6 +600,12 @@ Phaser.InputHandler.prototype = { */ _pointerOverHandler: function (pointer) { + if (this.sprite === null) + { + // Abort. We've been destroyed. + return; + } + if (this._pointerData[pointer.id].isOver === false) { this._pointerData[pointer.id].isOver = true; @@ -602,6 +621,7 @@ Phaser.InputHandler.prototype = { this.sprite.events.onInputOver.dispatch(this.sprite, pointer); } + }, /** @@ -612,6 +632,12 @@ Phaser.InputHandler.prototype = { */ _pointerOutHandler: function (pointer) { + if (this.sprite === null) + { + // Abort. We've been destroyed. + return; + } + this._pointerData[pointer.id].isOver = false; this._pointerData[pointer.id].isOut = true; this._pointerData[pointer.id].timeOut = this.game.time.now; @@ -636,6 +662,12 @@ Phaser.InputHandler.prototype = { */ _touchedHandler: function (pointer) { + if (this.sprite === null) + { + // Abort. We've been destroyed. + return; + } + if (this._pointerData[pointer.id].isDown === false && this._pointerData[pointer.id].isOver === true) { this._pointerData[pointer.id].isDown = true; @@ -668,6 +700,12 @@ Phaser.InputHandler.prototype = { */ _releasedHandler: function (pointer) { + if (this.sprite === null) + { + // Abort. We've been destroyed. + return; + } + // If was previously touched by this Pointer, check if still is AND still over this item if (this._pointerData[pointer.id].isDown && pointer.isUp) { diff --git a/src/input/Pointer.js b/src/input/Pointer.js index 56fc31bb..da895c38 100644 --- a/src/input/Pointer.js +++ b/src/input/Pointer.js @@ -386,7 +386,7 @@ Phaser.Pointer.prototype = { while (currentNode != null) } - if (this._highestRenderObject == null) + if (this._highestRenderObject === null) { // The pointer isn't currently over anything, check if we've got a lingering previous target if (this.targetObject) @@ -398,7 +398,7 @@ Phaser.Pointer.prototype = { } else { - if (this.targetObject == null) + if (this.targetObject === null) { // And now set the new one // console.log('And now set the new one'); @@ -409,7 +409,7 @@ Phaser.Pointer.prototype = { { // We've got a target from the last update // console.log("We've got a target from the last update"); - if (this.targetObject == this._highestRenderObject) + if (this.targetObject === this._highestRenderObject) { // Same target as before, so update it // console.log("Same target as before, so update it"); From d2366d5fa5d2cbf90e3c9dbe17337a2f6ccf52c0 Mon Sep 17 00:00:00 2001 From: photonstorm Date: Mon, 10 Feb 2014 02:23:45 +0000 Subject: [PATCH 14/99] Fix for issue #376 - IE11 didn't populate the Device.ieVersion value. Now extracted from Trident revision, but still use Device.trident instead for IE11+ checks. --- README.md | 1 + src/system/Device.js | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b35e939c..cfb88c15 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ Bug Fixes: * Fixed bug where changing State would cause the camera to not reset if it was following an object. * Tile had 2 properties (callback and callbackContext) that were never assigned, updated to use the proper names (thanks ratkingsimon) * Issue 382: Error when using InputHandler#onInputUp & sprite destroys itself during the event. +* IE11 didn't populate the Device.ieVersion value. Now extracted from Trident revision, but still use Device.trident instead for IE11+ checks. You can view the Change Log for all previous versions at https://github.com/photonstorm/phaser/changelog.md diff --git a/src/system/Device.js b/src/system/Device.js index ce1452eb..97ff397b 100644 --- a/src/system/Device.js +++ b/src/system/Device.js @@ -189,7 +189,7 @@ Phaser.Device = function () { this.ie = false; /** - * @property {number} ieVersion - If running in Internet Explorer this will contain the major version number. + * @property {number} ieVersion - If running in Internet Explorer this will contain the major version number. Beyond IE10 you should use Device.trident and Device.tridentVersion. * @default */ this.ieVersion = 0; @@ -474,11 +474,12 @@ Phaser.Device.prototype = { { this.silk = true; } - else if (/Trident\/(\d+\.\d+);/.test(ua)) + else if (/Trident\/(\d+\.\d+); rv:(\d+\.\d+)/.test(ua)) { this.ie = true; this.trident = true; this.tridentVersion = parseInt(RegExp.$1, 10); + this.ieVersion = parseInt(RegExp.$2, 10); } // WebApp mode in iOS From d26bda2736757d4da7bfe461609757f02696f2d5 Mon Sep 17 00:00:00 2001 From: photonstorm Date: Mon, 10 Feb 2014 02:32:56 +0000 Subject: [PATCH 15/99] Small textual changes. --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index cfb88c15..fad0f2ac 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Version 1.2 - "Shienar" - -in development- Significant API changes: * Upgraded to Pixi.js 1.5 -* Group now extends PIXI.DisplayObjectContainer, rather than owning a _container property, which makes life a whole lot easier re: nesting. +* Group now extends PIXI.DisplayObjectContainer, rather than owning a _container property, which makes life a whole lot easier re: nesting and child iteration. * Removed Sprite.group property. You can use Sprite.parent for all similar needs now. * PIXI.Point is now aliased to Phaser.Point - saves on code duplication and works exactly the same. * PIXI.Rectangle is now aliased to Phaser.Rectangle - saves on code duplication and works exactly the same. @@ -70,21 +70,21 @@ Significant API changes: * Sprite.deltaX and deltaY swapped to functions: Sprite.deltaX() and Sprite.deltaY() * Sprite.crop() now takes a Phaser.Rectangle instead of explicit parameters. * PixiPatch no longer needed, all features that it patched are now native in Pixi :) -* Removed: Sprite.offset, center, topLeft, topRight, bottomRight, bottomLeft and bounds as no longer needed internally. Use Sprite.getBounds() to derive them. +* Removed: Sprite.offset, center, topLeft, topRight, bottomRight, bottomLeft and bounds, as no longer needed internally. Use Sprite.getBounds() to derive them. * Button now extends Phaser.Image not Phaser.Sprite, all the same functionality as before remains, just no animations or physics body. -* Text.content has been replaced with Text.text. +* Text.content has been replaced with Text.text. The Text class has a lot of new methods, check the docs! New features: -* Phaser.Image is a brand new display object perfect for logos, backgrounds, etc. You can scale, rotate, tint and blend and Image, but it has no animation, physics body or input events. +* Phaser.Image is a brand new display object perfect for logos, backgrounds, etc. You can scale, rotate, tint, blend an get input events from an Image, but it has no animation, physics body. * You can now use the hitArea property on Sprites and Image objects. hitArea can be a geometry object (Rectangle, Circle, Polygon, Ellipse) and is used in pointerOver checks. * InputManager.getLocalPosition(displayObject, pointer, output) will return the local coordinates of the specified displayObject and pointer. * InputManager.hitTest will test for pointer hits against a Sprite/Image, its hitArea (if set) or any of its children. * Text has lots of new methods to help style it: Text.fill, Text.align, Text.stroke, etc. * Text now works happily with font names with spaces in them. * Text.setShadow applies a drop shadow to the Text being rendered. Control the x, y, color and blur. -* Text.lineSpacing allows you to control the spacing between each line that is rendered. +* Text.lineSpacing allows you to set additional spacing between each line that is rendered. * Text.inputEnabled allows you to enable all input events over Text objects: dragging, clicking, etc - anything that works on a Sprite works on Text now too. * Phaser.Ellipse added. A fully compatible port of the PIXI.Ellipse class, can be used in Sprite/Image hitArea tests. * Phaser.Polygon added. A fully compatible port of the PIXI.Polygon class, can be used in Sprite/Image hitArea tests. @@ -108,11 +108,11 @@ Bug Fixes: * Explicitly paused Timer continues if you un-focus and focus the browser window (thanks georgiee) * Added TimerEvent.pendingDelete and checks in Timer.update, so that removing an event in a callback no longer throws an exception (thanks georgiee) * Fixed TypeScript defs on lines 1741-1748 (thanks wombatbuddy) -* Previously if you used Sprite.crop() it would crop all Sprites using the same base image. It now takes a local copy of the texture data and crops just that. +* Previously if you used Sprite.crop() it would crop all Sprites that shared the same base image. It now takes a local copy of the texture data and crops just that. * Tilemap had the wrong @method signatures so most were missing from the docs. * Fixed bug where changing State would cause the camera to not reset if it was following an object. * Tile had 2 properties (callback and callbackContext) that were never assigned, updated to use the proper names (thanks ratkingsimon) -* Issue 382: Error when using InputHandler#onInputUp & sprite destroys itself during the event. +* Fixed an error that would occur if you used InputHandler.onInputUp and the Sprite destroys itself during the event. * IE11 didn't populate the Device.ieVersion value. Now extracted from Trident revision, but still use Device.trident instead for IE11+ checks. From 6f835d76969c86c7136cd8b2a77b46b3d9df21aa Mon Sep 17 00:00:00 2001 From: photonstorm Date: Mon, 10 Feb 2014 02:42:28 +0000 Subject: [PATCH 16/99] Added Tileset to TypeScript defs. --- build/phaser.d.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/build/phaser.d.ts b/build/phaser.d.ts index fb4991ce..ef600a3b 100644 --- a/build/phaser.d.ts +++ b/build/phaser.d.ts @@ -2112,9 +2112,18 @@ declare module Phaser { toString(): string; } - class TilemapRenderer { - constructor(game: Phaser.Game); - game: Phaser.Game; - render(tilemap: Tilemap): void; + class Tileset { + constructor(name: string, firstgid: number, width: number, height: number, margin: number, spacing: number, properties: any); + name: string; + firstgid: number; + tileWidth: number; + tileHeight: number; + tileMargin: number; + tileSpacing: number; + properties: any; + image: any; + rows: number; + setSpacing(margin: number, spacing: number); } + } From 47e23096bd3edf22e7cd9c3b28f924dae2b4c219 Mon Sep 17 00:00:00 2001 From: photonstorm Date: Mon, 10 Feb 2014 16:01:30 +0000 Subject: [PATCH 17/99] Integrating p2.js. --- build/config.php | 10 +- build/p2.js | 10013 ++++++++++++++++++++++++++++++++++++ examples/wip/index.php | 1 + examples/wip/p21.js | 130 + examples/wip/p22.js | 94 + examples/wip/pixi1.js | 4 +- src/Phaser.js | 4 + src/core/Game.js | 7 +- src/gameobjects/Sprite.js | 55 +- src/math/Math.js | 22 + src/physics/Body.js | 710 +++ src/physics/World.js | 39 + 12 files changed, 11071 insertions(+), 18 deletions(-) create mode 100644 build/p2.js create mode 100644 examples/wip/p21.js create mode 100644 examples/wip/p22.js create mode 100644 src/physics/Body.js create mode 100644 src/physics/World.js diff --git a/build/config.php b/build/config.php index bd122201..b192f137 100644 --- a/build/config.php +++ b/build/config.php @@ -48,11 +48,16 @@ + + + + */ echo << @@ -173,9 +178,8 @@ - - - + + diff --git a/build/p2.js b/build/p2.js new file mode 100644 index 00000000..6abd4da4 --- /dev/null +++ b/build/p2.js @@ -0,0 +1,10013 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013 p2.js authors + * + * 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. + */ +!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.p2=e():"undefined"!=typeof global?self.p2=e():"undefined"!=typeof self&&(self.p2=e())}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0) { + //TODO: evaluate use of glm_invsqrt here? + len = 1 / Math.sqrt(len); + out[0] = a[0] * len; + out[1] = a[1] * len; + } + return out; +}; + +/** + * Caclulates the dot product of two vec2's + * + * @param {vec2} a the first operand + * @param {vec2} b the second operand + * @returns {Number} dot product of a and b + */ +vec2.dot = function (a, b) { + return a[0] * b[0] + a[1] * b[1]; +}; + +/** + * Computes the cross product of two vec2's + * Note that the cross product must by definition produce a 3D vector + * + * @param {vec3} out the receiving vector + * @param {vec2} a the first operand + * @param {vec2} b the second operand + * @returns {vec3} out + */ +vec2.cross = function(out, a, b) { + var z = a[0] * b[1] - a[1] * b[0]; + out[0] = out[1] = 0; + out[2] = z; + return out; +}; + +/** + * Performs a linear interpolation between two vec2's + * + * @param {vec3} out the receiving vector + * @param {vec2} a the first operand + * @param {vec2} b the second operand + * @param {Number} t interpolation amount between the two inputs + * @returns {vec2} out + */ +vec2.lerp = function (out, a, b, t) { + var ax = a[0], + ay = a[1]; + out[0] = ax + t * (b[0] - ax); + out[1] = ay + t * (b[1] - ay); + return out; +}; + +/** + * Transforms the vec2 with a mat2 + * + * @param {vec2} out the receiving vector + * @param {vec2} a the vector to transform + * @param {mat2} m matrix to transform with + * @returns {vec2} out + */ +vec2.transformMat2 = function(out, a, m) { + var x = a[0], + y = a[1]; + out[0] = x * m[0] + y * m[1]; + out[1] = x * m[2] + y * m[3]; + return out; +}; + +/** + * Perform some operation over an array of vec2s. + * + * @param {Array} a the array of vectors to iterate over + * @param {Number} stride Number of elements between the start of each vec2. If 0 assumes tightly packed + * @param {Number} offset Number of elements to skip at the beginning of the array + * @param {Number} count Number of vec2s to iterate over. If 0 iterates over entire array + * @param {Function} fn Function to call for each vector in the array + * @param {Object} [arg] additional argument to pass to fn + * @returns {Array} a + * @function + */ +vec2.forEach = (function() { + var vec = new Float32Array(2); + + return function(a, stride, offset, count, fn, arg) { + var i, l; + if(!stride) { + stride = 2; + } + + if(!offset) { + offset = 0; + } + + if(count) { + l = Math.min((count * stride) + offset, a.length); + } else { + l = a.length; + } + + for(i = offset; i < l; i += stride) { + vec[0] = a[i]; vec[1] = a[i+1]; + fn(vec, vec, arg); + a[i] = vec[0]; a[i+1] = vec[1]; + } + + return a; + }; +})(); + +/** + * Returns a string representation of a vector + * + * @param {vec2} vec vector to represent as a string + * @returns {String} string representation of the vector + */ +vec2.str = function (a) { + return 'vec2(' + a[0] + ', ' + a[1] + ')'; +}; + +if(typeof(exports) !== 'undefined') { + exports.vec2 = vec2; +} + +},{}],3:[function(require,module,exports){ +var Scalar = require('./Scalar'); + +module.exports = Line; + +/** + * Container for line-related functions + * @class Line + */ +function Line(){}; + +/** + * Compute the intersection between two lines. + * @static + * @method lineInt + * @param {Array} l1 Line vector 1 + * @param {Array} l2 Line vector 2 + * @param {Number} precision Precision to use when checking if the lines are parallel + * @return {Array} The intersection point. + */ +Line.lineInt = function(l1,l2,precision){ + precision = precision || 0; + var i = [0,0]; // point + var a1, b1, c1, a2, b2, c2, det; // scalars + a1 = l1[1][1] - l1[0][1]; + b1 = l1[0][0] - l1[1][0]; + c1 = a1 * l1[0][0] + b1 * l1[0][1]; + a2 = l2[1][1] - l2[0][1]; + b2 = l2[0][0] - l2[1][0]; + c2 = a2 * l2[0][0] + b2 * l2[0][1]; + det = a1 * b2 - a2*b1; + if (!Scalar.eq(det, 0, precision)) { // lines are not parallel + i[0] = (b2 * c1 - b1 * c2) / det; + i[1] = (a1 * c2 - a2 * c1) / det; + } + return i; +}; + +/** + * Checks if two line segments intersects. + * @method segmentsIntersect + * @param {Array} p1 The start vertex of the first line segment. + * @param {Array} p2 The end vertex of the first line segment. + * @param {Array} q1 The start vertex of the second line segment. + * @param {Array} q2 The end vertex of the second line segment. + * @return {Boolean} True if the two line segments intersect + */ +Line.segmentsIntersect = function(p1, p2, q1, q2){ + var dx = p2[0] - p1[0]; + var dy = p2[1] - p1[1]; + var da = q2[0] - q1[0]; + var db = q2[1] - q1[1]; + + // segments are parallel + if(da*dy - db*dx == 0) + return false; + + var s = (dx * (q1[1] - p1[1]) + dy * (p1[0] - q1[0])) / (da * dy - db * dx) + var t = (da * (p1[1] - q1[1]) + db * (q1[0] - p1[0])) / (db * dx - da * dy) + + return (s>=0 && s<=1 && t>=0 && t<=1); +}; + + +},{"./Scalar":6}],4:[function(require,module,exports){ +module.exports = Point; + +/** + * Point related functions + * @class Point + */ +function Point(){}; + +/** + * Get the area of a triangle spanned by the three given points. Note that the area will be negative if the points are not given in counter-clockwise order. + * @static + * @method area + * @param {Array} a + * @param {Array} b + * @param {Array} c + * @return {Number} + */ +Point.area = function(a,b,c){ + return (((b[0] - a[0])*(c[1] - a[1]))-((c[0] - a[0])*(b[1] - a[1]))); +}; + +Point.left = function(a,b,c){ + return Point.area(a,b,c) > 0; +}; + +Point.leftOn = function(a,b,c) { + return Point.area(a, b, c) >= 0; +}; + +Point.right = function(a,b,c) { + return Point.area(a, b, c) < 0; +}; + +Point.rightOn = function(a,b,c) { + return Point.area(a, b, c) <= 0; +}; + +var tmpPoint1 = [], + tmpPoint2 = []; + +/** + * Check if three points are collinear + * @method collinear + * @param {Array} a + * @param {Array} b + * @param {Array} c + * @param {Number} [thresholdAngle=0] Threshold angle to use when comparing the vectors. The function will return true if the angle between the resulting vectors is less than this value. Use zero for max precision. + * @return {Boolean} + */ +Point.collinear = function(a,b,c,thresholdAngle) { + if(!thresholdAngle) + return Point.area(a, b, c) == 0; + else { + var ab = tmpPoint1, + bc = tmpPoint2; + + ab[0] = b[0]-a[0]; + ab[1] = b[1]-a[1]; + bc[0] = c[0]-b[0]; + bc[1] = c[1]-b[1]; + + var dot = ab[0]*bc[0] + ab[1]*bc[1], + magA = Math.sqrt(ab[0]*ab[0] + ab[1]*ab[1]), + magB = Math.sqrt(bc[0]*bc[0] + bc[1]*bc[1]), + angle = Math.acos(dot/(magA*magB)); + return angle < thresholdAngle; + } +}; + +Point.sqdist = function(a,b){ + var dx = b[0] - a[0]; + var dy = b[1] - a[1]; + return dx * dx + dy * dy; +}; + +},{}],5:[function(require,module,exports){ +var Line = require("./Line") +, Point = require("./Point") +, Scalar = require("./Scalar") + +module.exports = Polygon; + +/** + * Polygon class. + * @class Polygon + * @constructor + */ +function Polygon(){ + + /** + * Vertices that this polygon consists of. An array of array of numbers, example: [[0,0],[1,0],..] + * @property vertices + * @type {Array} + */ + this.vertices = []; +} + +/** + * Get a vertex at position i. It does not matter if i is out of bounds, this function will just cycle. + * @method at + * @param {Number} i + * @return {Array} + */ +Polygon.prototype.at = function(i){ + var v = this.vertices, + s = v.length; + return v[i < 0 ? i % s + s : i % s]; +}; + +/** + * Get first vertex + * @method first + * @return {Array} + */ +Polygon.prototype.first = function(){ + return this.vertices[0]; +}; + +/** + * Get last vertex + * @method last + * @return {Array} + */ +Polygon.prototype.last = function(){ + return this.vertices[this.vertices.length-1]; +}; + +/** + * Clear the polygon data + * @method clear + * @return {Array} + */ +Polygon.prototype.clear = function(){ + this.vertices.length = 0; +}; + +/** + * Append points "from" to "to"-1 from an other polygon "poly" onto this one. + * @method append + * @param {Polygon} poly The polygon to get points from. + * @param {Number} from The vertex index in "poly". + * @param {Number} to The end vertex index in "poly". Note that this vertex is NOT included when appending. + * @return {Array} + */ +Polygon.prototype.append = function(poly,from,to){ + if(typeof(from) == "undefined") throw new Error("From is not given!"); + if(typeof(to) == "undefined") throw new Error("To is not given!"); + + if(to-1 < from) throw new Error("lol1"); + if(to > poly.vertices.length) throw new Error("lol2"); + if(from < 0) throw new Error("lol3"); + + for(var i=from; i v[br][0])) { + br = i; + } + } + + // reverse poly if clockwise + if (!Point.left(this.at(br - 1), this.at(br), this.at(br + 1))) { + this.reverse(); + } +}; + +/** + * Reverse the vertices in the polygon + * @method reverse + */ +Polygon.prototype.reverse = function(){ + var tmp = []; + for(var i=0, N=this.vertices.length; i!==N; i++){ + tmp.push(this.vertices.pop()); + } + this.vertices = tmp; +}; + +/** + * Check if a point in the polygon is a reflex point + * @method isReflex + * @param {Number} i + * @return {Boolean} + */ +Polygon.prototype.isReflex = function(i){ + return Point.right(this.at(i - 1), this.at(i), this.at(i + 1)); +}; + +var tmpLine1=[], + tmpLine2=[]; + +/** + * Check if two vertices in the polygon can see each other + * @method canSee + * @param {Number} a Vertex index 1 + * @param {Number} b Vertex index 2 + * @return {Boolean} + */ +Polygon.prototype.canSee = function(a,b) { + var p, dist, l1=tmpLine1, l2=tmpLine2; + + if (Point.leftOn(this.at(a + 1), this.at(a), this.at(b)) && Point.rightOn(this.at(a - 1), this.at(a), this.at(b))) { + return false; + } + dist = Point.sqdist(this.at(a), this.at(b)); + for (var i = 0; i !== this.vertices.length; ++i) { // for each edge + if ((i + 1) % this.vertices.length === a || i === a) // ignore incident edges + continue; + if (Point.leftOn(this.at(a), this.at(b), this.at(i + 1)) && Point.rightOn(this.at(a), this.at(b), this.at(i))) { // if diag intersects an edge + l1[0] = this.at(a); + l1[1] = this.at(b); + l2[0] = this.at(i); + l2[1] = this.at(i + 1); + p = Line.lineInt(l1,l2); + if (Point.sqdist(this.at(a), p) < dist) { // if edge is blocking visibility to b + return false; + } + } + } + + return true; +}; + +/** + * Copy the polygon from vertex i to vertex j. + * @method copy + * @param {Number} i + * @param {Number} j + * @param {Polygon} [targetPoly] Optional target polygon to save in. + * @return {Polygon} The resulting copy. + */ +Polygon.prototype.copy = function(i,j,targetPoly){ + var p = targetPoly || new Polygon(); + p.clear(); + if (i < j) { + // Insert all vertices from i to j + for(var k=i; k<=j; k++) + p.vertices.push(this.vertices[k]); + + } else { + + // Insert vertices 0 to j + for(var k=0; k<=j; k++) + p.vertices.push(this.vertices[k]); + + // Insert vertices i to end + for(var k=i; k 0) + return this.slice(edges); + else + return [this]; +}; + +/** + * Slices the polygon given one or more cut edges. If given one, this function will return two polygons (false on failure). If many, an array of polygons. + * @method slice + * @param {Array} cutEdges A list of edges, as returned by .getCutEdges() + * @return {Array} + */ +Polygon.prototype.slice = function(cutEdges){ + if(cutEdges.length == 0) return [this]; + if(cutEdges instanceof Array && cutEdges.length && cutEdges[0] instanceof Array && cutEdges[0].length==2 && cutEdges[0][0] instanceof Array){ + + var polys = [this]; + + for(var i=0; i maxlevel){ + console.warn("quickDecomp: max level ("+maxlevel+") reached."); + return result; + } + + for (var i = 0; i < this.vertices.length; ++i) { + if (poly.isReflex(i)) { + reflexVertices.push(poly.vertices[i]); + upperDist = lowerDist = Number.MAX_VALUE; + + + for (var j = 0; j < this.vertices.length; ++j) { + if (Point.left(poly.at(i - 1), poly.at(i), poly.at(j)) + && Point.rightOn(poly.at(i - 1), poly.at(i), poly.at(j - 1))) { // if line intersects with an edge + p = getIntersectionPoint(poly.at(i - 1), poly.at(i), poly.at(j), poly.at(j - 1)); // find the point of intersection + if (Point.right(poly.at(i + 1), poly.at(i), p)) { // make sure it's inside the poly + d = Point.sqdist(poly.vertices[i], p); + if (d < lowerDist) { // keep only the closest intersection + lowerDist = d; + lowerInt = p; + lowerIndex = j; + } + } + } + if (Point.left(poly.at(i + 1), poly.at(i), poly.at(j + 1)) + && Point.rightOn(poly.at(i + 1), poly.at(i), poly.at(j))) { + p = getIntersectionPoint(poly.at(i + 1), poly.at(i), poly.at(j), poly.at(j + 1)); + if (Point.left(poly.at(i - 1), poly.at(i), p)) { + d = Point.sqdist(poly.vertices[i], p); + if (d < upperDist) { + upperDist = d; + upperInt = p; + upperIndex = j; + } + } + } + } + + // if there are no vertices to connect to, choose a point in the middle + if (lowerIndex == (upperIndex + 1) % this.vertices.length) { + //console.log("Case 1: Vertex("+i+"), lowerIndex("+lowerIndex+"), upperIndex("+upperIndex+"), poly.size("+this.vertices.length+")"); + p[0] = (lowerInt[0] + upperInt[0]) / 2; + p[1] = (lowerInt[1] + upperInt[1]) / 2; + steinerPoints.push(p); + + if (i < upperIndex) { + //lowerPoly.insert(lowerPoly.end(), poly.begin() + i, poly.begin() + upperIndex + 1); + lowerPoly.append(poly, i, upperIndex+1); + lowerPoly.vertices.push(p); + upperPoly.vertices.push(p); + if (lowerIndex != 0){ + //upperPoly.insert(upperPoly.end(), poly.begin() + lowerIndex, poly.end()); + upperPoly.append(poly,lowerIndex,poly.vertices.length); + } + //upperPoly.insert(upperPoly.end(), poly.begin(), poly.begin() + i + 1); + upperPoly.append(poly,0,i+1); + } else { + if (i != 0){ + //lowerPoly.insert(lowerPoly.end(), poly.begin() + i, poly.end()); + lowerPoly.append(poly,i,poly.vertices.length); + } + //lowerPoly.insert(lowerPoly.end(), poly.begin(), poly.begin() + upperIndex + 1); + lowerPoly.append(poly,0,upperIndex+1); + lowerPoly.vertices.push(p); + upperPoly.vertices.push(p); + //upperPoly.insert(upperPoly.end(), poly.begin() + lowerIndex, poly.begin() + i + 1); + upperPoly.append(poly,lowerIndex,i+1); + } + } else { + // connect to the closest point within the triangle + //console.log("Case 2: Vertex("+i+"), closestIndex("+closestIndex+"), poly.size("+this.vertices.length+")\n"); + + if (lowerIndex > upperIndex) { + upperIndex += this.vertices.length; + } + closestDist = Number.MAX_VALUE; + + if(upperIndex < lowerIndex){ + return result; + } + + for (var j = lowerIndex; j <= upperIndex; ++j) { + if (Point.leftOn(poly.at(i - 1), poly.at(i), poly.at(j)) + && Point.rightOn(poly.at(i + 1), poly.at(i), poly.at(j))) { + d = Point.sqdist(poly.at(i), poly.at(j)); + if (d < closestDist) { + closestDist = d; + closestIndex = j % this.vertices.length; + } + } + } + + if (i < closestIndex) { + lowerPoly.append(poly,i,closestIndex+1); + if (closestIndex != 0){ + upperPoly.append(poly,closestIndex,v.length); + } + upperPoly.append(poly,0,i+1); + } else { + if (i != 0){ + lowerPoly.append(poly,i,v.length); + } + lowerPoly.append(poly,0,closestIndex+1); + upperPoly.append(poly,closestIndex,i+1); + } + } + + // solve smallest poly first + if (lowerPoly.vertices.length < upperPoly.vertices.length) { + lowerPoly.quickDecomp(result,reflexVertices,steinerPoints,delta,maxlevel,level); + upperPoly.quickDecomp(result,reflexVertices,steinerPoints,delta,maxlevel,level); + } else { + upperPoly.quickDecomp(result,reflexVertices,steinerPoints,delta,maxlevel,level); + lowerPoly.quickDecomp(result,reflexVertices,steinerPoints,delta,maxlevel,level); + } + + return result; + } + } + result.push(this); + + return result; +}; + +/** + * Remove collinear points in the polygon. + * @method removeCollinearPoints + * @param {Number} [precision] The threshold angle to use when determining whether two edges are collinear. Use zero for finest precision. + * @return {Number} The number of points removed + */ +Polygon.prototype.removeCollinearPoints = function(precision){ + var num = 0; + for(var i=this.vertices.length-1; this.vertices.length>3 && i>=0; --i){ + if(Point.collinear(this.at(i-1),this.at(i),this.at(i+1),precision)){ + // Remove the middle point + this.vertices.splice(i%this.vertices.length,1); + i--; // Jump one point forward. Otherwise we may get a chain removal + num++; + } + } + return num; +}; + +},{"./Line":3,"./Point":4,"./Scalar":6}],6:[function(require,module,exports){ +module.exports = Scalar; + +/** + * Scalar functions + * @class Scalar + */ +function Scalar(){} + +/** + * Check if two scalars are equal + * @static + * @method eq + * @param {Number} a + * @param {Number} b + * @param {Number} [precision] + * @return {Boolean} + */ +Scalar.eq = function(a,b,precision){ + precision = precision || 0; + return Math.abs(a-b) < precision; +}; + +},{}],7:[function(require,module,exports){ +module.exports = { + Polygon : require("./Polygon"), + Point : require("./Point"), +}; + +},{"./Point":4,"./Polygon":5}],8:[function(require,module,exports){ +module.exports={ + "name": "p2", + "version": "0.4.0", + "description": "A JavaScript 2D physics engine.", + "author": "Stefan Hedman (http://steffe.se)", + "keywords": [ + "p2.js", + "p2", + "physics", + "engine", + "2d" + ], + "main": "./src/p2.js", + "engines": { + "node": "*" + }, + "repository": { + "type": "git", + "url": "https://github.com/schteppe/p2.js.git" + }, + "bugs": { + "url": "https://github.com/schteppe/p2.js/issues" + }, + "licenses" : [ + { + "type" : "MIT" + } + ], + "devDependencies" : { + "jshint" : "latest", + "nodeunit" : "latest", + "grunt": "~0.4.0", + "grunt-contrib-jshint": "~0.1.1", + "grunt-contrib-nodeunit": "~0.1.2", + "grunt-contrib-concat": "~0.1.3", + "grunt-contrib-uglify": "*", + "grunt-browserify" : "*", + "browserify":"*" + }, + "dependencies" : { + "underscore":"*", + "poly-decomp" : "git://github.com/schteppe/poly-decomp.js", + "gl-matrix":"2.0.0", + "jsonschema":"*" + } +} + +},{}],9:[function(require,module,exports){ +var vec2 = require('../math/vec2') +, Utils = require('../utils/Utils') + +module.exports = AABB; + +/** + * Axis aligned bounding box class. + * @class AABB + * @constructor + * @param {Object} options + * @param {Array} upperBound + * @param {Array} lowerBound + */ +function AABB(options){ + + /** + * The lower bound of the bounding box. + * @property lowerBound + * @type {Array} + */ + this.lowerBound = vec2.create(); + if(options && options.lowerBound) vec2.copy(this.lowerBound, options.lowerBound); + + /** + * The upper bound of the bounding box. + * @property upperBound + * @type {Array} + */ + this.upperBound = vec2.create(); + if(options && options.upperBound) vec2.copy(this.upperBound, options.upperBound); +} + +var tmp = vec2.create(); + +/** + * Set the AABB bounds from a set of points. + * @method setFromPoints + * @param {Array} points An array of vec2's. + */ +AABB.prototype.setFromPoints = function(points,position,angle){ + var l = this.lowerBound, + u = this.upperBound; + vec2.set(l, Number.MAX_VALUE, Number.MAX_VALUE); + vec2.set(u, -Number.MAX_VALUE, -Number.MAX_VALUE); + for(var i=0; i u[j]){ + u[j] = p[j]; + } + if(p[j] < l[j]){ + l[j] = p[j]; + } + } + } + + // Add offset + if(position){ + vec2.add(this.lowerBound, this.lowerBound, position); + vec2.add(this.upperBound, this.upperBound, position); + } +}; + +/** + * Copy bounds from an AABB to this AABB + * @method copy + * @param {AABB} aabb + */ +AABB.prototype.copy = function(aabb){ + vec2.copy(this.lowerBound, aabb.lowerBound); + vec2.copy(this.upperBound, aabb.upperBound); +}; + +/** + * Extend this AABB so that it covers the given AABB too. + * @method extend + * @param {AABB} aabb + */ +AABB.prototype.extend = function(aabb){ + // Loop over x and y + for(var i=0; i<2; i++){ + // Extend lower bound + if(aabb.lowerBound[i] < this.lowerBound[i]) + this.lowerBound[i] = aabb.lowerBound[i]; + + // Upper + if(aabb.upperBound[i] > this.upperBound[i]) + this.upperBound[i] = aabb.upperBound[i]; + } +}; + +/** + * Returns true if the given AABB overlaps this AABB. + * @param {AABB} aabb + * @return {Boolean} + */ +AABB.prototype.overlaps = function(aabb){ + var l1 = this.lowerBound, + u1 = this.upperBound, + l2 = aabb.lowerBound, + u2 = aabb.upperBound; + + // l2 u2 + // |---------| + // |--------| + // l1 u1 + + return ((l2[0] <= u1[0] && u1[0] <= u2[0]) || (l1[0] <= u2[0] && u2[0] <= u1[0])) && + ((l2[1] <= u1[1] && u1[1] <= u2[1]) || (l1[1] <= u2[1] && u2[1] <= u1[1])); +}; + +},{"../math/vec2":33,"../utils/Utils":49}],10:[function(require,module,exports){ +var vec2 = require('../math/vec2') +var Body = require('../objects/Body') + +module.exports = Broadphase; + +/** + * Base class for broadphase implementations. + * @class Broadphase + * @constructor + */ +function Broadphase(){ + + /** + * The resulting overlapping pairs. Will be filled with results during .getCollisionPairs(). + * @property result + * @type {Array} + */ + this.result = []; + + /** + * The world to search for collision pairs in. To change it, use .setWorld() + * @property world + * @type {World} + */ + this.world = null; +}; + +/** + * Set the world that we are searching for collision pairs in + * @method setWorld + * @param {World} world + */ +Broadphase.prototype.setWorld = function(world){ + this.world = world; +}; + +/** + * Get all potential intersecting body pairs. + * @method getCollisionPairs + * @param {World} world The world to search in. + * @return {Array} An array of the bodies, ordered in pairs. Example: A result of [a,b,c,d] means that the potential pairs are: (a,b), (c,d). + */ +Broadphase.prototype.getCollisionPairs = function(world){ + throw new Error("getCollisionPairs must be implemented in a subclass!"); +}; + +var dist = vec2.create(); + +/** + * Check whether the bounding radius of two bodies overlap. + * @method boundingRadiusCheck + * @param {Body} bodyA + * @param {Body} bodyB + * @return {Boolean} + */ +Broadphase.boundingRadiusCheck = function(bodyA, bodyB){ + vec2.sub(dist, bodyA.position, bodyB.position); + var d2 = vec2.squaredLength(dist), + r = bodyA.boundingRadius + bodyB.boundingRadius; + return d2 <= r*r; +}; + +/** + * Check whether the bounding radius of two bodies overlap. + * @method boundingRadiusCheck + * @param {Body} bodyA + * @param {Body} bodyB + * @return {Boolean} + */ +Broadphase.aabbCheck = function(bodyA, bodyB){ + if(bodyA.aabbNeedsUpdate) bodyA.updateAABB(); + if(bodyB.aabbNeedsUpdate) bodyB.updateAABB(); + return bodyA.aabb.overlaps(bodyB.aabb); +}; + +/** + * Check whether two bodies are allowed to collide at all. + * @method canCollide + * @param {Body} bodyA + * @param {Body} bodyB + * @return {Boolean} + */ +Broadphase.canCollide = function(bodyA, bodyB){ + // Cannot collide static bodies + if(bodyA.motionState & Body.STATIC && bodyB.motionState & Body.STATIC) + return false; + + // Cannot collide sleeping bodies + if(bodyA.sleepState & Body.SLEEPING && bodyB.sleepState & Body.SLEEPING) + return false; + + return true; +}; + +},{"../math/vec2":33,"../objects/Body":34}],11:[function(require,module,exports){ +var Circle = require('../shapes/Circle') +, Plane = require('../shapes/Plane') +, Particle = require('../shapes/Particle') +, Broadphase = require('../collision/Broadphase') +, vec2 = require('../math/vec2') + +module.exports = GridBroadphase; + +/** + * Broadphase that uses axis-aligned bins. + * @class GridBroadphase + * @constructor + * @extends Broadphase + * @param {number} xmin Lower x bound of the grid + * @param {number} xmax Upper x bound + * @param {number} ymin Lower y bound + * @param {number} ymax Upper y bound + * @param {number} nx Number of bins along x axis + * @param {number} ny Number of bins along y axis + * @todo test + */ +function GridBroadphase(xmin,xmax,ymin,ymax,nx,ny){ + Broadphase.apply(this); + + nx = nx || 10; + ny = ny || 10; + + this.binsizeX = (xmax-xmin) / nx; + this.binsizeY = (ymax-ymin) / ny; + this.nx = nx; + this.ny = ny; + this.xmin = xmin; + this.ymin = ymin; + this.xmax = xmax; + this.ymax = ymax; +}; +GridBroadphase.prototype = new Broadphase(); + +/** + * Get a bin index given a world coordinate + * @method getBinIndex + * @param {Number} x + * @param {Number} y + * @return {Number} Integer index + */ +GridBroadphase.prototype.getBinIndex = function(x,y){ + var nx = this.nx, + ny = this.ny, + xmin = this.xmin, + ymin = this.ymin, + xmax = this.xmax, + ymax = this.ymax; + + var xi = Math.floor(nx * (x - xmin) / (xmax-xmin)); + var yi = Math.floor(ny * (y - ymin) / (ymax-ymin)); + return xi*ny + yi; +} + +/** + * Get collision pairs. + * @method getCollisionPairs + * @param {World} world + * @return {Array} + */ +GridBroadphase.prototype.getCollisionPairs = function(world){ + var result = [], + collidingBodies = world.bodies, + Ncolliding = Ncolliding=collidingBodies.length, + binsizeX = this.binsizeX, + binsizeY = this.binsizeY; + + var bins=[], Nbins=nx*ny; + for(var i=0; i= 0 && xi*(ny-1) + yi < Nbins) + bins[ xi*(ny-1) + yi ].push(bi); + } + } + } else if(si instanceof Plane){ + // Put in all bins for now + if(bi.angle == 0){ + var y = bi.position[1]; + for(var j=0; j!==Nbins && ymin+binsizeY*(j-1) id2){ + var tmp = id1; + id1 = id2; + id2 = tmp; + } + return !!this.collidingBodiesLastStep[id1 + " " + id2]; +}; + +// "for in" loops aren't optimised in chrome... is there a better way to handle last-step collision memory? +// Maybe do this: http://jsperf.com/reflection-vs-array-of-keys +function clearObject(obj){ + for(var i = 0, l = obj.keys.length; i < l; i++) { + delete obj[obj.keys[i]]; + } + obj.keys.length = 0; + /* + for(var key in this.collidingBodiesLastStep) + delete this.collidingBodiesLastStep[key]; + */ +} + +/** + * Throws away the old equations and gets ready to create new + * @method reset + */ +Narrowphase.prototype.reset = function(world){ + + // Emit world separation event + if(world && world.emitSeparationEvent){ + for(var i=0; i id2){ + var tmp = id1; + id1 = id2; + id2 = tmp; + } + var key = id1 + " " + id2; + if(!this.collidingBodiesLastStep[key]){ + this.collidingBodiesLastStep[key] = true; + this.collidingBodiesLastStep.keys.push(key); + } + } + + if(this.reuseObjects){ + var ce = this.contactEquations, + fe = this.frictionEquations, + rfe = this.reusableFrictionEquations, + rce = this.reusableContactEquations; + Utils.appendArray(rce,ce); + Utils.appendArray(rfe,fe); + } + + // Reset + this.contactEquations.length = this.frictionEquations.length = 0; +}; + +/** + * Creates a ContactEquation, either by reusing an existing object or creating a new one. + * @method createContactEquation + * @param {Body} bodyA + * @param {Body} bodyB + * @return {ContactEquation} + */ +Narrowphase.prototype.createContactEquation = function(bodyA,bodyB,shapeA,shapeB){ + var c = this.reusableContactEquations.length ? this.reusableContactEquations.pop() : new ContactEquation(bodyA,bodyB); + c.bi = bodyA; + c.bj = bodyB; + c.shapeA = shapeA; + c.shapeB = shapeB; + c.restitution = this.restitution; + c.firstImpact = !this.collidedLastStep(bodyA,bodyB); + + if(bodyA.allowSleep && (bodyA.motionState & Body.DYNAMIC) && !(bodyB.motionState & Body.STATIC || bodyB.sleepState === Body.SLEEPY)) + bodyA.wakeUp(); + if(bodyB.allowSleep && (bodyB.motionState & Body.DYNAMIC) && !(bodyA.motionState & Body.STATIC || bodyA.sleepState === Body.SLEEPY)) + bodyB.wakeUp(); + + return c; +}; + +/** + * Creates a FrictionEquation, either by reusing an existing object or creating a new one. + * @method createFrictionEquation + * @param {Body} bodyA + * @param {Body} bodyB + * @return {FrictionEquation} + */ +Narrowphase.prototype.createFrictionEquation = function(bodyA,bodyB,shapeA,shapeB){ + var c = this.reusableFrictionEquations.length ? this.reusableFrictionEquations.pop() : new FrictionEquation(bodyA,bodyB); + c.bi = bodyA; + c.bj = bodyB; + c.shapeA = shapeA; + c.shapeB = shapeB; + c.setSlipForce(this.slipForce); + c.frictionCoefficient = this.frictionCoefficient; + return c; +}; + +/** + * Creates a FrictionEquation given the data in the ContactEquation. Uses same offset vectors ri and rj, but the tangent vector will be constructed from the collision normal. + * @method createFrictionFromContact + * @param {ContactEquation} contactEquation + * @return {FrictionEquation} + */ +Narrowphase.prototype.createFrictionFromContact = function(c){ + var eq = this.createFrictionEquation(c.bi,c.bj,c.shapeA,c.shapeB); + vec2.copy(eq.ri, c.ri); + vec2.copy(eq.rj, c.rj); + vec2.rotate(eq.t, c.ni, -Math.PI / 2); + eq.contactEquation = c; + return eq; +} + +/** + * Convex/line narrowphase + * @method convexLine + * @param {Body} bi + * @param {Convex} si + * @param {Array} xi + * @param {Number} ai + * @param {Body} bj + * @param {Line} sj + * @param {Array} xj + * @param {Number} aj + * @todo Implement me! + */ +Narrowphase.prototype[Shape.LINE | Shape.CONVEX] = +Narrowphase.prototype.convexLine = function(bi,si,xi,ai, bj,sj,xj,aj){ + // TODO +}; + +/** + * Line/rectangle narrowphase + * @method lineRectangle + * @param {Body} bi + * @param {Line} si + * @param {Array} xi + * @param {Number} ai + * @param {Body} bj + * @param {Rectangle} sj + * @param {Array} xj + * @param {Number} aj + * @todo Implement me! + */ +Narrowphase.prototype[Shape.LINE | Shape.RECTANGLE] = +Narrowphase.prototype.lineRectangle = function(bi,si,xi,ai, bj,sj,xj,aj){ + // TODO +}; + +/** + * Rectangle/capsule narrowphase + * @method rectangleCapsule + * @param {Body} bi + * @param {Rectangle} si + * @param {Array} xi + * @param {Number} ai + * @param {Body} bj + * @param {Capsule} sj + * @param {Array} xj + * @param {Number} aj + * @todo Implement me! + */ +Narrowphase.prototype[Shape.CAPSULE | Shape.RECTANGLE] = +Narrowphase.prototype.rectangleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj){ + // TODO +}; + +/** + * Convex/capsule narrowphase + * @method convexCapsule + * @param {Body} bi + * @param {Convex} si + * @param {Array} xi + * @param {Number} ai + * @param {Body} bj + * @param {Capsule} sj + * @param {Array} xj + * @param {Number} aj + * @todo Implement me! + */ +Narrowphase.prototype[Shape.CAPSULE | Shape.CONVEX] = +Narrowphase.prototype.convexCapsule = function(bi,si,xi,ai, bj,sj,xj,aj){ + // TODO +}; + +/** + * Capsule/line narrowphase + * @method lineCapsule + * @param {Body} bi + * @param {Line} si + * @param {Array} xi + * @param {Number} ai + * @param {Body} bj + * @param {Capsule} sj + * @param {Array} xj + * @param {Number} aj + * @todo Implement me! + */ +Narrowphase.prototype[Shape.CAPSULE | Shape.LINE] = +Narrowphase.prototype.lineCapsule = function(bi,si,xi,ai, bj,sj,xj,aj){ + // TODO +}; + +/** + * Capsule/capsule narrowphase + * @method capsuleCapsule + * @param {Body} bi + * @param {Capsule} si + * @param {Array} xi + * @param {Number} ai + * @param {Body} bj + * @param {Capsule} sj + * @param {Array} xj + * @param {Number} aj + * @todo Implement me! + */ +Narrowphase.prototype[Shape.CAPSULE | Shape.CAPSULE] = +Narrowphase.prototype.capsuleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj){ + // TODO +}; + +/** + * Line/line narrowphase + * @method lineLine + * @param {Body} bi + * @param {Line} si + * @param {Array} xi + * @param {Number} ai + * @param {Body} bj + * @param {Line} sj + * @param {Array} xj + * @param {Number} aj + * @todo Implement me! + */ +Narrowphase.prototype[Shape.LINE | Shape.LINE] = +Narrowphase.prototype.lineLine = function(bi,si,xi,ai, bj,sj,xj,aj){ + // TODO +}; + +/** + * Plane/line Narrowphase + * @method planeLine + * @param {Body} planeBody + * @param {Plane} planeShape + * @param {Array} planeOffset + * @param {Number} planeAngle + * @param {Body} lineBody + * @param {Line} lineShape + * @param {Array} lineOffset + * @param {Number} lineAngle + */ +Narrowphase.prototype[Shape.PLANE | Shape.LINE] = +Narrowphase.prototype.planeLine = function(planeBody, planeShape, planeOffset, planeAngle, + lineBody, lineShape, lineOffset, lineAngle){ + var worldVertex0 = tmp1, + worldVertex1 = tmp2, + worldVertex01 = tmp3, + worldVertex11 = tmp4, + worldEdge = tmp5, + worldEdgeUnit = tmp6, + dist = tmp7, + worldNormal = tmp8, + worldTangent = tmp9, + verts = tmpArray; + + // Get start and end points + vec2.set(worldVertex0, -lineShape.length/2, 0); + vec2.set(worldVertex1, lineShape.length/2, 0); + + // Not sure why we have to use worldVertex*1 here, but it won't work otherwise. Tired. + vec2.rotate(worldVertex01, worldVertex0, lineAngle); + vec2.rotate(worldVertex11, worldVertex1, lineAngle); + + add(worldVertex01, worldVertex01, lineOffset); + add(worldVertex11, worldVertex11, lineOffset); + + vec2.copy(worldVertex0,worldVertex01); + vec2.copy(worldVertex1,worldVertex11); + + // Get vector along the line + sub(worldEdge, worldVertex1, worldVertex0); + vec2.normalize(worldEdgeUnit, worldEdge); + + // Get tangent to the edge. + vec2.rotate(worldTangent, worldEdgeUnit, -Math.PI/2); + + vec2.rotate(worldNormal, yAxis, planeAngle); + + // Check line ends + verts[0] = worldVertex0; + verts[1] = worldVertex1; + for(var i=0; i pos0 && pos < pos1){ + // We got contact! + + if(justTest) return true; + + var c = this.createContactEquation(circleBody,lineBody,si,sj); + + vec2.scale(c.ni, orthoDist, -1); + vec2.normalize(c.ni, c.ni); + + vec2.scale( c.ri, c.ni, circleRadius); + add(c.ri, c.ri, circleOffset); + sub(c.ri, c.ri, circleBody.position); + + sub(c.rj, projectedPoint, lineOffset); + add(c.rj, c.rj, lineOffset); + sub(c.rj, c.rj, lineBody.position); + + this.contactEquations.push(c); + + if(this.enableFriction){ + this.frictionEquations.push(this.createFrictionFromContact(c)); + } + + return true; + } + } + + // Add corner + // @todo reuse array object + verts[0] = worldVertex0; + verts[1] = worldVertex1; + + for(var i=0; i 0){ + + // Now project the circle onto the edge + vec2.scale(orthoDist, worldTangent, d); + sub(projectedPoint, circleOffset, orthoDist); + + + // Check if the point is within the edge span + var pos = dot(worldEdgeUnit, projectedPoint); + var pos0 = dot(worldEdgeUnit, worldVertex0); + var pos1 = dot(worldEdgeUnit, worldVertex1); + + if(pos > pos0 && pos < pos1){ + // We got contact! + + if(justTest) return true; + + if(closestEdgeDistance === null || d*d 0){ + for(var i=0; i= 0){ + + // Now project the particle onto the edge + vec2.scale(orthoDist, worldTangent, d); + sub(projectedPoint, particleOffset, orthoDist); + + // Check if the point is within the edge span + var pos = dot(worldEdgeUnit, projectedPoint); + var pos0 = dot(worldEdgeUnit, worldVertex0); + var pos1 = dot(worldEdgeUnit, worldVertex1); + + if(pos > pos0 && pos < pos1){ + // We got contact! + if(justTest) return true; + + if(closestEdgeDistance === null || d*d r*r){ + return false; + } + + if(justTest) return true; + + var c = this.createContactEquation(bodyA,bodyB,si,sj); + sub(c.ni, offsetB, offsetA); + vec2.normalize(c.ni,c.ni); + + vec2.scale( c.ri, c.ni, shapeA.radius); + vec2.scale( c.rj, c.ni, -shapeB.radius); + + add(c.ri, c.ri, offsetA); + sub(c.ri, c.ri, bodyA.position); + + add(c.rj, c.rj, offsetB); + sub(c.rj, c.rj, bodyB.position); + + this.contactEquations.push(c); + + if(this.enableFriction){ + this.frictionEquations.push(this.createFrictionFromContact(c)); + } + return true; +}; + +/** + * Plane/Convex Narrowphase + * @method planeConvex + * @param {Body} bi + * @param {Plane} si + * @param {Array} xi + * @param {Number} ai + * @param {Body} bj + * @param {Convex} sj + * @param {Array} xj + * @param {Number} aj + */ +Narrowphase.prototype[Shape.PLANE | Shape.CONVEX] = +Narrowphase.prototype.planeConvex = function( bi,si,xi,ai, bj,sj,xj,aj ){ + var convexBody = bj, + convexOffset = xj, + convexShape = sj, + convexAngle = aj, + planeBody = bi, + planeShape = si, + planeOffset = xi, + planeAngle = ai; + + var worldVertex = tmp1, + worldNormal = tmp2, + dist = tmp3; + + var numReported = 0; + vec2.rotate(worldNormal, yAxis, planeAngle); + + for(var i=0; i= 2) + break; + } + } + return numReported > 0; +}; + +/** + * @method convexPlane + * @deprecated Use .planeConvex() instead! + */ +Narrowphase.prototype.convexPlane = function( bi,si,xi,ai, bj,sj,xj,aj ){ + console.warn("Narrowphase.prototype.convexPlane is deprecated. Use planeConvex instead!"); + return this.planeConvex( bj,sj,xj,aj, bi,si,xi,ai ); +} + +/** + * Narrowphase for particle vs plane + * @method particlePlane + * @param {Body} bi The particle body + * @param {Particle} si Particle shape + * @param {Array} xi World position for the particle + * @param {Number} ai World angle for the particle + * @param {Body} bj Plane body + * @param {Plane} sj Plane shape + * @param {Array} xj World position for the plane + * @param {Number} aj World angle for the plane + */ +Narrowphase.prototype[Shape.PARTICLE | Shape.PLANE] = +Narrowphase.prototype.particlePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){ + var particleBody = bi, + particleShape = si, + particleOffset = xi, + planeBody = bj, + planeShape = sj, + planeOffset = xj, + planeAngle = aj; + + var dist = tmp1, + worldNormal = tmp2; + + planeAngle = planeAngle || 0; + + sub(dist, particleOffset, planeOffset); + vec2.rotate(worldNormal, yAxis, planeAngle); + + var d = dot(dist, worldNormal); + + if(d > 0) return false; + if(justTest) return true; + + var c = this.createContactEquation(planeBody,particleBody,sj,si); + + vec2.copy(c.ni, worldNormal); + vec2.scale( dist, c.ni, d ); + // dist is now the distance vector in the normal direction + + // ri is the particle position projected down onto the plane, from the plane center + sub( c.ri, particleOffset, dist); + sub( c.ri, c.ri, planeBody.position); + + // rj is from the body center to the particle center + sub( c.rj, particleOffset, particleBody.position ); + + this.contactEquations.push(c); + + if(this.enableFriction){ + this.frictionEquations.push(this.createFrictionFromContact(c)); + } + return true; +}; + +/** + * Circle/Particle Narrowphase + * @method circleParticle + * @param {Body} bi + * @param {Circle} si + * @param {Array} xi + * @param {Number} ai + * @param {Body} bj + * @param {Particle} sj + * @param {Array} xj + * @param {Number} aj + */ +Narrowphase.prototype[Shape.CIRCLE | Shape.PARTICLE] = +Narrowphase.prototype.circleParticle = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){ + var circleBody = bi, + circleShape = si, + circleOffset = xi, + particleBody = bj, + particleShape = sj, + particleOffset = xj, + dist = tmp1; + + sub(dist, particleOffset, circleOffset); + if(vec2.squaredLength(dist) > circleShape.radius*circleShape.radius) return false; + if(justTest) return true; + + var c = this.createContactEquation(circleBody,particleBody,si,sj); + vec2.copy(c.ni, dist); + vec2.normalize(c.ni,c.ni); + + // Vector from circle to contact point is the normal times the circle radius + vec2.scale(c.ri, c.ni, circleShape.radius); + add(c.ri, c.ri, circleOffset); + sub(c.ri, c.ri, circleBody.position); + + // Vector from particle center to contact point is zero + sub(c.rj, particleOffset, particleBody.position); + + this.contactEquations.push(c); + + if(this.enableFriction){ + this.frictionEquations.push(this.createFrictionFromContact(c)); + } + + return true; +}; + +var capsulePlane_tmpCircle = new Circle(1), + capsulePlane_tmp1 = vec2.create(), + capsulePlane_tmp2 = vec2.create(), + capsulePlane_tmp3 = vec2.create(); + +Narrowphase.prototype[Shape.PLANE | Shape.CAPSULE] = +Narrowphase.prototype.planeCapsule = function( bi,si,xi,ai, bj,sj,xj,aj ){ + var end1 = capsulePlane_tmp1, + end2 = capsulePlane_tmp2, + circle = capsulePlane_tmpCircle, + dst = capsulePlane_tmp3; + + // Compute world end positions + vec2.set(end1, -sj.length/2, 0); + vec2.rotate(end1,end1,aj); + add(end1,end1,xj); + + vec2.set(end2, sj.length/2, 0); + vec2.rotate(end2,end2,aj); + add(end2,end2,xj); + + circle.radius = sj.radius; + + // Do Narrowphase as two circles + this.circlePlane(bj,circle,end1,0, bi,si,xi,ai); + this.circlePlane(bj,circle,end2,0, bi,si,xi,ai); +}; + +/** + * @method capsulePlane + * @deprecated Use .planeCapsule() instead! + */ +Narrowphase.prototype.capsulePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){ + console.warn("Narrowphase.prototype.capsulePlane() is deprecated. Use .planeCapsule() instead!"); + return this.planeCapsule( bj,sj,xj,aj, bi,si,xi,ai ); +} + +/** + * Creates ContactEquations and FrictionEquations for a collision. + * @method circlePlane + * @param {Body} bi The first body that should be connected to the equations. + * @param {Circle} si The circle shape participating in the collision. + * @param {Array} xi Extra offset to take into account for the Shape, in addition to the one in circleBody.position. Will *not* be rotated by circleBody.angle (maybe it should, for sake of homogenity?). Set to null if none. + * @param {Body} bj The second body that should be connected to the equations. + * @param {Plane} sj The Plane shape that is participating + * @param {Array} xj Extra offset for the plane shape. + * @param {Number} aj Extra angle to apply to the plane + */ +Narrowphase.prototype[Shape.CIRCLE | Shape.PLANE] = +Narrowphase.prototype.circlePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){ + var circleBody = bi, + circleShape = si, + circleOffset = xi, // Offset from body center, rotated! + planeBody = bj, + shapeB = sj, + planeOffset = xj, + planeAngle = aj; + + planeAngle = planeAngle || 0; + + // Vector from plane to circle + var planeToCircle = tmp1, + worldNormal = tmp2, + temp = tmp3; + + sub(planeToCircle, circleOffset, planeOffset); + + // World plane normal + vec2.rotate(worldNormal, yAxis, planeAngle); + + // Normal direction distance + var d = dot(worldNormal, planeToCircle); + + if(d > circleShape.radius) return false; // No overlap. Abort. + + // Create contact + var contact = this.createContactEquation(planeBody,circleBody,sj,si); + + // ni is the plane world normal + vec2.copy(contact.ni, worldNormal); + + // rj is the vector from circle center to the contact point + vec2.scale(contact.rj, contact.ni, -circleShape.radius); + add(contact.rj, contact.rj, circleOffset); + sub(contact.rj, contact.rj, circleBody.position); + + // ri is the distance from plane center to contact. + vec2.scale(temp, contact.ni, d); + sub(contact.ri, planeToCircle, temp ); // Subtract normal distance vector from the distance vector + add(contact.ri, contact.ri, planeOffset); + sub(contact.ri, contact.ri, planeBody.position); + + this.contactEquations.push(contact); + + if(this.enableFriction){ + this.frictionEquations.push( this.createFrictionFromContact(contact) ); + } + + return true; +}; + + +/** + * Convex/convex Narrowphase.See this article for more info. + * @method convexConvex + * @param {Body} bi + * @param {Convex} si + * @param {Array} xi + * @param {Number} ai + * @param {Body} bj + * @param {Convex} sj + * @param {Array} xj + * @param {Number} aj + */ +Narrowphase.prototype[Shape.CONVEX] = +Narrowphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj, precision ){ + var sepAxis = tmp1, + worldPoint = tmp2, + worldPoint0 = tmp3, + worldPoint1 = tmp4, + worldEdge = tmp5, + projected = tmp6, + penetrationVec = tmp7, + dist = tmp8, + worldNormal = tmp9, + precision = precision || 1e-10; + + var found = Narrowphase.findSeparatingAxis(si,xi,ai,sj,xj,aj,sepAxis); + if(!found) return false; + + // Make sure the separating axis is directed from shape i to shape j + sub(dist,xj,xi); + if(dot(sepAxis,dist) > 0){ + vec2.scale(sepAxis,sepAxis,-1); + } + + // Find edges with normals closest to the separating axis + var closestEdge1 = Narrowphase.getClosestEdge(si,ai,sepAxis,true), // Flipped axis + closestEdge2 = Narrowphase.getClosestEdge(sj,aj,sepAxis); + + if(closestEdge1==-1 || closestEdge2==-1) return false; + + // Loop over the shapes + for(var k=0; k<2; k++){ + + var closestEdgeA = closestEdge1, + closestEdgeB = closestEdge2, + shapeA = si, shapeB = sj, + offsetA = xi, offsetB = xj, + angleA = ai, angleB = aj, + bodyA = bi, bodyB = bj; + + if(k==0){ + // Swap! + var tmp; + tmp = closestEdgeA; closestEdgeA = closestEdgeB; closestEdgeB = tmp; + tmp = shapeA; shapeA = shapeB; shapeB = tmp; + tmp = offsetA; offsetA = offsetB; offsetB = tmp; + tmp = angleA; angleA = angleB; angleB = tmp; + tmp = bodyA; bodyA = bodyB; bodyB = tmp; + } + + // Loop over 2 points in convex B + for(var j=closestEdgeB; j max) max = value; + if(min === null || value < min) min = value; + } + + if(min > max){ + var t = min; + min = max; + max = t; + } + + // Project the position of the body onto the axis - need to add this to the result + var offset = dot(convexOffset, worldAxis); + + vec2.set( result, min + offset, max + offset); +}; + +// .findSeparatingAxis is called by other functions, need local tmp vectors +var fsa_tmp1 = vec2.fromValues(0,0) +, fsa_tmp2 = vec2.fromValues(0,0) +, fsa_tmp3 = vec2.fromValues(0,0) +, fsa_tmp4 = vec2.fromValues(0,0) +, fsa_tmp5 = vec2.fromValues(0,0) +, fsa_tmp6 = vec2.fromValues(0,0) + +/** + * Find a separating axis between the shapes, that maximizes the separating distance between them. + * @method findSeparatingAxis + * @static + * @param {Convex} c1 + * @param {Array} offset1 + * @param {Number} angle1 + * @param {Convex} c2 + * @param {Array} offset2 + * @param {Number} angle2 + * @param {Array} sepAxis The resulting axis + * @return {Boolean} Whether the axis could be found. + */ +Narrowphase.findSeparatingAxis = function(c1,offset1,angle1,c2,offset2,angle2,sepAxis){ + var maxDist = null, + overlap = false, + found = false, + edge = fsa_tmp1, + worldPoint0 = fsa_tmp2, + worldPoint1 = fsa_tmp3, + normal = fsa_tmp4, + span1 = fsa_tmp5, + span2 = fsa_tmp6; + + for(var j=0; j!==2; j++){ + var c = c1, + angle = angle1; + if(j===1){ + c = c2; + angle = angle2; + } + + for(var i=0; i!==c.vertices.length; i++){ + // Get the world edge + vec2.rotate(worldPoint0, c.vertices[i], angle); + vec2.rotate(worldPoint1, c.vertices[(i+1)%c.vertices.length], angle); + + sub(edge, worldPoint1, worldPoint0); + + // Get normal - just rotate 90 degrees since vertices are given in CCW + vec2.rotate(normal, edge, -Math.PI / 2); + vec2.normalize(normal,normal); + + // Project hulls onto that normal + Narrowphase.projectConvexOntoAxis(c1,offset1,angle1,normal,span1); + Narrowphase.projectConvexOntoAxis(c2,offset2,angle2,normal,span2); + + // Order by span position + var a=span1, + b=span2, + swapped = false; + if(span1[0] > span2[0]){ + b=span1; + a=span2; + swapped = true; + } + + // Get separating distance + var dist = b[0] - a[1]; + overlap = dist < 0; + + if(maxDist===null || dist > maxDist){ + vec2.copy(sepAxis, normal); + maxDist = dist; + found = overlap; + } + } + } + + return found; +}; + +// .getClosestEdge is called by other functions, need local tmp vectors +var gce_tmp1 = vec2.fromValues(0,0) +, gce_tmp2 = vec2.fromValues(0,0) +, gce_tmp3 = vec2.fromValues(0,0) + +/** + * Get the edge that has a normal closest to an axis. + * @method getClosestEdge + * @static + * @param {Convex} c + * @param {Number} angle + * @param {Array} axis + * @param {Boolean} flip + * @return {Number} Index of the edge that is closest. This index and the next spans the resulting edge. Returns -1 if failed. + */ +Narrowphase.getClosestEdge = function(c,angle,axis,flip){ + var localAxis = gce_tmp1, + edge = gce_tmp2, + normal = gce_tmp3; + + // Convert the axis to local coords of the body + vec2.rotate(localAxis, axis, -angle); + if(flip){ + vec2.scale(localAxis,localAxis,-1); + } + + var closestEdge = -1, + N = c.vertices.length, + halfPi = Math.PI / 2; + for(var i=0; i!==N; i++){ + // Get the edge + sub(edge, c.vertices[(i+1)%N], c.vertices[i%N]); + + // Get normal - just rotate 90 degrees since vertices are given in CCW + vec2.rotate(normal, edge, -halfPi); + vec2.normalize(normal,normal); + + var d = dot(normal,localAxis); + if(closestEdge == -1 || d > maxDot){ + closestEdge = i % N; + maxDot = d; + } + } + + return closestEdge; +}; + + +},{"../equations/ContactEquation":23,"../equations/FrictionEquation":25,"../math/vec2":33,"../objects/Body":34,"../shapes/Circle":38,"../shapes/Shape":44,"../utils/Utils":49}],14:[function(require,module,exports){ +var Plane = require("../shapes/Plane"); +var Broadphase = require("../collision/Broadphase"); + +module.exports = { + QuadTree : QuadTree, + Node : Node, + BoundsNode : BoundsNode, +}; + +/** + * QuadTree data structure. See https://github.com/mikechambers/ExamplesByMesh/tree/master/JavaScript/QuadTree + * @class QuadTree + * @constructor + * @param {Object} An object representing the bounds of the top level of the QuadTree. The object + * should contain the following properties : x, y, width, height + * @param {Boolean} pointQuad Whether the QuadTree will contain points (true), or items with bounds + * (width / height)(false). Default value is false. + * @param {Number} maxDepth The maximum number of levels that the quadtree will create. Default is 4. + * @param {Number} maxChildren The maximum number of children that a node can contain before it is split into sub-nodes. + */ +function QuadTree(bounds, pointQuad, maxDepth, maxChildren){ + var node; + if(pointQuad){ + node = new Node(bounds, 0, maxDepth, maxChildren); + } else { + node = new BoundsNode(bounds, 0, maxDepth, maxChildren); + } + + /** + * The root node of the QuadTree which covers the entire area being segmented. + * @property root + * @type Node + */ + this.root = node; +} + +/** + * Inserts an item into the QuadTree. + * @method insert + * @param {Object|Array} item The item or Array of items to be inserted into the QuadTree. The item should expose x, y + * properties that represents its position in 2D space. + */ +QuadTree.prototype.insert = function(item){ + if(item instanceof Array){ + var len = item.length; + for(var i = 0; i < len; i++){ + this.root.insert(item[i]); + } + } else { + this.root.insert(item); + } +} + +/** + * Clears all nodes and children from the QuadTree + * @method clear + */ +QuadTree.prototype.clear = function(){ + this.root.clear(); +} + +/** + * Retrieves all items / points in the same node as the specified item / point. If the specified item + * overlaps the bounds of a node, then all children in both nodes will be returned. + * @method retrieve + * @param {Object} item An object representing a 2D coordinate point (with x, y properties), or a shape + * with dimensions (x, y, width, height) properties. + */ +QuadTree.prototype.retrieve = function(item){ + //get a copy of the array of items + var out = this.root.retrieve(item).slice(0); + return out; +} + +QuadTree.prototype.getCollisionPairs = function(world){ + + var result = []; + + // Add all bodies + this.insert(world.bodies); + + /* + console.log("bodies",world.bodies.length); + console.log("maxDepth",this.root.maxDepth,"maxChildren",this.root.maxChildren); + */ + + for(var i=0; i!==world.bodies.length; i++){ + var b = world.bodies[i], + items = this.retrieve(b); + + //console.log("items",items.length); + + // Check results + for(var j=0, len=items.length; j!==len; j++){ + var item = items[j]; + + if(b === item) continue; // Do not add self + + // Check if they were already added + var found = false; + for(var k=0, numAdded=result.length; k= this.maxDepth) && len > this.maxChildren) { + this.subdivide(); + + for(var i = 0; i < len; i++){ + this.insert(this.children[i]); + } + + this.children.length = 0; + } +} + +Node.prototype.retrieve = function(item){ + if(this.nodes.length){ + var index = this.findIndex(item); + return this.nodes[index].retrieve(item); + } + + return this.children; +} + +Node.prototype.findIndex = function(item){ + var b = this.bounds; + var left = (item.position[0]-item.boundingRadius > b.x + b.width / 2) ? false : true; + var top = (item.position[1]-item.boundingRadius > b.y + b.height / 2) ? false : true; + + if(item instanceof Plane){ + left = top = false; // Will overlap the left/top boundary since it is infinite + } + + //top left + var index = Node.TOP_LEFT; + if(left){ + if(!top){ + index = Node.BOTTOM_LEFT; + } + } else { + if(top){ + index = Node.TOP_RIGHT; + } else { + index = Node.BOTTOM_RIGHT; + } + } + + return index; +} + + +Node.prototype.subdivide = function(){ + var depth = this.depth + 1; + + var bx = this.bounds.x; + var by = this.bounds.y; + + //floor the values + var b_w_h = (this.bounds.width / 2); + var b_h_h = (this.bounds.height / 2); + var bx_b_w_h = bx + b_w_h; + var by_b_h_h = by + b_h_h; + + //top left + this.nodes[Node.TOP_LEFT] = new this.classConstructor({ + x:bx, + y:by, + width:b_w_h, + height:b_h_h + }, + depth); + + //top right + this.nodes[Node.TOP_RIGHT] = new this.classConstructor({ + x:bx_b_w_h, + y:by, + width:b_w_h, + height:b_h_h + }, + depth); + + //bottom left + this.nodes[Node.BOTTOM_LEFT] = new this.classConstructor({ + x:bx, + y:by_b_h_h, + width:b_w_h, + height:b_h_h + }, + depth); + + + //bottom right + this.nodes[Node.BOTTOM_RIGHT] = new this.classConstructor({ + x:bx_b_w_h, + y:by_b_h_h, + width:b_w_h, + height:b_h_h + }, + depth); +} + +Node.prototype.clear = function(){ + this.children.length = 0; + + var len = this.nodes.length; + for(var i = 0; i < len; i++){ + this.nodes[i].clear(); + } + + this.nodes.length = 0; +} + + +// BoundsQuadTree + +function BoundsNode(bounds, depth, maxChildren, maxDepth){ + Node.call(this, bounds, depth, maxChildren, maxDepth); + this.stuckChildren = []; +} + +BoundsNode.prototype = new Node(); +BoundsNode.prototype.classConstructor = BoundsNode; +BoundsNode.prototype.stuckChildren = null; + +//we use this to collect and conctenate items being retrieved. This way +//we dont have to continuously create new Array instances. +//Note, when returned from QuadTree.retrieve, we then copy the array +BoundsNode.prototype.out = []; + +BoundsNode.prototype.insert = function(item){ + if(this.nodes.length){ + var index = this.findIndex(item); + var node = this.nodes[index]; + + /* + console.log("radius:",item.boundingRadius); + console.log("item x:",item.position[0] - item.boundingRadius,"x range:",node.bounds.x,node.bounds.x+node.bounds.width); + console.log("item y:",item.position[1] - item.boundingRadius,"y range:",node.bounds.y,node.bounds.y+node.bounds.height); + */ + + //todo: make _bounds bounds + if( !(item instanceof Plane) && // Plane is infinite.. Make it a "stuck" child + item.position[0] - item.boundingRadius >= node.bounds.x && + item.position[0] + item.boundingRadius <= node.bounds.x + node.bounds.width && + item.position[1] - item.boundingRadius >= node.bounds.y && + item.position[1] + item.boundingRadius <= node.bounds.y + node.bounds.height){ + this.nodes[index].insert(item); + } else { + this.stuckChildren.push(item); + } + + return; + } + + this.children.push(item); + + var len = this.children.length; + + if(this.depth < this.maxDepth && len > this.maxChildren){ + this.subdivide(); + + for(var i=0; i