From bd54460e80b226ecc685c0d40cbdb4f2107b162f Mon Sep 17 00:00:00 2001 From: Richard Davey Date: Thu, 23 May 2013 00:01:58 +0100 Subject: [PATCH] Added the GameObjectManager --- Phaser/AnimationManager.ts | 23 +- Phaser/CameraManager.ts | 3 - Phaser/Collision.ts | 896 +++++++--- Phaser/Game.ts | 129 +- Phaser/GameMath.ts | 29 +- Phaser/GameObjectFactory.ts | 219 +++ Phaser/Loader.ts | 7 + Phaser/Phaser.csproj | 12 + Phaser/Phaser.ts | 4 +- Phaser/Stage.ts | 24 +- Phaser/State.ts | 126 +- Phaser/TweenManager.ts | 1 + Phaser/VerletManager.ts | 114 +- Phaser/World.ts | 11 +- Phaser/gameobjects/GameObject.ts | 6 + Phaser/gameobjects/GeomSprite.ts | 29 + Phaser/gameobjects/Sprite.ts | 45 +- Phaser/geom/Circle.ts | 13 +- Phaser/geom/Polygon.ts | 60 + Phaser/geom/Quad.ts | 31 + Phaser/geom/Response.ts | 87 + Phaser/geom/Vector2.ts | 314 +++- Phaser/phaser.js | 4 +- Phaser/system/Camera.ts | 2 +- Phaser/system/CollisionMask.ts | 23 +- Phaser/system/input/Input.ts | 200 ++- Phaser/system/input/Pointer.ts | 40 +- Phaser/system/screens/BootScreen.ts | 2 +- Phaser/system/screens/PauseScreen.ts | 7 +- Phaser/verlet/Composite.ts | 127 +- Phaser/verlet/DistanceConstraint.ts | 1 + README.md | 28 +- Tests/Tests.csproj | 16 + Tests/collision/mask animation 1.js | 31 + Tests/collision/mask animation 1.ts | 50 + Tests/collision/mask test 1.js | 3 +- Tests/collision/mask test 1.ts | 3 - Tests/geometry/rope bridge.js | 34 + Tests/geometry/rope bridge.ts | 55 + Tests/geometry/verlet 1.js | 6 +- Tests/geometry/verlet 1.ts | 6 - Tests/geometry/verlet sprites.js | 39 +- Tests/geometry/verlet sprites.ts | 54 +- Tests/phaser.js | 2338 ++++++++++++++++++-------- Tests/physics/temp1.js | 113 ++ Tests/physics/temp1.ts | 200 +++ Tests/physics/temp2.js | 1218 ++++++++++++++ Tests/physics/temp2.ts | 1683 ++++++++++++++++++ Tests/scrollzones/blasteroids.js | 1 + Tests/tilemap/collide with tile.ts | 11 +- Tests/tilemap/collision.js | 34 +- Tests/tilemap/collision.ts | 34 +- build/phaser.d.ts | 1015 ++++++++--- build/phaser.js | 2338 ++++++++++++++++++-------- 54 files changed, 9378 insertions(+), 2521 deletions(-) create mode 100644 Phaser/GameObjectFactory.ts create mode 100644 Phaser/geom/Polygon.ts create mode 100644 Phaser/geom/Response.ts create mode 100644 Tests/collision/mask animation 1.js create mode 100644 Tests/collision/mask animation 1.ts create mode 100644 Tests/geometry/rope bridge.js create mode 100644 Tests/geometry/rope bridge.ts create mode 100644 Tests/physics/temp1.js create mode 100644 Tests/physics/temp1.ts create mode 100644 Tests/physics/temp2.js create mode 100644 Tests/physics/temp2.ts diff --git a/Phaser/AnimationManager.ts b/Phaser/AnimationManager.ts index c8aac8ee..8c530f37 100644 --- a/Phaser/AnimationManager.ts +++ b/Phaser/AnimationManager.ts @@ -211,7 +211,15 @@ module Phaser { } public get frameTotal(): number { - return this._frameData.total; + + if (this._frameData) + { + return this._frameData.total; + } + else + { + return -1; + } } public get frame(): number { @@ -248,6 +256,19 @@ module Phaser { } + /** + * Removes all related references + */ + public destroy() { + + this._anims = {}; + this._frameData = null; + this._frameIndex = 0; + this.currentAnim = null; + this.currentFrame = null; + + } + } } \ No newline at end of file diff --git a/Phaser/CameraManager.ts b/Phaser/CameraManager.ts index fa8a7af2..272a5f7e 100644 --- a/Phaser/CameraManager.ts +++ b/Phaser/CameraManager.ts @@ -6,9 +6,6 @@ * * Your game only has one CameraManager instance and it's responsible for looking after, creating and destroying * all of the cameras in the world. -* -* TODO: If the Camera is larger than the Stage size then the rotation offset isn't correct -* TODO: Texture Repeat doesn't scroll, because it's part of the camera not the world, need to think about this more */ module Phaser { diff --git a/Phaser/Collision.ts b/Phaser/Collision.ts index 36e46a0e..2ba41621 100644 --- a/Phaser/Collision.ts +++ b/Phaser/Collision.ts @@ -5,6 +5,8 @@ /// /// /// +/// +/// /// /** @@ -25,6 +27,20 @@ module Phaser { this._game = game; + Collision.T_VECTORS = []; + + for (var i = 0; i < 10; i++) + { + Collision.T_VECTORS.push(new Vector2); + } + + Collision.T_ARRAYS = []; + + for (var i = 0; i < 5; i++) + { + Collision.T_ARRAYS.push([]); + } + } /** @@ -553,20 +569,6 @@ module Phaser { } - /* - public static circleToQuad(circle: Circle, quad: Quad): bool { - - // Check if the center of the circle is within the Quad - if (quad.contains(circle.x, circle.y)) - { - return true; - } - - // Failing that let's check each line of the quad against the circle - return false; - } - */ - /** * Checks if the Circle object intersects with the Rectangle and returns the result in an IntersectResult object. * @param circle The Circle object to check @@ -647,8 +649,6 @@ module Phaser { */ public static separate(object1, object2): bool { - console.log('sep o'); - object1.collisionMask.update(); object2.collisionMask.update(); @@ -667,6 +667,8 @@ module Phaser { */ public static separateTile(object:GameObject, x: number, y: number, width: number, height: number, mass: number, collideLeft: bool, collideRight: bool, collideUp: bool, collideDown: bool, separateX: bool, separateY: bool): bool { + object.collisionMask.update(); + var separatedX: bool = Collision.separateTileX(object, x, y, width, height, mass, collideLeft, collideRight, separateX); var separatedY: bool = Collision.separateTileY(object, x, y, width, height, mass, collideUp, collideDown, separateY); @@ -691,11 +693,13 @@ module Phaser { // First, get the object delta var overlap: number = 0; var objDelta: number = object.x - object.last.x; + //var objDelta: number = object.collisionMask.deltaX; if (objDelta != 0) { // Check if the X hulls actually overlap var objDeltaAbs: number = (objDelta > 0) ? objDelta : -objDelta; + //var objDeltaAbs: number = object.collisionMask.deltaXAbs; var objBounds: Quad = new Quad(object.x - ((objDelta > 0) ? objDelta : 0), object.last.y, object.width + ((objDelta > 0) ? objDelta : -objDelta), object.height); if ((objBounds.x + objBounds.width > x) && (objBounds.x < x + width) && (objBounds.y + objBounds.height > y) && (objBounds.y < y + height)) @@ -739,6 +743,7 @@ module Phaser { { if (separate == true) { + //console.log(' object.x = object.x - overlap; object.velocity.x = -(object.velocity.x * object.elasticity); } @@ -831,6 +836,170 @@ module Phaser { } } + + /** + * Separates the two objects on their x axis + * @param object The GameObject to separate + * @param tile The Tile to separate + * @returns {boolean} Whether the objects in fact touched and were separated along the X axis. + */ + public static NEWseparateTileX(object:GameObject, x: number, y: number, width: number, height: number, mass: number, collideLeft: bool, collideRight: bool, separate: bool): bool { + + // Can't separate two immovable objects (tiles are always immovable) + if (object.immovable) + { + return false; + } + + // First, get the object delta + var overlap: number = 0; + + if (object.collisionMask.deltaX != 0) + { + // Check if the X hulls actually overlap + //var objDeltaAbs: number = (objDelta > 0) ? objDelta : -objDelta; + //var objBounds: Quad = new Quad(object.x - ((objDelta > 0) ? objDelta : 0), object.last.y, object.width + ((objDelta > 0) ? objDelta : -objDelta), object.height); + + //if ((objBounds.x + objBounds.width > x) && (objBounds.x < x + width) && (objBounds.y + objBounds.height > y) && (objBounds.y < y + height)) + if (object.collisionMask.intersectsRaw(x, x + width, y, y + height)) + { + var maxOverlap: number = object.collisionMask.deltaXAbs + Collision.OVERLAP_BIAS; + + // If they did overlap (and can), figure out by how much and flip the corresponding flags + if (object.collisionMask.deltaX > 0) + { + //overlap = object.x + object.width - x; + overlap = object.collisionMask.right - x; + + if ((overlap > maxOverlap) || !(object.allowCollisions & Collision.RIGHT) || collideLeft == false) + { + overlap = 0; + } + else + { + object.touching |= Collision.RIGHT; + } + } + else if (object.collisionMask.deltaX < 0) + { + //overlap = object.x - width - x; + overlap = object.collisionMask.x - width - x; + + if ((-overlap > maxOverlap) || !(object.allowCollisions & Collision.LEFT) || collideRight == false) + { + overlap = 0; + } + else + { + object.touching |= Collision.LEFT; + } + + } + + } + } + + // Then adjust their positions and velocities accordingly (if there was any overlap) + if (overlap != 0) + { + if (separate == true) + { + object.x = object.x - overlap; + object.velocity.x = -(object.velocity.x * object.elasticity); + } + + Collision.TILE_OVERLAP = true; + return true; + } + else + { + return false; + } + + } + + /** + * Separates the two objects on their y axis + * @param object The first GameObject to separate + * @param tile The second GameObject to separate + * @returns {boolean} Whether the objects in fact touched and were separated along the Y axis. + */ + public static NEWseparateTileY(object: GameObject, x: number, y: number, width: number, height: number, mass: number, collideUp: bool, collideDown: bool, separate: bool): bool { + + // Can't separate two immovable objects (tiles are always immovable) + if (object.immovable) + { + return false; + } + + // First, get the two object deltas + var overlap: number = 0; + //var objDelta: number = object.y - object.last.y; + + if (object.collisionMask.deltaY != 0) + { + // Check if the Y hulls actually overlap + //var objDeltaAbs: number = (objDelta > 0) ? objDelta : -objDelta; + //var objBounds: Quad = new Quad(object.x, object.y - ((objDelta > 0) ? objDelta : 0), object.width, object.height + objDeltaAbs); + + //if ((objBounds.x + objBounds.width > x) && (objBounds.x < x + width) && (objBounds.y + objBounds.height > y) && (objBounds.y < y + height)) + if (object.collisionMask.intersectsRaw(x, x + width, y, y + height)) + { + //var maxOverlap: number = objDeltaAbs + Collision.OVERLAP_BIAS; + var maxOverlap: number = object.collisionMask.deltaYAbs + Collision.OVERLAP_BIAS; + + // If they did overlap (and can), figure out by how much and flip the corresponding flags + if (object.collisionMask.deltaY > 0) + { + //overlap = object.y + object.height - y; + overlap = object.collisionMask.bottom - y; + + if ((overlap > maxOverlap) || !(object.allowCollisions & Collision.DOWN) || collideUp == false) + { + overlap = 0; + } + else + { + object.touching |= Collision.DOWN; + } + } + else if (object.collisionMask.deltaY < 0) + { + //overlap = object.y - height - y; + overlap = object.collisionMask.y - height - y; + + if ((-overlap > maxOverlap) || !(object.allowCollisions & Collision.UP) || collideDown == false) + { + overlap = 0; + } + else + { + object.touching |= Collision.UP; + } + } + } + } + + // TODO - with super low velocities you get lots of stuttering, set some kind of base minimum here + + // Then adjust their positions and velocities accordingly (if there was any overlap) + if (overlap != 0) + { + if (separate == true) + { + object.y = object.y - overlap; + object.velocity.y = -(object.velocity.y * object.elasticity); + } + + Collision.TILE_OVERLAP = true; + return true; + } + else + { + return false; + } + } + /** * Separates the two objects on their x axis * @param object1 The first GameObject to separate @@ -1032,229 +1201,6 @@ module Phaser { } } - /** - * Separates the two objects on their x axis - * @param object1 The first GameObject to separate - * @param object2 The second GameObject to separate - * @returns {boolean} Whether the objects in fact touched and were separated along the X axis. - */ - public static OLDseparateX(object1, object2): bool { - - // Can't separate two immovable objects - if (object1.immovable && object2.immovable) - { - return false; - } - - // First, get the two object deltas - var overlap: number = 0; - var obj1Delta: number = object1.x - object1.last.x; - var obj2Delta: number = object2.x - object2.last.x; - - if (obj1Delta != obj2Delta) - { - // Check if the X hulls actually overlap - var obj1DeltaAbs: number = (obj1Delta > 0) ? obj1Delta : -obj1Delta; - var obj2DeltaAbs: number = (obj2Delta > 0) ? obj2Delta : -obj2Delta; - var obj1Bounds: Quad = new Quad(object1.x - ((obj1Delta > 0) ? obj1Delta : 0), object1.last.y, object1.width + ((obj1Delta > 0) ? obj1Delta : -obj1Delta), object1.height); - var obj2Bounds: Quad = new Quad(object2.x - ((obj2Delta > 0) ? obj2Delta : 0), object2.last.y, object2.width + ((obj2Delta > 0) ? obj2Delta : -obj2Delta), object2.height); - - if ((obj1Bounds.x + obj1Bounds.width > obj2Bounds.x) && (obj1Bounds.x < obj2Bounds.x + obj2Bounds.width) && (obj1Bounds.y + obj1Bounds.height > obj2Bounds.y) && (obj1Bounds.y < obj2Bounds.y + obj2Bounds.height)) - { - var maxOverlap: number = obj1DeltaAbs + obj2DeltaAbs + Collision.OVERLAP_BIAS; - - // If they did overlap (and can), figure out by how much and flip the corresponding flags - if (obj1Delta > obj2Delta) - { - overlap = object1.x + object1.width - object2.x; - - if ((overlap > maxOverlap) || !(object1.allowCollisions & Collision.RIGHT) || !(object2.allowCollisions & Collision.LEFT)) - { - overlap = 0; - } - else - { - object1.touching |= Collision.RIGHT; - object2.touching |= Collision.LEFT; - } - } - else if (obj1Delta < obj2Delta) - { - overlap = object1.x - object2.width - object2.x; - - if ((-overlap > maxOverlap) || !(object1.allowCollisions & Collision.LEFT) || !(object2.allowCollisions & Collision.RIGHT)) - { - overlap = 0; - } - else - { - object1.touching |= Collision.LEFT; - object2.touching |= Collision.RIGHT; - } - - } - - } - } - - // Then adjust their positions and velocities accordingly (if there was any overlap) - if (overlap != 0) - { - var obj1Velocity: number = object1.velocity.x; - var obj2Velocity: number = object2.velocity.x; - - if (!object1.immovable && !object2.immovable) - { - overlap *= 0.5; - object1.x = object1.x - overlap; - object2.x += overlap; - - var obj1NewVelocity: number = Math.sqrt((obj2Velocity * obj2Velocity * object2.mass) / object1.mass) * ((obj2Velocity > 0) ? 1 : -1); - var obj2NewVelocity: number = Math.sqrt((obj1Velocity * obj1Velocity * object1.mass) / object2.mass) * ((obj1Velocity > 0) ? 1 : -1); - var average: number = (obj1NewVelocity + obj2NewVelocity) * 0.5; - obj1NewVelocity -= average; - obj2NewVelocity -= average; - object1.velocity.x = average + obj1NewVelocity * object1.elasticity; - object2.velocity.x = average + obj2NewVelocity * object2.elasticity; - } - else if (!object1.immovable) - { - object1.x = object1.x - overlap; - object1.velocity.x = obj2Velocity - obj1Velocity * object1.elasticity; - } - else if (!object2.immovable) - { - object2.x += overlap; - object2.velocity.x = obj1Velocity - obj2Velocity * object2.elasticity; - } - - return true; - } - else - { - return false; - } - - } - - /** - * Separates the two objects on their y axis - * @param object1 The first GameObject to separate - * @param object2 The second GameObject to separate - * @returns {boolean} Whether the objects in fact touched and were separated along the Y axis. - */ - public static OLDseparateY(object1, object2): bool { - - // Can't separate two immovable objects - if (object1.immovable && object2.immovable) { - return false; - } - - // First, get the two object deltas - var overlap: number = 0; - var obj1Delta: number = object1.y - object1.last.y; - var obj2Delta: number = object2.y - object2.last.y; - - if (obj1Delta != obj2Delta) - { - // Check if the Y hulls actually overlap - var obj1DeltaAbs: number = (obj1Delta > 0) ? obj1Delta : -obj1Delta; - var obj2DeltaAbs: number = (obj2Delta > 0) ? obj2Delta : -obj2Delta; - var obj1Bounds: Quad = new Quad(object1.x, object1.y - ((obj1Delta > 0) ? obj1Delta : 0), object1.width, object1.height + obj1DeltaAbs); - var obj2Bounds: Quad = new Quad(object2.x, object2.y - ((obj2Delta > 0) ? obj2Delta : 0), object2.width, object2.height + obj2DeltaAbs); - - console.log(obj1Bounds.toString(), obj2Bounds.toString()); - - if ((obj1Bounds.x + obj1Bounds.width > obj2Bounds.x) && (obj1Bounds.x < obj2Bounds.x + obj2Bounds.width) && (obj1Bounds.y + obj1Bounds.height > obj2Bounds.y) && (obj1Bounds.y < obj2Bounds.y + obj2Bounds.height)) - { - var maxOverlap: number = obj1DeltaAbs + obj2DeltaAbs + Collision.OVERLAP_BIAS; - console.log('max33', maxOverlap, obj1Delta, obj2Delta, obj1DeltaAbs, obj2DeltaAbs); - - // If they did overlap (and can), figure out by how much and flip the corresponding flags - if (obj1Delta > obj2Delta) - { - overlap = object1.y + object1.height - object2.y; - - if ((overlap > maxOverlap) || !(object1.allowCollisions & Collision.DOWN) || !(object2.allowCollisions & Collision.UP)) - { - overlap = 0; - } - else - { - object1.touching |= Collision.DOWN; - object2.touching |= Collision.UP; - } - } - else if (obj1Delta < obj2Delta) - { - overlap = object1.y - object2.height - object2.y; - - if ((-overlap > maxOverlap) || !(object1.allowCollisions & Collision.UP) || !(object2.allowCollisions & Collision.DOWN)) - { - overlap = 0; - } - else - { - object1.touching |= Collision.UP; - object2.touching |= Collision.DOWN; - } - } - } - } - - // Then adjust their positions and velocities accordingly (if there was any overlap) - if (overlap != 0) - { - console.log('y overlap', overlap); - - var obj1Velocity: number = object1.velocity.y; - var obj2Velocity: number = object2.velocity.y; - - if (!object1.immovable && !object2.immovable) - { - overlap *= 0.5; - object1.y = object1.y - overlap; - object2.y += overlap; - - var obj1NewVelocity: number = Math.sqrt((obj2Velocity * obj2Velocity * object2.mass) / object1.mass) * ((obj2Velocity > 0) ? 1 : -1); - var obj2NewVelocity: number = Math.sqrt((obj1Velocity * obj1Velocity * object1.mass) / object2.mass) * ((obj1Velocity > 0) ? 1 : -1); - var average: number = (obj1NewVelocity + obj2NewVelocity) * 0.5; - obj1NewVelocity -= average; - obj2NewVelocity -= average; - object1.velocity.y = average + obj1NewVelocity * object1.elasticity; - object2.velocity.y = average + obj2NewVelocity * object2.elasticity; - } - else if (!object1.immovable) - { - object1.y = object1.y - overlap; - object1.velocity.y = obj2Velocity - obj1Velocity * object1.elasticity; - // This is special case code that handles things like horizontal moving platforms you can ride - if (object2.active && object2.moves && (obj1Delta > obj2Delta)) - { - object1.x += object2.x - object2.last.x; - } - console.log('y2', object1.y, object1.velocity.y); - } - else if (!object2.immovable) - { - object2.y += overlap; - object2.velocity.y = obj1Velocity - obj2Velocity * object2.elasticity; - // This is special case code that handles things like horizontal moving platforms you can ride - if (object1.active && object1.moves && (obj1Delta < obj2Delta)) - { - object2.x += object1.x - object1.last.x; - } - console.log('y3', object2.y, object2.velocity.y); - } - - return true; - } - else - { - return false; - } - } - /** * Returns the distance between the two given coordinates. * @param x1 The X value of the first coordinate @@ -1279,6 +1225,478 @@ module Phaser { return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); } + + + // SAT + + /** + * Flattens the specified array of points onto a unit vector axis, + * resulting in a one dimensional range of the minimum and + * maximum value on that axis. + * + * @param {Array.} points The points to flatten. + * @param {Vector} normal The unit vector axis to flatten on. + * @param {Array.} result An array. After calling this function, + * result[0] will be the minimum value, + * result[1] will be the maximum value. + */ + public static flattenPointsOn(points, normal, result) { + + var min = Number.MAX_VALUE; + var max = -Number.MAX_VALUE; + var len = points.length; + + for (var i = 0; i < len; i++) + { + // Get the magnitude of the projection of the point onto the normal + var dot = points[i].dot(normal); + if (dot < min) { min = dot; } + if (dot > max) { max = dot; } + } + + result[0] = min; result[1] = max; + + } + + /** + * Pool of Vectors used in calculations. + * + * @type {Array.} + */ + public static T_VECTORS: Vector2[]; + + /** + * Pool of Arrays used in calculations. + * + * @type {Array.>} + */ + public static T_ARRAYS; + + /** + * Check whether two convex clockwise polygons are separated by the specified + * axis (must be a unit vector). + * + * @param {Vector} aPos The position of the first polygon. + * @param {Vector} bPos The position of the second polygon. + * @param {Array.} aPoints The points in the first polygon. + * @param {Array.} bPoints The points in the second polygon. + * @param {Vector} axis The axis (unit sized) to test against. The points of both polygons + * will be projected onto this axis. + * @param {Response=} response A Response object (optional) which will be populated + * if the axis is not a separating axis. + * @return {boolean} true if it is a separating axis, false otherwise. If false, + * and a response is passed in, information about how much overlap and + * the direction of the overlap will be populated. + */ + public static isSeparatingAxis(aPos, bPos, aPoints, bPoints, axis, response?:Response = null): bool { + + var rangeA = Collision.T_ARRAYS.pop(); + var rangeB = Collision.T_ARRAYS.pop(); + + // Get the magnitude of the offset between the two polygons + var offsetV = Collision.T_VECTORS.pop().copyFrom(bPos).sub(aPos); + var projectedOffset = offsetV.dot(axis); + + // Project the polygons onto the axis. + Collision.flattenPointsOn(aPoints, axis, rangeA); + Collision.flattenPointsOn(bPoints, axis, rangeB); + + // Move B's range to its position relative to A. + rangeB[0] += projectedOffset; + rangeB[1] += projectedOffset; + + // Check if there is a gap. If there is, this is a separating axis and we can stop + if (rangeA[0] > rangeB[1] || rangeB[0] > rangeA[1]) + { + Collision.T_VECTORS.push(offsetV); + Collision.T_ARRAYS.push(rangeA); + Collision.T_ARRAYS.push(rangeB); + return true; + } + + // If we're calculating a response, calculate the overlap. + if (response) + { + var overlap = 0; + + // A starts further left than B + if (rangeA[0] < rangeB[0]) + { + response.aInB = false; + + // A ends before B does. We have to pull A out of B + if (rangeA[1] < rangeB[1]) + { + overlap = rangeA[1] - rangeB[0]; + response.bInA = false; + // B is fully inside A. Pick the shortest way out. + } + else + { + var option1 = rangeA[1] - rangeB[0]; + var option2 = rangeB[1] - rangeA[0]; + overlap = option1 < option2 ? option1 : -option2; + } + // B starts further left than A + } + else + { + response.bInA = false; + + // B ends before A ends. We have to push A out of B + if (rangeA[1] > rangeB[1]) + { + overlap = rangeA[0] - rangeB[1]; + response.aInB = false; + // A is fully inside B. Pick the shortest way out. + } + else + { + var option1 = rangeA[1] - rangeB[0]; + var option2 = rangeB[1] - rangeA[0]; + overlap = option1 < option2 ? option1 : -option2; + } + } + + // If this is the smallest amount of overlap we've seen so far, set it as the minimum overlap. + var absOverlap = Math.abs(overlap); + + if (absOverlap < response.overlap) + { + response.overlap = absOverlap; + response.overlapN.copyFrom(axis); + + if (overlap < 0) + { + response.overlapN.reverse(); + } + } + } + + Collision.T_VECTORS.push(offsetV); + Collision.T_ARRAYS.push(rangeA); + Collision.T_ARRAYS.push(rangeB); + + return false; + + } + + public static LEFT_VORNOI_REGION:number = -1; + public static MIDDLE_VORNOI_REGION:number = 0; + public static RIGHT_VORNOI_REGION:number = 1; + + /** + * Calculates which Vornoi region a point is on a line segment. + * It is assumed that both the line and the point are relative to (0, 0) + * + * | (0) | + * (-1) [0]--------------[1] (1) + * | (0) | + * + * @param {Vector} line The line segment. + * @param {Vector} point The point. + * @return {number} LEFT_VORNOI_REGION (-1) if it is the left region, + * MIDDLE_VORNOI_REGION (0) if it is the middle region, + * RIGHT_VORNOI_REGION (1) if it is the right region. + */ + public static vornoiRegion(line: Vector2, point: Vector2): number { + + var len2 = line.length2(); + var dp = point.dot(line); + + if (dp < 0) { return Collision.LEFT_VORNOI_REGION; } + else if (dp > len2) { return Collision.RIGHT_VORNOI_REGION; } + else { return Collision.MIDDLE_VORNOI_REGION; } + + } + + /** + * Check if two circles intersect. + * + * @param {Circle} a The first circle. + * @param {Circle} b The second circle. + * @param {Response=} response Response object (optional) that will be populated if + * the circles intersect. + * @return {boolean} true if the circles intersect, false if they don't. + */ + public static testCircleCircle(a: Circle, b: Circle, response?: Response = null): bool { + + var differenceV = Collision.T_VECTORS.pop().copyFrom(b.pos).sub(a.pos); + var totalRadius = a.radius + b.radius; + var totalRadiusSq = totalRadius * totalRadius; + var distanceSq = differenceV.length2(); + + if (distanceSq > totalRadiusSq) + { + // They do not intersect + Collision.T_VECTORS.push(differenceV); + return false; + } + + // They intersect. If we're calculating a response, calculate the overlap. + if (response) + { + var dist = Math.sqrt(distanceSq); + response.a = a; + response.b = b; + response.overlap = totalRadius - dist; + response.overlapN.copyFrom(differenceV.normalize()); + response.overlapV.copyFrom(differenceV).scale(response.overlap); + response.aInB = a.radius <= b.radius && dist <= b.radius - a.radius; + response.bInA = b.radius <= a.radius && dist <= a.radius - b.radius; + } + + Collision.T_VECTORS.push(differenceV); + return true; + + } + + /** + * Check if a polygon and a circle intersect. + * + * @param {Polygon} polygon The polygon. + * @param {Circle} circle The circle. + * @param {Response=} response Response object (optional) that will be populated if + * they interset. + * @return {boolean} true if they intersect, false if they don't. + */ + public static testPolygonCircle(polygon: Polygon, circle: Circle, response?: Response = null): bool { + + var circlePos = Collision.T_VECTORS.pop().copyFrom(circle.pos).sub(polygon.pos); + var radius = circle.radius; + var radius2 = radius * radius; + var points = polygon.points; + var len = points.length; + var edge = T_VECTORS.pop(); + var point = T_VECTORS.pop(); + + // For each edge in the polygon + for (var i = 0; i < len; i++) + { + var next = i === len - 1 ? 0 : i + 1; + var prev = i === 0 ? len - 1 : i - 1; + var overlap = 0; + var overlapN = null; + + // Get the edge + edge.copyFrom(polygon.edges[i]); + // Calculate the center of the cirble relative to the starting point of the edge + point.copyFrom(circlePos).sub(points[i]); + + // If the distance between the center of the circle and the point + // is bigger than the radius, the polygon is definitely not fully in + // the circle. + if (response && point.length2() > radius2) + { + response.aInB = false; + } + + // Calculate which Vornoi region the center of the circle is in. + var region = vornoiRegion(edge, point); + + if (region === Collision.LEFT_VORNOI_REGION) + { + // Need to make sure we're in the RIGHT_VORNOI_REGION of the previous edge. + edge.copyFrom(polygon.edges[prev]); + + // Calculate the center of the circle relative the starting point of the previous edge + var point2 = Collision.T_VECTORS.pop().copyFrom(circlePos).sub(points[prev]); + region = vornoiRegion(edge, point2); + + if (region === Collision.RIGHT_VORNOI_REGION) + { + // It's in the region we want. Check if the circle intersects the point. + var dist = point.length2(); + + if (dist > radius) + { + // No intersection + Collision.T_VECTORS.push(circlePos); + Collision.T_VECTORS.push(edge); + Collision.T_VECTORS.push(point); + Collision.T_VECTORS.push(point2); + return false; + } + else if (response) + { + // It intersects, calculate the overlap + response.bInA = false; + overlapN = point.normalize(); + overlap = radius - dist; + } + } + + Collision.T_VECTORS.push(point2); + + } + else if (region === Collision.RIGHT_VORNOI_REGION) + { + // Need to make sure we're in the left region on the next edge + edge.copyFrom(polygon.edges[next]); + + // Calculate the center of the circle relative to the starting point of the next edge + point.copyFrom(circlePos).sub(points[next]); + region = vornoiRegion(edge, point); + + if (region === Collision.LEFT_VORNOI_REGION) + { + // It's in the region we want. Check if the circle intersects the point. + var dist = point.length2(); + + if (dist > radius) + { + // No intersection + Collision.T_VECTORS.push(circlePos); + Collision.T_VECTORS.push(edge); + Collision.T_VECTORS.push(point); + return false; + } + else if (response) + { + // It intersects, calculate the overlap + response.bInA = false; + overlapN = point.normalize(); + overlap = radius - dist; + } + } + // MIDDLE_VORNOI_REGION + } + else + { + // Need to check if the circle is intersecting the edge, + // Change the edge into its "edge normal". + var normal = edge.perp().normalize(); + + // Find the perpendicular distance between the center of the + // circle and the edge. + var dist = point.dot(normal); + var distAbs = Math.abs(dist); + + // If the circle is on the outside of the edge, there is no intersection + if (dist > 0 && distAbs > radius) + { + Collision.T_VECTORS.push(circlePos); + Collision.T_VECTORS.push(normal); + Collision.T_VECTORS.push(point); + return false; + } + else if (response) + { + // It intersects, calculate the overlap. + overlapN = normal; + overlap = radius - dist; + // If the center of the circle is on the outside of the edge, or part of the + // circle is on the outside, the circle is not fully inside the polygon. + if (dist >= 0 || overlap < 2 * radius) + { + response.bInA = false; + } + } + } + + // If this is the smallest overlap we've seen, keep it. + // (overlapN may be null if the circle was in the wrong Vornoi region) + if (overlapN && response && Math.abs(overlap) < Math.abs(response.overlap)) + { + response.overlap = overlap; + response.overlapN.copyFrom(overlapN); + } + } + + // Calculate the final overlap vector - based on the smallest overlap. + if (response) + { + response.a = polygon; + response.b = circle; + response.overlapV.copyFrom(response.overlapN).scale(response.overlap); + } + + Collision.T_VECTORS.push(circlePos); + Collision.T_VECTORS.push(edge); + Collision.T_VECTORS.push(point); + + return true; + } + + /** + * Check if a circle and a polygon intersect. + * + * NOTE: This runs slightly slower than polygonCircle as it just + * runs polygonCircle and reverses everything at the end. + * + * @param {Circle} circle The circle. + * @param {Polygon} polygon The polygon. + * @param {Response=} response Response object (optional) that will be populated if + * they interset. + * @return {boolean} true if they intersect, false if they don't. + */ + public static testCirclePolygon(circle: Circle, polygon: Polygon, response?: Response = null): bool { + + var result = Collision.testPolygonCircle(polygon, circle, response); + + if (result && response) + { + // Swap A and B in the response. + var a = response.a; + var aInB = response.aInB; + response.overlapN.reverse(); + response.overlapV.reverse(); + response.a = response.b; + response.b = a; + response.aInB = response.bInA; + response.bInA = aInB; + } + + return result; + } + + /** + * Checks whether two convex, clockwise polygons intersect. + * + * @param {Polygon} a The first polygon. + * @param {Polygon} b The second polygon. + * @param {Response=} response Response object (optional) that will be populated if + * they interset. + * @return {boolean} true if they intersect, false if they don't. + */ + public static testPolygonPolygon(a: Polygon, b: Polygon, response?: Response = null): bool { + + var aPoints = a.points; + var aLen = aPoints.length; + var bPoints = b.points; + var bLen = bPoints.length; + + // If any of the edge normals of A is a separating axis, no intersection. + for (var i = 0; i < aLen; i++) + { + if (Collision.isSeparatingAxis(a.pos, b.pos, aPoints, bPoints, a.normals[i], response)) + { + return false; + } + } + + // If any of the edge normals of B is a separating axis, no intersection. + for (var i = 0; i < bLen; i++) + { + if (Collision.isSeparatingAxis(a.pos, b.pos, aPoints, bPoints, b.normals[i], response)) + { + return false; + } + } + + // Since none of the edge normals of A or B are a separating axis, there is an intersection + // and we've already calculated the smallest overlap (in isSeparatingAxis). Calculate the + // final overlap vector. + if (response) + { + response.a = a; + response.b = b; + response.overlapV.copyFrom(response.overlapN).scale(response.overlap); + } + + return true; + } + } } \ No newline at end of file diff --git a/Phaser/Game.ts b/Phaser/Game.ts index 76c1ec1d..a8befff3 100644 --- a/Phaser/Game.ts +++ b/Phaser/Game.ts @@ -6,6 +6,7 @@ /// /// /// +/// /// /// /// @@ -172,6 +173,12 @@ module Phaser { */ public onDestroyCallback = null; + /** + * Reference to the GameObject Factory. + * @type {GameObjectFactory} + */ + public add: GameObjectFactory; + /** * Reference to the assets cache. * @type {Cache} @@ -251,7 +258,7 @@ module Phaser { public rnd: RandomDataGenerator; /** - * Device detector. + * Contains device information and capabilities. * @type {Device} */ public device: Device; @@ -292,6 +299,7 @@ module Phaser { this.math = new GameMath(this); this.stage = new Stage(this, parent, width, height); this.world = new World(this, width, height); + this.add = new GameObjectFactory(this); this.sound = new SoundManager(this); this.cache = new Cache(this); this.collision = new Collision(this); @@ -619,125 +627,6 @@ module Phaser { } - // Handy Proxy methods - - /** - * Create a new camera with specific position and size. - * - * @param x {number} X position of the new camera. - * @param y {number} Y position of the new camera. - * @param width {number} Width of the new camera. - * @param height {number} Height of the new camera. - * @returns {Camera} The newly created camera object. - */ - public createCamera(x: number, y: number, width: number, height: number): Camera { - return this.world.createCamera(x, y, width, height); - } - - /** - * Create a new GeomSprite with specific position. - * - * @param x {number} X position of the new geom sprite. - * @param y {number} Y position of the new geom sprite. - * @returns {GeomSprite} The newly created geom sprite object. - */ - public createGeomSprite(x: number, y: number): GeomSprite { - return this.world.createGeomSprite(x, y); - } - - /** - * Create a new Sprite with specific position and sprite sheet key. - * - * @param x {number} X position of the new sprite. - * @param y {number} Y position of the new sprite. - * @param key {string} Optional, key for the sprite sheet you want it to use. - * @returns {Sprite} The newly created sprite object. - */ - public createSprite(x: number, y: number, key?: string = ''): Sprite { - return this.world.createSprite(x, y, key); - } - - /** - * Create a new DynamicTexture with specific size. - * - * @param width {number} Width of the texture. - * @param height {number} Height of the texture. - * @returns {DynamicTexture} The newly created dynamic texture object. - */ - public createDynamicTexture(width: number, height: number): DynamicTexture { - return this.world.createDynamicTexture(width, height); - } - - /** - * Create a new object container. - * - * @param maxSize {number} Optional, capacity of this group. - * @returns {Group} The newly created group. - */ - public createGroup(maxSize?: number = 0): Group { - return this.world.createGroup(maxSize); - } - - /** - * Create a new Particle. - * - * @return {Particle} The newly created particle object. - */ - public createParticle(): Particle { - return this.world.createParticle(); - } - - /** - * Create a new Emitter. - * - * @param x {number} Optional, x position of the emitter. - * @param y {number} Optional, y position of the emitter. - * @param size {number} Optional, size of this emitter. - * @return {Emitter} The newly created emitter object. - */ - public createEmitter(x?: number = 0, y?: number = 0, size?: number = 0): Emitter { - return this.world.createEmitter(x, y, size); - } - - /** - * Create a new ScrollZone object with image key, position and size. - * - * @param key {string} Key to a image you wish this object to use. - * @param x {number} X position of this object. - * @param y {number} Y position of this object. - * @param width number} Width of this object. - * @param height {number} Height of this object. - * @returns {ScrollZone} The newly created scroll zone object. - */ - public createScrollZone(key: string, x?: number = 0, y?: number = 0, width?: number = 0, height?: number = 0): ScrollZone { - return this.world.createScrollZone(key, x, y, width, height); - } - - /** - * Create a new Tilemap. - * - * @param key {string} Key for tileset image. - * @param mapData {string} Data of this tilemap. - * @param format {number} Format of map data. (Tilemap.FORMAT_CSV or Tilemap.FORMAT_TILED_JSON) - * @param [resizeWorld] {boolean} resize the world to make same as tilemap? - * @param [tileWidth] {number} width of each tile. - * @param [tileHeight] {number} height of each tile. - * @return {Tilemap} The newly created tilemap object. - */ - public createTilemap(key: string, mapData: string, format: number, resizeWorld: bool = true, tileWidth?: number = 0, tileHeight?: number = 0): Tilemap { - return this.world.createTilemap(key, mapData, format, resizeWorld, tileWidth, tileHeight); - } - - /** - * Create a tween object for a specific object. - * - * @param obj Object you wish the tween will affect. - * @return {Phaser.Tween} The newly created tween object. - */ - public createTween(obj): Tween { - return this.tweens.create(obj); - } - /** * Checks for overlaps between two objects using the world QuadTree. Can be GameObject vs. GameObject, GameObject vs. Group or Group vs. Group. * Note: Does not take the objects scrollFactor into account. All overlaps are check in world space. diff --git a/Phaser/GameMath.ts b/Phaser/GameMath.ts index 8138252b..1264e46f 100644 --- a/Phaser/GameMath.ts +++ b/Phaser/GameMath.ts @@ -1039,25 +1039,32 @@ module Phaser { } /** - * Rotates a point around the x/y coordinates given to the desired angle + * Rotates the point around the x/y coordinates given to the desired angle and distance + * @param point {Object} Any object with exposed x and y properties * @param x {number} The x coordinate of the anchor point * @param y {number} The y coordinate of the anchor point - * @param angle {number} The angle of the rotation in radians - * @param point {Point} The point object to perform the rotation on + * @param {Number} angle The angle in radians (unless asDegrees is true) to return the point from. + * @param {Boolean} asDegrees Is the given angle in radians (false) or degrees (true)? + * @param {Number} distance An optional distance constraint between the point and the anchor * @return The modified point object */ - public rotatePoint(x: number, y: number, angle: number, point) { + public rotatePoint(point, x1: number, y1: number, angle: number, asDegrees: bool = false, distance?:number = null) { - var s: number = Math.sin(angle); - var c: number = Math.cos(angle); + if (asDegrees) + { + angle = angle * GameMath.DEG_TO_RAD; + } - point.x -= x; - point.y -= y; + // Get distance from origin to the point + if (distance === null) + { + distance = Math.sqrt(((x1 - point.x) * (x1 - point.x)) + ((y1 - point.y) * (y1 - point.y))); + } - var newX: number = point.x * c - point.y * s; - var newY: number = point.x * s - point.y * c; + point.x = x1 + distance * Math.cos(angle); + point.y = y1 + distance * Math.sin(angle); - return point.setTo(newX + x, newY + y); + return point; } diff --git a/Phaser/GameObjectFactory.ts b/Phaser/GameObjectFactory.ts new file mode 100644 index 00000000..8be5beed --- /dev/null +++ b/Phaser/GameObjectFactory.ts @@ -0,0 +1,219 @@ +/// + +/** +* Phaser - GameObjectFactory +* +* A quick way to create new world objects and add existing objects to the current world. +*/ + +module Phaser { + + export class GameObjectFactory { + + /** + * GameObjectFactory constructor + * @param game {Game} A reference to the current Game. + */ + constructor(game: Phaser.Game) { + + this._game = game; + this._world = this._game.world; + + } + + /** + * Local private reference to Game + */ + private _game: Phaser.Game; + + /** + * Local private reference to World + */ + private _world: Phaser.World; + + /** + * Create a new camera with specific position and size. + * + * @param x {number} X position of the new camera. + * @param y {number} Y position of the new camera. + * @param width {number} Width of the new camera. + * @param height {number} Height of the new camera. + * @returns {Camera} The newly created camera object. + */ + public camera(x: number, y: number, width: number, height: number): Camera { + return this._world.createCamera(x, y, width, height); + } + + /** + * Create a new GeomSprite with specific position. + * + * @param x {number} X position of the new geom sprite. + * @param y {number} Y position of the new geom sprite. + * @returns {GeomSprite} The newly created geom sprite object. + */ + public geomSprite(x: number, y: number): GeomSprite { + return this._world.createGeomSprite(x, y); + } + + /** + * Create a new Sprite with specific position and sprite sheet key. + * + * @param x {number} X position of the new sprite. + * @param y {number} Y position of the new sprite. + * @param key {string} Optional, key for the sprite sheet you want it to use. + * @returns {Sprite} The newly created sprite object. + */ + public sprite(x: number, y: number, key?: string = ''): Sprite { + return this._world.createSprite(x, y, key); + } + + /** + * Create a new DynamicTexture with specific size. + * + * @param width {number} Width of the texture. + * @param height {number} Height of the texture. + * @returns {DynamicTexture} The newly created dynamic texture object. + */ + public dynamicTexture(width: number, height: number): DynamicTexture { + return this._world.createDynamicTexture(width, height); + } + + /** + * Create a new object container. + * + * @param maxSize {number} Optional, capacity of this group. + * @returns {Group} The newly created group. + */ + public group(maxSize?: number = 0): Group { + return this._world.createGroup(maxSize); + } + + /** + * Create a new Particle. + * + * @return {Particle} The newly created particle object. + */ + public particle(): Particle { + return this._world.createParticle(); + } + + /** + * Create a new Emitter. + * + * @param x {number} Optional, x position of the emitter. + * @param y {number} Optional, y position of the emitter. + * @param size {number} Optional, size of this emitter. + * @return {Emitter} The newly created emitter object. + */ + public emitter(x?: number = 0, y?: number = 0, size?: number = 0): Emitter { + return this._world.createEmitter(x, y, size); + } + + /** + * Create a new ScrollZone object with image key, position and size. + * + * @param key {string} Key to a image you wish this object to use. + * @param x {number} X position of this object. + * @param y {number} Y position of this object. + * @param width number} Width of this object. + * @param height {number} Height of this object. + * @returns {ScrollZone} The newly created scroll zone object. + */ + public scrollZone(key: string, x?: number = 0, y?: number = 0, width?: number = 0, height?: number = 0): ScrollZone { + return this._world.createScrollZone(key, x, y, width, height); + } + + /** + * Create a new Tilemap. + * + * @param key {string} Key for tileset image. + * @param mapData {string} Data of this tilemap. + * @param format {number} Format of map data. (Tilemap.FORMAT_CSV or Tilemap.FORMAT_TILED_JSON) + * @param [resizeWorld] {boolean} resize the world to make same as tilemap? + * @param [tileWidth] {number} width of each tile. + * @param [tileHeight] {number} height of each tile. + * @return {Tilemap} The newly created tilemap object. + */ + public tilemap(key: string, mapData: string, format: number, resizeWorld: bool = true, tileWidth?: number = 0, tileHeight?: number = 0): Tilemap { + return this._world.createTilemap(key, mapData, format, resizeWorld, tileWidth, tileHeight); + } + + /** + * Create a tween object for a specific object. + * + * @param obj Object you wish the tween will affect. + * @return {Phaser.Tween} The newly created tween object. + */ + public tween(obj): Tween { + return this._game.tweens.create(obj); + } + + /** + * Add an existing Sprite to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param sprite The Sprite to add to the Game World + * @return {Phaser.Sprite} The Sprite object + */ + public existingSprite(sprite: Sprite): Sprite { + return this._world.group.add(sprite); + } + + /** + * Add an existing GeomSprite to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param sprite The GeomSprite to add to the Game World + * @return {Phaser.GeomSprite} The GeomSprite object + */ + public existingGeomSprite(sprite: GeomSprite): GeomSprite { + return this._world.group.add(sprite); + } + + /** + * Add an existing Emitter to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param emitter The Emitter to add to the Game World + * @return {Phaser.Emitter} The Emitter object + */ + public existingEmitter(emitter: Emitter): Emitter { + return this._world.group.add(emitter); + } + + /** + * Add an existing ScrollZone to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param scrollZone The ScrollZone to add to the Game World + * @return {Phaser.ScrollZone} The ScrollZone object + */ + public existingScrollZone(scrollZone: ScrollZone): ScrollZone { + return this._world.group.add(scrollZone); + } + + /** + * Add an existing Tilemap to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param tilemap The Tilemap to add to the Game World + * @return {Phaser.Tilemap} The Tilemap object + */ + public existingTilemap(tilemap: Tilemap): Tilemap { + return this._world.group.add(tilemap); + } + + /** + * Add an existing Tween to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param tween The Tween to add to the Game World + * @return {Phaser.Tween} The Tween object + */ + public existingTween(tween: Tween): Tween { + return this._game.tweens.add(tween); + } + + } + +} diff --git a/Phaser/Loader.ts b/Phaser/Loader.ts index 0c25fd19..6b604c19 100644 --- a/Phaser/Loader.ts +++ b/Phaser/Loader.ts @@ -76,6 +76,12 @@ module Phaser { */ public progress: number; + /** + * The crossOrigin value applied to loaded images + * @type {string} + */ + public crossOrigin: string = ''; + /** * Reset loader, this will remove all loaded assets. */ @@ -280,6 +286,7 @@ module Phaser { file.data.name = file.key; file.data.onload = () => this.fileComplete(file.key); file.data.onerror = () => this.fileError(file.key); + file.data.crossOrigin = this.crossOrigin; file.data.src = file.url; break; diff --git a/Phaser/Phaser.csproj b/Phaser/Phaser.csproj index 4bce4c50..9e1c2a0e 100644 --- a/Phaser/Phaser.csproj +++ b/Phaser/Phaser.csproj @@ -79,6 +79,10 @@ GameMath.ts + + + GameObjectFactory.ts + Emitter.ts @@ -107,6 +111,14 @@ + + + Polygon.ts + + + + Response.ts + VerletManager.ts diff --git a/Phaser/Phaser.ts b/Phaser/Phaser.ts index 623bae7d..a2f663b4 100644 --- a/Phaser/Phaser.ts +++ b/Phaser/Phaser.ts @@ -1,7 +1,7 @@ /** * Phaser * -* v0.9.5 - April 28th 2013 +* v0.9.6 - May 21st 2013 * * A small and feature-packed 2D canvas game framework born from the firey pits of Flixel and Kiwi. * @@ -16,6 +16,6 @@ module Phaser { - export var VERSION: string = 'Phaser version 0.9.5'; + export var VERSION: string = 'Phaser version 0.9.6'; } diff --git a/Phaser/Stage.ts b/Phaser/Stage.ts index 24bb9b3e..38ee1305 100644 --- a/Phaser/Stage.ts +++ b/Phaser/Stage.ts @@ -195,23 +195,35 @@ module Phaser { { if (this._game.paused == false) { - this._pauseScreen.onPaused(); - this.saveCanvasValues(); - this._game.paused = true; + this.pauseGame(); } } else { if (this._game.paused == true) { - this._pauseScreen.onResume(); - this._game.paused = false; - this.restoreCanvasValues(); + this.resumeGame(); } } } + public pauseGame() { + + this._pauseScreen.onPaused(); + this.saveCanvasValues(); + this._game.paused = true; + + } + + public resumeGame() { + + this._pauseScreen.onResume(); + this.restoreCanvasValues(); + this._game.paused = false; + + } + /** * Get the DOM offset values of the given element */ diff --git a/Phaser/State.ts b/Phaser/State.ts index 329e18da..99800280 100644 --- a/Phaser/State.ts +++ b/Phaser/State.ts @@ -18,6 +18,7 @@ module Phaser { this.game = game; + this.add = game.add; this.camera = game.camera; this.cache = game.cache; this.collision = game.collision; @@ -56,6 +57,12 @@ module Phaser { */ public collision: Collision; + /** + * Reference to the GameObject Factory. + * @type {GameObjectFactory} + */ + public add: GameObjectFactory; + /** * Reference to the input manager * @type {Input} @@ -144,125 +151,6 @@ module Phaser { */ public destroy() { } - // Handy Proxy methods - - /** - * Create a new camera with specific position and size. - * - * @param x {number} X position of the new camera. - * @param y {number} Y position of the new camera. - * @param width {number} Width of the new camera. - * @param height {number} Height of the new camera. - * @returns {Camera} The newly created camera object. - */ - public createCamera(x: number, y: number, width: number, height: number): Camera { - return this.game.world.createCamera(x, y, width, height); - } - - /** - * Create a new GeomSprite with specific position. - * - * @param x {number} X position of the new geom sprite. - * @param y {number} Y position of the new geom sprite. - * @returns {GeomSprite} The newly created geom sprite object. - */ - public createGeomSprite(x: number, y: number): GeomSprite { - return this.world.createGeomSprite(x, y); - } - - /** - * Create a new Sprite with specific position and sprite sheet key. - * - * @param x {number} X position of the new sprite. - * @param y {number} Y position of the new sprite. - * @param key {string} [optional] key for the sprite sheet you want it to use. - * @returns {Sprite} The newly created sprite object. - */ - public createSprite(x: number, y: number, key?: string = ''): Sprite { - return this.game.world.createSprite(x, y, key); - } - - /** - * Create a new DynamicTexture with specific size. - * - * @param width {number} Width of the texture. - * @param height {number} Height of the texture. - * @returns {DynamicTexture} The newly created dynamic texture object. - */ - public createDynamicTexture(width: number, height: number): DynamicTexture { - return this.game.world.createDynamicTexture(width, height); - } - - /** - * Create a new object container. - * - * @param maxSize {number} [optional] capacity of this group. - * @returns {Group} The newly created group. - */ - public createGroup(maxSize?: number = 0): Group { - return this.game.world.createGroup(maxSize); - } - - /** - * Create a new Particle. - * - * @return {Particle} The newly created particle object. - */ - public createParticle(): Particle { - return this.game.world.createParticle(); - } - - /** - * Create a new Emitter. - * - * @param x {number} [optional] x position of the emitter. - * @param y {number} [optional] y position of the emitter. - * @param size {number} [optional] size of this emitter. - * @return {Emitter} The newly created emitter object. - */ - public createEmitter(x?: number = 0, y?: number = 0, size?: number = 0): Emitter { - return this.game.world.createEmitter(x, y, size); - } - - /** - * Create a new ScrollZone object with image key, position and size. - * - * @param key {string} Key to a image you wish this object to use. - * @param x {number} X position of this object. - * @param y {number} Y position of this object. - * @param width {number} Width of this object. - * @param height {number} Height of this object. - * @returns {ScrollZone} The newly created scroll zone object. - */ - public createScrollZone(key: string, x?: number = 0, y?: number = 0, width?: number = 0, height?: number = 0): ScrollZone { - return this.game.world.createScrollZone(key, x, y, width, height); - } - - /** - * Create a new Tilemap. - * - * @param key {string} Key for tileset image. - * @param mapData {string} Data of this tilemap. - * @param format {number} Format of map data. (Tilemap.FORMAT_CSV or Tilemap.FORMAT_TILED_JSON) - * @param resizeWorld {boolean} [optional] resize the world to make same as tilemap? - * @param tileWidth {number} [optional] width of each tile. - * @param tileHeight number} [optional] height of each tile. - * @return {Tilemap} The newly created tilemap object. - */ - public createTilemap(key: string, mapData: string, format: number, resizeWorld: bool = true, tileWidth?: number = 0, tileHeight?: number = 0): Tilemap { - return this.game.world.createTilemap(key, mapData, format, resizeWorld, tileWidth, tileHeight); - } - - /** - * Create a tween object for a specific object. - * - * @param obj Object you wish the tween will affect. - * @return {Phaser.Tween} The newly created tween object. - */ - public createTween(obj): Tween { - return this.game.tweens.create(obj); - } - /** * Call this method to see if one object collids another. * @return {boolean} Whether the given objects or groups collids. diff --git a/Phaser/TweenManager.ts b/Phaser/TweenManager.ts index f5cdb5c4..f6d1a897 100644 --- a/Phaser/TweenManager.ts +++ b/Phaser/TweenManager.ts @@ -30,6 +30,7 @@ module Phaser { * Local private reference to Game */ private _game: Phaser.Game; + /** * Local private array which is the container of all tween objects. */ diff --git a/Phaser/VerletManager.ts b/Phaser/VerletManager.ts index c9a60b69..8f4d1f52 100644 --- a/Phaser/VerletManager.ts +++ b/Phaser/VerletManager.ts @@ -42,12 +42,13 @@ module Phaser.Verlet { } private _game: Game; + private _v = new Phaser.Vector2; public composites = []; public width: number; public height: number; - public step: number = 32; + public step: number = 16; public gravity: Vector2; public friction: number; public groundFriction: number; @@ -55,15 +56,14 @@ module Phaser.Verlet { public draggedEntity = null; public highlightColor = '#4f545c'; - /** - * This class is actually a wrapper of canvas. + * A reference to the canvas this renders to * @type {HTMLCanvasElement} */ public canvas: HTMLCanvasElement; /** - * Canvas context of this object. + * A reference to the context this renders to * @type {CanvasRenderingContext2D} */ public context: CanvasRenderingContext2D; @@ -71,10 +71,10 @@ module Phaser.Verlet { /** * Computes time of intersection of a particle with a wall * - * @param {Vec2} line wall's root position - * @param {Vec2} p particle's position - * @param {Vec2} dir walls's direction - * @param {Vec2} v particle's velocity + * @param {Vec2} line walls root position + * @param {Vec2} p particle position + * @param {Vec2} dir walls direction + * @param {Vec2} v particles velocity */ public intersectionTime(wall, p, dir, v) { @@ -100,81 +100,56 @@ module Phaser.Verlet { return new Phaser.Vector2(p.x + v.x * t, p.y + v.y * t); } - private v = new Phaser.Vector2(); - public bounds(particle: Phaser.Verlet.Particle) { - this.v.mutableSet(particle.pos); - this.v.mutableSub(particle.lastPos); + this._v.mutableSet(particle.pos); + this._v.mutableSub(particle.lastPos); if (particle.pos.y > this.height - 1) { - particle.pos.mutableSet( - this.intersectionPoint(new Phaser.Vector2(0, this.height - 1), particle.lastPos, new Phaser.Vector2(1, 0), this.v)); + particle.pos.mutableSet(this.intersectionPoint(new Phaser.Vector2(0, this.height - 1), particle.lastPos, new Phaser.Vector2(1, 0), this._v)); } if (particle.pos.x < 0) { - particle.pos.mutableSet( - this.intersectionPoint(new Phaser.Vector2(0, 0), particle.pos, new Phaser.Vector2(0, 1), this.v)); + particle.pos.mutableSet(this.intersectionPoint(new Phaser.Vector2(0, 0), particle.pos, new Phaser.Vector2(0, 1), this._v)); } if (particle.pos.x > this.width - 1) { - particle.pos.mutableSet( - this.intersectionPoint(new Phaser.Vector2(this.width - 1, 0), particle.pos, new Phaser.Vector2(0, 1), this.v)); + particle.pos.mutableSet(this.intersectionPoint(new Phaser.Vector2(this.width - 1, 0), particle.pos, new Phaser.Vector2(0, 1), this._v)); } } - public OLDbounds(particle: Phaser.Verlet.Particle) { - - if (particle.pos.y > this.height - 1) - particle.pos.y = this.height - 1; - - if (particle.pos.x < 0) - { - var vx = particle.pos.x - particle.lastPos.x; - var vy = particle.pos.y - particle.lastPos.y; - - if (vx == 0) - { - particle.pos.x = 0; - } - else - { - var t = -particle.lastPos.x / vx; - particle.pos.x = particle.lastPos.x + t * vx; - particle.pos.y = particle.lastPos.y + t * vy; - } - } - - if (particle.pos.x > this.width - 1) - particle.pos.x = this.width - 1; - - } - public createPoint(pos: Vector2) { var composite = new Phaser.Verlet.Composite(this._game); + composite.particles.push(new Phaser.Verlet.Particle(pos)); + this.composites.push(composite); + return composite; } public createLineSegments(vertices, stiffness) { - var i; var composite = new Phaser.Verlet.Composite(this._game); + var i; for (i in vertices) { composite.particles.push(new Phaser.Verlet.Particle(vertices[i])); + if (i > 0) + { composite.constraints.push(new Phaser.Verlet.DistanceConstraint(composite.particles[i], composite.particles[i - 1], stiffness)); + } } this.composites.push(composite); + return composite; } @@ -186,30 +161,40 @@ module Phaser.Verlet { var xStride = width / segments; var yStride = height / segments; - var x, y; + var x; + var y; + for (y = 0; y < segments; ++y) { for (x = 0; x < segments; ++x) { var px = origin.x + x * xStride - width / 2 + xStride / 2; var py = origin.y + y * yStride - height / 2 + yStride / 2; + composite.particles.push(new Phaser.Verlet.Particle(new Vector2(px, py))); if (x > 0) + { composite.constraints.push(new Phaser.Verlet.DistanceConstraint(composite.particles[y * segments + x], composite.particles[y * segments + x - 1], stiffness)); + } if (y > 0) + { composite.constraints.push(new Phaser.Verlet.DistanceConstraint(composite.particles[y * segments + x], composite.particles[(y - 1) * segments + x], stiffness)); + } } } for (x = 0; x < segments; ++x) { if (x % pinMod == 0) + { composite.pin(x); + } } this.composites.push(composite); + return composite; } @@ -284,7 +269,9 @@ module Phaser.Verlet { // handle dragging of entities if (this.draggedEntity) - this.draggedEntity.pos.mutableSet(new Vector2(this._game.input.x, this._game.input.y)); + { + this.draggedEntity.pos.mutableSet(this._game.input.position); + } // relax var stepCoef = 1 / this.step; @@ -292,17 +279,25 @@ module Phaser.Verlet { for (c in this.composites) { var constraints = this.composites[c].constraints; + for (i = 0; i < this.step; ++i) + { for (j in constraints) + { constraints[j].relax(stepCoef); + } + } } // bounds checking for (c in this.composites) { var particles = this.composites[c].particles; + for (i in particles) + { this.bounds(particles[i]); + } } } @@ -335,7 +330,7 @@ module Phaser.Verlet { for (i in particles) { - var d2 = particles[i].pos.dist2(new Vector2(this._game.input.x, this._game.input.y)); + var d2 = particles[i].pos.distance2(this._game.input.position); if (d2 <= this.selectionRadius * this.selectionRadius && (entity == null || d2 < d2Nearest)) { @@ -348,13 +343,19 @@ module Phaser.Verlet { // search for pinned constraints for this entity for (i in constraintsNearest) + { if (constraintsNearest[i] instanceof PinConstraint && constraintsNearest[i].a == entity) + { entity = constraintsNearest[i]; + } + } return entity; } + public hideNearestEntityCircle: bool = false; + public render() { var i, c; @@ -365,34 +366,43 @@ module Phaser.Verlet { if (this.composites[c].drawConstraints) { this.composites[c].drawConstraints(this.context, this.composites[c]); - } else + } + else { var constraints = this.composites[c].constraints; + for (i in constraints) + { constraints[i].render(this.context); + } } // draw particles if (this.composites[c].drawParticles) { this.composites[c].drawParticles(this.context, this.composites[c]); - } else + } + else { var particles = this.composites[c].particles; + for (i in particles) + { particles[i].render(this.context); + } } } // highlight nearest / dragged entity var nearest = this.draggedEntity || this.nearestEntity(); - if (nearest) + if (nearest && this.hideNearestEntityCircle == false) { this.context.beginPath(); this.context.arc(nearest.pos.x, nearest.pos.y, 8, 0, 2 * Math.PI); this.context.strokeStyle = this.highlightColor; this.context.stroke(); + this.context.closePath(); } } diff --git a/Phaser/World.ts b/Phaser/World.ts index 1f8abfc0..d490d497 100644 --- a/Phaser/World.ts +++ b/Phaser/World.ts @@ -101,13 +101,14 @@ module Phaser { /** * Update size of this world with specific width and height. - * You can choose update camera bounds automatically or not. + * You can choose update camera bounds and verlet manager automatically or not. * * @param width {number} New width of the world. * @param height {number} New height of the world. * @param [updateCameraBounds] {boolean} update camera bounds automatically or not. Default to true. + * @param [updateVerletBounds] {boolean} update verlet bounds automatically or not. Default to true. */ - public setSize(width: number, height: number, updateCameraBounds: bool = true) { + public setSize(width: number, height: number, updateCameraBounds: bool = true, updateVerletBounds: bool = true) { this.bounds.width = width; this.bounds.height = height; @@ -117,6 +118,12 @@ module Phaser { this._game.camera.setBounds(0, 0, width, height); } + if (updateVerletBounds == true) + { + this._game.verlet.width = width; + this._game.verlet.height = height; + } + } public get width(): number { diff --git a/Phaser/gameobjects/GameObject.ts b/Phaser/gameobjects/GameObject.ts index 9ddf6559..b4a945bd 100644 --- a/Phaser/gameobjects/GameObject.ts +++ b/Phaser/gameobjects/GameObject.ts @@ -835,6 +835,12 @@ module Phaser { * Clean up memory. */ public destroy() { + } + + public setPosition(x: number, y: number) { + + this.x = x; + this.y = y; } diff --git a/Phaser/gameobjects/GeomSprite.ts b/Phaser/gameobjects/GeomSprite.ts index 4c68c2ba..053c3dd6 100644 --- a/Phaser/gameobjects/GeomSprite.ts +++ b/Phaser/gameobjects/GeomSprite.ts @@ -1,4 +1,5 @@ /// +/// /** * Phaser - GeomSprite @@ -71,6 +72,12 @@ module Phaser { */ public static RECTANGLE: number = 4; + /** + * Polygon. + * @type {number} + */ + public static POLYGON: number = 5; + /** * Circle shape container. A Circle instance. * @type {Circle} @@ -95,6 +102,12 @@ module Phaser { */ public rect: Rectangle; + /** + * Polygon shape container. A Polygon instance. + * @type {Polygon} + */ + public polygon: Polygon; + /** * Render outline of this sprite or not. (default is true) * @type {boolean} @@ -243,6 +256,22 @@ module Phaser { } + /** + * Create a polygon object + * @param width {Number} Width of the rectangle + * @param height {Number} Height of the rectangle + * @return {GeomSprite} GeomSprite instance. + */ + createPolygon(points?: Vector2[] = []): GeomSprite { + + this.refresh(); + this.polygon = new Polygon(new Vector2(this.x, this.y), points); + this.type = GeomSprite.POLYGON; + //this.frameBounds.copyFrom(this.rect); + return this; + + } + /** * Destroy all geom shapes of this sprite. */ diff --git a/Phaser/gameobjects/Sprite.ts b/Phaser/gameobjects/Sprite.ts index 8563fd95..c4a4eff7 100644 --- a/Phaser/gameobjects/Sprite.ts +++ b/Phaser/gameobjects/Sprite.ts @@ -109,9 +109,15 @@ module Phaser { /** * Load graphic for this sprite. (graphic can be SpriteSheet or Texture) * @param key {string} Key of the graphic you want to load for this sprite. + * @param clearAnimations {boolean} If this Sprite has a set of animation data already loaded you can choose to keep or clear it with this boolean * @return {Sprite} Sprite instance itself. */ - public loadGraphic(key: string): Sprite { + public loadGraphic(key: string, clearAnimations: bool = true): Sprite { + + if (clearAnimations && this.animations.frameData !== null) + { + this.animations.destroy(); + } if (this._game.cache.getImage(key) !== null) { @@ -127,8 +133,8 @@ module Phaser { { this._texture = this._game.cache.getImage(key); this.animations.loadFrameData(this._game.cache.getFrameData(key)); - //this.collisionMask.width = this._texture.width; - //this.collisionMask.height = this._texture.height; + this.collisionMask.width = this.animations.currentFrame.width; + this.collisionMask.height = this.animations.currentFrame.height; } this._dynamicTexture = false; @@ -181,19 +187,27 @@ module Phaser { */ public inCamera(camera: Rectangle): bool { - if (this.scrollFactor.x !== 1.0 || this.scrollFactor.y !== 1.0) + // Object fixed in place regardless of the camera scrolling? Then it's always visible + if (this.scrollFactor.x == 0 && this.scrollFactor.y == 0) { - this._dx = this.frameBounds.x - (camera.x * this.scrollFactor.x); - this._dy = this.frameBounds.y - (camera.y * this.scrollFactor.x); + return true; + } + + // Otherwise, if it's scrolling perfectly in sync with the camera (1 to 1) then it's a simple bounds check on world coordinates + if (this.scrollFactor.x == 1 && this.scrollFactor.y == 1) + { + return camera.intersects(this.frameBounds, this.frameBounds.length); + } + else + { + // Else apply the offsets + this._dx = (this.frameBounds.x - camera.x) * this.scrollFactor.x; + this._dy = (this.frameBounds.y - camera.y) * this.scrollFactor.y; this._dw = this.frameBounds.width * this.scale.x; this._dh = this.frameBounds.height * this.scale.y; return (camera.right > this._dx) && (camera.x < this._dx + this._dw) && (camera.bottom > this._dy) && (camera.y < this._dy + this._dh); } - else - { - return camera.intersects(this.frameBounds, this.frameBounds.length); - } } @@ -250,8 +264,9 @@ module Phaser { this._sy = 0; this._sw = this.frameBounds.width; this._sh = this.frameBounds.height; - this._dx = cameraOffsetX + (this.frameBounds.topLeft.x - camera.worldView.x); - this._dy = cameraOffsetY + (this.frameBounds.topLeft.y - camera.worldView.y); + + this._dx = (cameraOffsetX * this.scrollFactor.x) + this.frameBounds.topLeft.x - (camera.worldView.x * this.scrollFactor.x); + this._dy = (cameraOffsetY * this.scrollFactor.y) + this.frameBounds.topLeft.y - (camera.worldView.y * this.scrollFactor.y); this._dw = this.frameBounds.width * this.scale.x; this._dh = this.frameBounds.height * this.scale.y; @@ -305,10 +320,10 @@ module Phaser { } // Apply camera difference - if (this.scrollFactor.x !== 1.0 || this.scrollFactor.y !== 1.0) + if (this.scrollFactor.x !== 1 || this.scrollFactor.y !== 1) { - this._dx -= (camera.worldView.x * this.scrollFactor.x); - this._dy -= (camera.worldView.y * this.scrollFactor.y); + //this._dx -= (camera.worldView.x * this.scrollFactor.x); + //this._dy -= (camera.worldView.y * this.scrollFactor.y); } // Rotation - needs to work from origin point really, but for now from center diff --git a/Phaser/geom/Circle.ts b/Phaser/geom/Circle.ts index f162c179..348096bb 100644 --- a/Phaser/geom/Circle.ts +++ b/Phaser/geom/Circle.ts @@ -21,12 +21,14 @@ module Phaser { **/ constructor(x: number = 0, y: number = 0, diameter: number = 0) { + this._pos = new Vector2; this.setTo(x, y, diameter); } private _diameter: number = 0; private _radius: number = 0; + private _pos: Vector2; /** * The x coordinate of the center of the circle @@ -42,15 +44,22 @@ module Phaser { **/ public y: number = 0; + /** + * The position of this Circle object represented by a Vector2 + * @property pos + * @type Vector2 + **/ + public get pos(): Vector2 { + return this._pos.setTo(this.x, this.y); + } + /** * The diameter of the circle. The largest distance between any two points on the circle. The same as the radius * 2. * @method diameter * @return {Number} **/ get diameter(): number { - return this._diameter; - } /** diff --git a/Phaser/geom/Polygon.ts b/Phaser/geom/Polygon.ts new file mode 100644 index 00000000..cef1b05c --- /dev/null +++ b/Phaser/geom/Polygon.ts @@ -0,0 +1,60 @@ +/// + +/** +* Phaser - Polygon +* +*/ + +module Phaser { + + export class Polygon { + + /** + * A *convex* clockwise polygon + * @class Polygon + * @constructor + * @param {Vector2} pos A vector representing the origin of the polygon (all other points are relative to this one) + * @param {Array.} points An Array of vectors representing the points in the polygon, in clockwise order. + **/ + constructor(pos?: Vector2 = new Vector2, points?: Vector2[] = [], parent?:any = null) { + + this.pos = pos; + this.points = points; + this.parent = parent; + this.recalc(); + + } + + public parent: any; + public pos: Vector2; + public points: Vector2[]; + public edges: Vector2[]; + public normals: Vector2[]; + + /** + * Recalculate the edges and normals of the polygon. This + * MUST be called if the points array is modified at all and + * the edges or normals are to be accessed. + */ + public recalc() { + + var points = this.points; + var len = points.length; + + this.edges = []; + this.normals = []; + + for (var i = 0; i < len; i++) + { + var p1 = points[i]; + var p2 = i < len - 1 ? points[i + 1] : points[0]; + var e = new Vector2().copyFrom(p2).sub(p1); + var n = new Vector2().copyFrom(e).perp().normalize(); + this.edges.push(e); + this.normals.push(n); + } + } + + } + +} \ No newline at end of file diff --git a/Phaser/geom/Quad.ts b/Phaser/geom/Quad.ts index d6cb7aa0..4008c7a5 100644 --- a/Phaser/geom/Quad.ts +++ b/Phaser/geom/Quad.ts @@ -1,4 +1,5 @@ /// +/// /** * Phaser - Quad @@ -90,6 +91,22 @@ module Phaser { } + /** + * Determines whether the object specified intersects (overlaps) with the given values. + * @method intersectsProps + * @param {Number} left + * @param {Number} right + * @param {Number} top + * @param {Number} bottomt + * @param {Number} tolerance A tolerance value to allow for an intersection test with padding, default to 0 + * @return {Boolean} A value of true if the specified object intersects with this Quad; otherwise false. + **/ + public intersectsRaw(left: number, right: number, top: number, bottom: number, tolerance?: number = 0): bool { + + return !(left > this.right + tolerance || right < this.left - tolerance || top > this.bottom + tolerance || bottom < this.top - tolerance); + + } + /** * Determines whether the specified coordinates are contained within the region defined by this Quad object. * @method contains @@ -133,6 +150,20 @@ module Phaser { } + /** + * Creates and returns a Polygon that is the same as this Quad. + * @method toPolygon + * @return {Polygon} A new Polygon that represents this quad. + **/ + public toPolygon(): Polygon { + + return new Polygon(new Vector2(this.x, this.y), [ + new Vector2(), new Vector2(this.width, 0), + new Vector2(this.width, this.height), new Vector2(0, this.height) + ]); + + } + /** * Returns a string representation of this object. * @method toString diff --git a/Phaser/geom/Response.ts b/Phaser/geom/Response.ts new file mode 100644 index 00000000..563f25cb --- /dev/null +++ b/Phaser/geom/Response.ts @@ -0,0 +1,87 @@ +/// +/// + +/** +* Phaser - Response +* +*/ + +module Phaser { + + export class Response { + + /** + * An object representing the result of an intersection. Contain information about: + * - The two objects participating in the intersection + * - The vector representing the minimum change necessary to extract the first object + * from the second one. + * - Whether the first object is entirely inside the second, or vice versa. + * + * @constructor + */ + constructor() { + + this.a = null; + this.b = null; + this.overlapN = new Vector2; + this.overlapV = new Vector2; + + this.clear(); + + } + + /** + * The first object in the collision + */ + public a; + + /** + * The second object in the collision + */ + public b; + + /** + * The shortest colliding axis (unit-vector) + */ + public overlapN: Vector2; + + /** + * The overlap vector (i.e. overlapN.scale(overlap, overlap)). + * If this vector is subtracted from the position of `a`, `a` and `b` will no longer be colliding. + */ + public overlapV: Vector2; + + /** + * Whether the first object is completely inside the second. + */ + public aInB: bool; + + /** + * Whether the second object is completely inside the first. + */ + public bInA: bool; + + /** + * Magnitude of the overlap on the shortest colliding axis + */ + public overlap: number; + + /** + * Set some values of the response back to their defaults. Call this between tests if + * you are going to reuse a single Response object for multiple intersection tests (recommented) + * + * @return {Response} This for chaining + */ + public clear() { + + this.aInB = true; + this.bInA = true; + this.overlap = Number.MAX_VALUE; + + return this; + + } + + } + +} \ No newline at end of file diff --git a/Phaser/geom/Vector2.ts b/Phaser/geom/Vector2.ts index 7ea3d62e..91da7f43 100644 --- a/Phaser/geom/Vector2.ts +++ b/Phaser/geom/Vector2.ts @@ -3,7 +3,8 @@ /** * Phaser - Vector2 * -* A simple 2-dimensional vector class. Based on the one included with verlet-js by Sub Protocol released under MIT +* A two dimensional vector. +* Contains methods and ideas from verlet-js by Sub Protocol, SAT.js by Jim Riecken and N by Metanet Software. */ module Phaser { @@ -14,8 +15,8 @@ module Phaser { * Creates a new Vector2 object. * @class Vector2 * @constructor - * @param {Number} x The x coordinate of vector2 - * @param {Number} y The y coordinate of vector2 + * @param {Number} x The x position of the vector + * @param {Number} y The y position of the vector * @return {Vector2} This object **/ constructor(x: number = 0, y: number = 0) { @@ -34,62 +35,163 @@ module Phaser { return this; } + /** + * Add this vector to the given one and return the result. + * + * @param {Vector2} v The other Vector. + * @param {Vector2} The output Vector. + * @return {Vector2} The new Vector + */ public add(v: Vector2, output?:Vector2 = new Vector2): Vector2 { return output.setTo(this.x + v.x, this.y + v.y); } + /** + * Subtract this vector to the given one and return the result. + * + * @param {Vector2} v The other Vector. + * @param {Vector2} The output Vector. + * @return {Vector2} The new Vector + */ public sub(v: Vector2, output?:Vector2 = new Vector2): Vector2 { return output.setTo(this.x - v.x, this.y - v.y); } + /** + * Multiply this vector with the given one and return the result. + * + * @param {Vector2} v The other Vector. + * @param {Vector2} The output Vector. + * @return {Vector2} The new Vector + */ public mul(v: Vector2, output?:Vector2 = new Vector2): Vector2 { return output.setTo(this.x * v.x, this.y * v.y); } + /** + * Divide this vector by the given one and return the result. + * + * @param {Vector2} v The other Vector. + * @param {Vector2} The output Vector. + * @return {Vector2} The new Vector + */ public div(v: Vector2, output?:Vector2 = new Vector2): Vector2 { return output.setTo(this.x / v.x, this.y / v.y); } - public scale(coef: number, output?:Vector2 = new Vector2): Vector2 { - return output.setTo(this.x * coef, this.y * coef); + /** + * Scale this vector by the given values and return the result. + * + * @param {number} x The scaling factor in the x direction. + * @param {?number=} y The scaling factor in the y direction. If this + * is not specified, the x scaling factor will be used. + * @return {Vector} The new Vector + */ + public scale(x: number, y?:number = null, output?:Vector2 = new Vector2): Vector2 { + + if (y === null) + { + y = x; + } + + return output.setTo(this.x * x, this.y * y); + } + /** + * Rotate this vector by 90 degrees + * + * @return {Vector} This for chaining. + */ + public perp(output?: Vector2 = this): Vector2 { + var x = this.x; + return output.setTo(this.y, -x); + } + + // Same as copyFrom, used by VerletManager public mutableSet(v: Vector2): Vector2 { this.x = v.x; this.y = v.y; return this; } + /** + * Add another vector to this one. + * + * @param {Vector} other The other Vector. + * @return {Vector} This for chaining. + */ public mutableAdd(v: Vector2): Vector2 { this.x += v.x; this.y += v.y; return this; } + /** + * Subtract another vector from this one. + * + * @param {Vector} other The other Vector. + * @return {Vector} This for chaining. + */ public mutableSub(v: Vector2): Vector2 { this.x -= v.x; this.y -= v.y; return this; } + /** + * Multiply another vector with this one. + * + * @param {Vector} other The other Vector. + * @return {Vector} This for chaining. + */ public mutableMul(v: Vector2): Vector2 { this.x *= v.x; this.y *= v.y; return this; } + /** + * Divide this vector by another one. + * + * @param {Vector} other The other Vector. + * @return {Vector} This for chaining. + */ public mutableDiv(v: Vector2): Vector2 { this.x /= v.x; this.y /= v.y; return this; } - public mutableScale(coef: number): Vector2 { - this.x *= coef; - this.y *= coef; + /** + * Scale this vector. + * + * @param {number} x The scaling factor in the x direction. + * @param {?number=} y The scaling factor in the y direction. If this + * is not specified, the x scaling factor will be used. + * @return {Vector} This for chaining. + */ + public mutableScale(x: number, y?:number): Vector2 { + this.x *= x; + this.y *= y || x; return this; } + /** + * Reverse this vector. + * + * @return {Vector} This for chaining. + */ + public reverse(): Vector2 { + this.x = -this.x; + this.y = -this.y; + return this; + } + + public edge(v: Vector2, output?: Vector2 = new Vector2): Vector2 { + return this.sub(v, output); + } + public equals(v: Vector2): bool { return this.x == v.x && this.y == v.y; } @@ -98,31 +200,190 @@ module Phaser { return Math.abs(this.x - v.x) <= epsilon && Math.abs(this.y - v.y) <= epsilon; } + /** + * Get the length of this vector. + * + * @return {number} The length of this vector. + */ public length(): number { - return Math.sqrt(this.x * this.x + this.y * this.y); + return Math.sqrt((this.x * this.x) + (this.y * this.y)); } + /** + * Get the length^2 of this vector. + * + * @return {number} The length^2 of this vector. + */ public length2(): number { - return this.x * this.x + this.y * this.y; + return (this.x * this.x) + (this.y * this.y); } - public dist(v: Vector2): number { - return Math.sqrt(this.dist2(v)); + /** + * Get the distance between this vector and the given vector. + * + * @return {Vector2} v The vector to check + */ + public distance(v: Vector2): number { + return Math.sqrt(this.distance2(v)); } - public dist2(v: Vector2): number { + /** + * Get the distance^2 between this vector and the given vector. + * + * @return {Vector2} v The vector to check + */ + public distance2(v: Vector2): number { return ((v.x - this.x) * (v.x - this.x)) + ((v.y - this.y) * (v.y - this.y)); } - public normal(output?: Vector2 = new Vector2) { - var m = Math.sqrt(this.x * this.x + this.y * this.y); - return output.setTo(this.x / m, this.y / m); + /** + * Project this vector on to another vector. + * + * @param {Vector} other The vector to project onto. + * @return {Vector} This for chaining. + */ + public project(other: Vector2): Vector2 { + + var amt = this.dot(other) / other.length2(); + + if (amt != 0) + { + this.x = amt * other.x; + this.y = amt * other.y; + } + + return this; + } + /** + * Project this vector onto a vector of unit length. + * + * @param {Vector} other The unit vector to project onto. + * @return {Vector} This for chaining. + */ + public projectN(other: Vector2): Vector2 { + + var amt = this.dot(other); + + if (amt != 0) + { + this.x = amt * other.x; + this.y = amt * other.y; + } + + return this; + + } + + /** + * Reflect this vector on an arbitrary axis. + * + * @param {Vector} axis The vector representing the axis. + * @return {Vector} This for chaining. + */ + public reflect(axis): Vector2 { + + var x = this.x; + var y = this.y; + this.project(axis).scale(2); + this.x -= x; + this.y -= y; + + return this; + + } + + /** + * Reflect this vector on an arbitrary axis (represented by a unit vector) + * + * @param {Vector} axis The unit vector representing the axis. + * @return {Vector} This for chaining. + */ + public reflectN(axis): Vector2 { + + var x = this.x; + var y = this.y; + this.projectN(axis).scale(2); + this.x -= x; + this.y -= y; + + return this; + + } + + public getProjectionMagnitude(v: Vector2): number { + + var den = v.dot(v); + + if (den == 0) + { + return 0; + } + else + { + return Math.abs(this.dot(v) / den); + } + + } + + public direction(output?: Vector2 = new Vector2): Vector2 { + + output.copyFrom(this); + return this.normalize(output); + + } + + public normalRightHand(output?: Vector2 = this): Vector2 { + return output.setTo(this.y * -1, this.x); + } + + /** + * Normalize (make unit length) this vector. + * + * @return {Vector} This for chaining. + */ + public normalize(output?: Vector2 = this): Vector2 { + + var m = this.length(); + + if (m != 0) + { + output.setTo(this.x / m, this.y / m); + } + + return output; + } + + public getMagnitude(): number { + return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2)); + } + + /** + * Get the dot product of this vector against another. + * + * @param {Vector} other The vector to dot this one against. + * @return {number} The dot product. + */ public dot(v: Vector2): number { - return this.x * v.x + this.y * v.y; + return ((this.x * v.x) + (this.y * v.y)); } + /** + * Get the cross product of this vector against another. + * + * @param {Vector} other The vector to cross this one against. + * @return {number} The cross product. + */ + public cross(v: Vector2): number { + return ((this.x * v.y) - (this.y * v.x)); + } + + /** + * Get the angle between this vector and the given vector. + * + * @return {Vector2} v The vector to check + */ public angle(v: Vector2): number { return Math.atan2(this.x * v.y - this.y * v.x, this.x * v.x + this.y * v.y); } @@ -131,12 +392,31 @@ module Phaser { return vLeft.sub(this).angle(vRight.sub(this)); } + /** + * Rotate this vector around the origin to the given angle (theta) and return the result in a new Vector + * + * @return {Vector2} v The vector to check + */ public rotate(origin, theta, output?: Vector2 = new Vector2): Vector2 { var x = this.x - origin.x; var y = this.y - origin.y; return output.setTo(x * Math.cos(theta) - y * Math.sin(theta) + origin.x, x * Math.sin(theta) + y * Math.cos(theta) + origin.y); } + public clone(output?: Vector2 = new Vector2): Vector2 { + return output.setTo(this.x, this.y); + } + + public copyFrom(v: Vector2): Vector2 { + this.x = v.x; + this.y = v.y; + return this; + } + + public copyTo(v: Vector2): Vector2 { + return v.setTo(this.x, this.y); + } + /** * Returns a string representation of this object. * @method toString diff --git a/Phaser/phaser.js b/Phaser/phaser.js index d2fe1029..9d6e7b54 100644 --- a/Phaser/phaser.js +++ b/Phaser/phaser.js @@ -1,7 +1,7 @@ /** * Phaser * -* v0.9.5 - April 28th 2013 +* v0.9.6 - May 21st 2013 * * A small and feature-packed 2D canvas game framework born from the firey pits of Flixel and Kiwi. * @@ -15,5 +15,5 @@ */ var Phaser; (function (Phaser) { - Phaser.VERSION = 'Phaser version 0.9.5'; + Phaser.VERSION = 'Phaser version 0.9.6'; })(Phaser || (Phaser = {})); diff --git a/Phaser/system/Camera.ts b/Phaser/system/Camera.ts index 029bf999..e7a585c6 100644 --- a/Phaser/system/Camera.ts +++ b/Phaser/system/Camera.ts @@ -318,7 +318,7 @@ module Phaser { { if (this.deadzone == null) { - this.focusOnXY(this._target.x + this._target.origin.x, this._target.y + this._target.origin.y); + this.focusOnXY(this._target.x, this._target.y); } else { diff --git a/Phaser/system/CollisionMask.ts b/Phaser/system/CollisionMask.ts index 61ae360d..f0186ae0 100644 --- a/Phaser/system/CollisionMask.ts +++ b/Phaser/system/CollisionMask.ts @@ -155,8 +155,6 @@ module Phaser { public update() { - //this.quad.x = this._parent.x + this.offset.x; - //this.quad.y = this._parent.y + this.offset.y; this._ref.x = this._parent.x + this.offset.x; this._ref.y = this._parent.y + this.offset.y; @@ -173,8 +171,7 @@ module Phaser { var _dx = cameraOffsetX + (this.x - camera.worldView.x); var _dy = cameraOffsetY + (this.y - camera.worldView.y); - //this._parent.context.fillStyle = this._parent.renderDebugColor; - this._parent.context.fillStyle = 'rgba(255,0,0,0.4)'; + this._parent.context.fillStyle = this._parent.renderDebugColor; if (this.type == CollisionMask.QUAD) { @@ -207,6 +204,23 @@ module Phaser { } + public intersectsRaw(left: number, right: number, top: number, bottom: number): bool { + +//if ((objBounds.x + objBounds.width > x) && (objBounds.x < x + width) && (objBounds.y + objBounds.height > y) && (objBounds.y < y + height)) + + return true; + + } + + public intersectsVector(vector: Phaser.Vector2): bool { + + if (this.type == CollisionMask.QUAD) + { + return this.quad.contains(vector.x, vector.y); + } + + } + /** * Gives a basic boolean response to a geometric collision. * If you need the details of the collision use the Collision functions instead and inspect the IntersectResult object. @@ -224,7 +238,6 @@ module Phaser { // Circle vs. Circle if (this.type == CollisionMask.CIRCLE && source.type == CollisionMask.CIRCLE) { - console.log('c vs c'); return Collision.circleToCircle(this.circle, source.circle).result; } diff --git a/Phaser/system/input/Input.ts b/Phaser/system/input/Input.ts index a71725ab..3449a437 100644 --- a/Phaser/system/input/Input.ts +++ b/Phaser/system/input/Input.ts @@ -25,11 +25,6 @@ module Phaser { this.pointer3 = new Pointer(this._game, 3); this.pointer4 = new Pointer(this._game, 4); this.pointer5 = new Pointer(this._game, 5); - this.pointer6 = new Pointer(this._game, 6); - this.pointer7 = new Pointer(this._game, 7); - this.pointer8 = new Pointer(this._game, 8); - this.pointer9 = new Pointer(this._game, 9); - this.pointer10 = new Pointer(this._game, 10); this.mouse = new Mouse(this._game); this.keyboard = new Keyboard(this._game); @@ -42,7 +37,7 @@ module Phaser { this.onTap = new Phaser.Signal(); this.onHold = new Phaser.Signal(); - this.point = new Point(); + this.position = new Vector2; this.circle = new Circle(0, 0, 44); this.currentPointers = 0; @@ -115,11 +110,11 @@ module Phaser { public gestures: Gestures; /** - * A Point object representing the x/y screen coordinates of the Pointer. - * @property point - * @type {Point} + * A vector object representing the current position of the Pointer. + * @property vector + * @type {Vector2} **/ - public point: Point = null; + public position: Vector2 = null; /** * A Circle object centered on the x/y screen coordinates of the Input. @@ -299,35 +294,35 @@ module Phaser { * @property pointer6 * @type {Pointer} **/ - public pointer6: Pointer; + public pointer6: Pointer = null; /** * A Pointer object * @property pointer7 * @type {Pointer} **/ - public pointer7: Pointer; + public pointer7: Pointer = null; /** * A Pointer object * @property pointer8 * @type {Pointer} **/ - public pointer8: Pointer; + public pointer8: Pointer = null; /** * A Pointer object * @property pointer9 * @type {Pointer} **/ - public pointer9: Pointer; + public pointer9: Pointer = null; /** * A Pointer object * @property pointer10 * @type {Pointer} **/ - public pointer10: Pointer; + public pointer10: Pointer = null; /** * The screen X coordinate @@ -363,6 +358,58 @@ module Phaser { } + /** + * Add a new Pointer object to the Input Manager. By default Input creates 5 pointer objects for you. If you need more + * use this to create a new one, up to a maximum of 10. + * @method addPointer + * @return {Pointer} A reference to the new Pointer object + **/ + public addPointer(): Pointer { + + var next: number = 0; + + if (this.pointer10 === null) + { + next = 10; + } + + if (this.pointer9 === null) + { + next = 9; + } + + if (this.pointer8 === null) + { + next = 8; + } + + if (this.pointer7 === null) + { + next = 7; + } + + if (this.pointer6 === null) + { + next = 6; + } + + if (next == 0) + { + throw new Error("You can only have 10 Pointer objects"); + return null; + } + else + { + this['pointer' + next] = new Pointer(this._game, next); + return this['pointer' + next]; + } + + } + + /** + * Starts the Input Manager running + * @method start + **/ public start() { this.mouse.start(); @@ -373,6 +420,10 @@ module Phaser { } + /** + * Updates the Input Manager. Called by the core Game loop. + * @method update + **/ public update() { this.mousePointer.update(); @@ -381,11 +432,12 @@ module Phaser { this.pointer3.update(); this.pointer4.update(); this.pointer5.update(); - this.pointer6.update(); - this.pointer7.update(); - this.pointer8.update(); - this.pointer9.update(); - this.pointer10.update(); + + if (this.pointer6) { this.pointer6.update(); } + if (this.pointer7) { this.pointer7.update(); } + if (this.pointer8) { this.pointer8.update(); } + if (this.pointer9) { this.pointer9.update(); } + if (this.pointer10) { this.pointer10.update(); } } @@ -403,11 +455,12 @@ module Phaser { this.pointer3.reset(); this.pointer4.reset(); this.pointer5.reset(); - this.pointer6.reset(); - this.pointer7.reset(); - this.pointer8.reset(); - this.pointer9.reset(); - this.pointer10.reset(); + + if (this.pointer6) { this.pointer6.reset(); } + if (this.pointer7) { this.pointer7.reset(); } + if (this.pointer8) { this.pointer8.reset(); } + if (this.pointer9) { this.pointer9.reset(); } + if (this.pointer10) { this.pointer10.reset(); } this.currentPointers = 0; @@ -417,7 +470,6 @@ module Phaser { this.onUp = new Phaser.Signal(); this.onTap = new Phaser.Signal(); this.onHold = new Phaser.Signal(); - } } @@ -462,23 +514,23 @@ module Phaser { { this.currentPointers++; } - else if (this.pointer6.active == true) + else if (this.pointer6 && this.pointer6.active == true) { this.currentPointers++; } - else if (this.pointer7.active == true) + else if (this.pointer7 && this.pointer7.active == true) { this.currentPointers++; } - else if (this.pointer8.active == true) + else if (this.pointer8 && this.pointer8.active == true) { this.currentPointers++; } - else if (this.pointer9.active == true) + else if (this.pointer9 && this.pointer9.active == true) { this.currentPointers++; } - else if (this.pointer10.active == true) + else if (this.pointer10 && this.pointer10.active == true) { this.currentPointers++; } @@ -521,23 +573,23 @@ module Phaser { { return this.pointer5.start(event); } - else if (this.pointer6.active == false) + else if (this.pointer6 && this.pointer6.active == false) { return this.pointer6.start(event); } - else if (this.pointer7.active == false) + else if (this.pointer7 && this.pointer7.active == false) { return this.pointer7.start(event); } - else if (this.pointer8.active == false) + else if (this.pointer8 && this.pointer8.active == false) { return this.pointer8.start(event); } - else if (this.pointer9.active == false) + else if (this.pointer9 && this.pointer9.active == false) { return this.pointer9.start(event); } - else if (this.pointer10.active == false) + else if (this.pointer10 && this.pointer10.active == false) { return this.pointer10.start(event); } @@ -575,23 +627,23 @@ module Phaser { { return this.pointer5.move(event); } - else if (this.pointer6.active == true && this.pointer6.identifier == event.identifier) + else if (this.pointer6 && this.pointer6.active == true && this.pointer6.identifier == event.identifier) { return this.pointer6.move(event); } - else if (this.pointer7.active == true && this.pointer7.identifier == event.identifier) + else if (this.pointer7 && this.pointer7.active == true && this.pointer7.identifier == event.identifier) { return this.pointer7.move(event); } - else if (this.pointer8.active == true && this.pointer8.identifier == event.identifier) + else if (this.pointer8 && this.pointer8.active == true && this.pointer8.identifier == event.identifier) { return this.pointer8.move(event); } - else if (this.pointer9.active == true && this.pointer9.identifier == event.identifier) + else if (this.pointer9 && this.pointer9.active == true && this.pointer9.identifier == event.identifier) { return this.pointer9.move(event); } - else if (this.pointer10.active == true && this.pointer10.identifier == event.identifier) + else if (this.pointer10 && this.pointer10.active == true && this.pointer10.identifier == event.identifier) { return this.pointer10.move(event); } @@ -629,23 +681,23 @@ module Phaser { { return this.pointer5.stop(event); } - else if (this.pointer6.active == true && this.pointer6.identifier == event.identifier) + else if (this.pointer6 && this.pointer6.active == true && this.pointer6.identifier == event.identifier) { return this.pointer6.stop(event); } - else if (this.pointer7.active == true && this.pointer7.identifier == event.identifier) + else if (this.pointer7 && this.pointer7.active == true && this.pointer7.identifier == event.identifier) { return this.pointer7.stop(event); } - else if (this.pointer8.active == true && this.pointer8.identifier == event.identifier) + else if (this.pointer8 && this.pointer8.active == true && this.pointer8.identifier == event.identifier) { return this.pointer8.stop(event); } - else if (this.pointer9.active == true && this.pointer9.identifier == event.identifier) + else if (this.pointer9 && this.pointer9.active == true && this.pointer9.identifier == event.identifier) { return this.pointer9.stop(event); } - else if (this.pointer10.active == true && this.pointer10.identifier == event.identifier) + else if (this.pointer10 && this.pointer10.active == true && this.pointer10.identifier == event.identifier) { return this.pointer10.stop(event); } @@ -683,23 +735,23 @@ module Phaser { { return this.pointer5; } - else if (this.pointer6.active == state) + else if (this.pointer6 && this.pointer6.active == state) { return this.pointer6; } - else if (this.pointer7.active == state) + else if (this.pointer7 && this.pointer7.active == state) { return this.pointer7; } - else if (this.pointer8.active == state) + else if (this.pointer8 && this.pointer8.active == state) { return this.pointer8; } - else if (this.pointer9.active == state) + else if (this.pointer9 && this.pointer9.active == state) { return this.pointer9; } - else if (this.pointer10.active == state) + else if (this.pointer10 && this.pointer10.active == state) { return this.pointer10; } @@ -737,23 +789,23 @@ module Phaser { { return this.pointer5; } - else if (this.pointer6.identifier == identifier) + else if (this.pointer6 && this.pointer6.identifier == identifier) { return this.pointer6; } - else if (this.pointer7.identifier == identifier) + else if (this.pointer7 && this.pointer7.identifier == identifier) { return this.pointer7; } - else if (this.pointer8.identifier == identifier) + else if (this.pointer8 && this.pointer8.identifier == identifier) { return this.pointer8; } - else if (this.pointer9.identifier == identifier) + else if (this.pointer9 && this.pointer9.identifier == identifier) { return this.pointer9; } - else if (this.pointer10.identifier == identifier) + else if (this.pointer10 && this.pointer10.identifier == identifier) { return this.pointer10; } @@ -797,32 +849,24 @@ module Phaser { } /** - * - * @method calculateDistance - * @param {Finger} finger1 - * @param {Finger} finger2 + * Get the distance between two Pointer objects + * @method getDistance + * @param {Pointer} pointer1 + * @param {Pointer} pointer2 **/ - //public calculateDistance(finger1: Finger, finger2: Finger) { - //} + public getDistance(pointer1: Pointer, pointer2: Pointer): number { + return pointer1.position.distance(pointer2.position); + } /** - * - * @method calculateAngle - * @param {Finger} finger1 - * @param {Finger} finger2 + * Get the angle between two Pointer objects + * @method getAngle + * @param {Pointer} pointer1 + * @param {Pointer} pointer2 **/ - //public calculateAngle(finger1: Finger, finger2: Finger) { - //} - - /** - * - * @method checkOverlap - * @param {Finger} finger1 - * @param {Finger} finger2 - **/ - //public checkOverlap(finger1: Finger, finger2: Finger) { - //} - + public getAngle(pointer1: Pointer, pointer2: Pointer): number { + return pointer1.position.angle(pointer2.position); + } } diff --git a/Phaser/system/input/Pointer.ts b/Phaser/system/input/Pointer.ts index a461f265..f12bc62e 100644 --- a/Phaser/system/input/Pointer.ts +++ b/Phaser/system/input/Pointer.ts @@ -1,4 +1,5 @@ /// +/// /** * Phaser - Pointer @@ -21,8 +22,8 @@ module Phaser { this.id = id; this.active = false; - this.pointA = new Point(); - this.pointB = new Point(); + this.position = new Vector2; + this.positionDown = new Vector2; this.circle = new Circle(0, 0, 44); if (id == 0) @@ -88,18 +89,18 @@ module Phaser { public active: bool; /** - * A Point object representing the x/y screen coordinates of the Pointer. - * @property pointA - * @type {Point} + * A Vector object containing the initial position when the Pointer was engaged with the screen. + * @property positionDown + * @type {Vector2} **/ - public pointA: Point = null; + public positionDown: Vector2 = null; /** - * A Point object representing the x/y screen coordinates of the Pointer. - * @property pointB - * @type {Point} + * A Vector object containing the current position of the Pointer on the screen. + * @property position + * @type {Vector2} **/ - public pointB: Point = null; + public position: Vector2 = null; /** * A Circle object centered on the x/y screen coordinates of the Pointer. @@ -286,11 +287,18 @@ module Phaser { this.button = event.button; } + // Fix to stop rogue browser plugins from blocking the visibility state event + if (this._game.paused == true) + { + this._game.stage.resumeGame(); + return this; + } + this._history.length = 0; this.move(event); - this.pointA.setTo(this.x, this.y); + this.positionDown.setTo(this.x, this.y); this.active = true; this.withinGame = true; @@ -335,7 +343,7 @@ module Phaser { if (this._game.input.recordPointerHistory && this._game.time.now >= this._nextDrop) { this._nextDrop = this._game.time.now + this._game.input.recordRate; - this._history.push({ x: this.pointB.x, y: this.pointB.y }); + this._history.push({ x: this.position.x, y: this.position.y }); if (this._history.length > this._game.input.recordLimit) { @@ -369,7 +377,7 @@ module Phaser { this.x = this.pageX - this._game.stage.offset.x; this.y = this.pageY - this._game.stage.offset.y; - this.pointB.setTo(this.x, this.y); + this.position.setTo(this.x, this.y); this.circle.x = this.x; this.circle.y = this.y; @@ -377,7 +385,7 @@ module Phaser { { this._game.input.x = this.x * this._game.input.scaleX; this._game.input.y = this.y * this._game.input.scaleY; - this._game.input.point.setTo(this._game.input.x, this._game.input.y); + this._game.input.position.setTo(this._game.input.x, this._game.input.y); this._game.input.circle.x = this._game.input.x; this._game.input.circle.y = this._game.input.y; } @@ -529,8 +537,8 @@ module Phaser { // Render the points this._game.stage.context.beginPath(); - this._game.stage.context.moveTo(this.pointA.x, this.pointA.y); - this._game.stage.context.lineTo(this.pointB.x, this.pointB.y); + this._game.stage.context.moveTo(this.positionDown.x, this.positionDown.y); + this._game.stage.context.lineTo(this.position.x, this.position.y); this._game.stage.context.lineWidth = 2; this._game.stage.context.stroke(); this._game.stage.context.closePath(); diff --git a/Phaser/system/screens/BootScreen.ts b/Phaser/system/screens/BootScreen.ts index 22c80284..b84d6c76 100644 --- a/Phaser/system/screens/BootScreen.ts +++ b/Phaser/system/screens/BootScreen.ts @@ -112,7 +112,7 @@ module Phaser { */ private colorCycle() { - this._fade = this._game.createTween(this._color2); + this._fade = this._game.add.tween(this._color2); this._fade.to({ r: Math.random() * 250, g: Math.random() * 250, b: Math.random() * 250 }, 3000, Phaser.Easing.Linear.None); this._fade.onComplete.add(this.colorCycle, this); diff --git a/Phaser/system/screens/PauseScreen.ts b/Phaser/system/screens/PauseScreen.ts index 5ef4482c..875c59a9 100644 --- a/Phaser/system/screens/PauseScreen.ts +++ b/Phaser/system/screens/PauseScreen.ts @@ -31,11 +31,13 @@ module Phaser { * Local private reference to game. */ private _game: Game; + /** * Canvas element used by engine. * @type {HTMLCanvasElement} */ private _canvas: HTMLCanvasElement; + /** * Render context of stage's canvas. * @type {CanvasRenderingContext2D} @@ -46,6 +48,7 @@ module Phaser { * Background color. */ private _color; + /** * Fade effect tween. * @type {Phaser.Tween} @@ -114,7 +117,7 @@ module Phaser { */ private fadeOut() { - this._fade = this._game.createTween(this._color); + this._fade = this._game.add.tween(this._color); this._fade.to({ r: 50, g: 50, b: 50 }, 1000, Phaser.Easing.Linear.None); this._fade.onComplete.add(this.fadeIn, this); @@ -127,7 +130,7 @@ module Phaser { */ private fadeIn() { - this._fade = this._game.createTween(this._color); + this._fade = this._game.add.tween(this._color); this._fade.to({ r: 255, g: 255, b: 255 }, 1000, Phaser.Easing.Linear.None); this._fade.onComplete.add(this.fadeOut, this); diff --git a/Phaser/verlet/Composite.ts b/Phaser/verlet/Composite.ts index 19055cfa..4d9dcaed 100644 --- a/Phaser/verlet/Composite.ts +++ b/Phaser/verlet/Composite.ts @@ -25,19 +25,47 @@ module Phaser.Verlet { this._game = game; + this.sprites = []; this.particles = []; this.constraints = []; + this.frameBounds = new Phaser.Quad(); + } private _game: Game; + /** + * Texture of the particles to be rendered. + */ + private _texture = null; + + /** + * Rendering bounds for the texture + * @type {Quad} + */ + private frameBounds: Quad; + + // local rendering related temp vars to help avoid gc spikes + private _sx: number = 0; + private _sy: number = 0; + private _sw: number = 0; + private _sh: number = 0; + private _dx: number = 0; + private _dy: number = 0; + private _dw: number = 0; + private _dh: number = 0; + private _hw: number = 0; + private _hh: number = 0; + + public sprites: Phaser.Sprite[]; public particles: Phaser.Verlet.Particle[]; public constraints; + public drawParticles = null; public drawConstraints = null; - // Map sprites to particles + // Create Constraints public createDistanceConstraint(a: Phaser.Verlet.Particle, b: Phaser.Verlet.Particle, stiffness: number, distance?: number = null): Phaser.Verlet.DistanceConstraint { @@ -60,6 +88,103 @@ module Phaser.Verlet { } + /** + * Load a graphic for this Composite. The graphic cannot be a SpriteSheet yet. + * @param key {string} Key of the graphic you want to load for this sprite. + * @return {Composite} This object + */ + public loadGraphic(key: string): Composite { + + if (this._game.cache.getImage(key) !== null) + { + if (this._game.cache.isSpriteSheet(key) == false) + { + this._texture = this._game.cache.getImage(key); + this.frameBounds.width = this._texture.width; + this.frameBounds.height = this._texture.height; + this._hw = Math.floor(this.frameBounds.width / 2); + this._hh = Math.floor(this.frameBounds.width / 2); + this.drawParticles = this.render; + this.drawConstraints = this.renderConstraints; + } + } + + return this; + + } + + public hideConstraints: bool = true; + public constraintLineColor: string = 'rgba(200,200,200,1)'; + + private renderConstraints(context) { + + if (this.hideConstraints == true || this.constraints.length == 0) + { + return; + } + + var i; + + context.beginPath(); + + for (i in this.constraints) + { + if (this.constraints[i].b) + { + context.moveTo(this.constraints[i].a.pos.x, this.constraints[i].a.pos.y); + context.lineTo(this.constraints[i].b.pos.x, this.constraints[i].b.pos.y); + } + } + + context.strokeStyle = this.constraintLineColor; + context.stroke(); + context.closePath(); + + } + + private render(context) { + + this._sx = 0; + this._sy = 0; + this._sw = this.frameBounds.width; + this._sh = this.frameBounds.height; + this._dw = this.frameBounds.width; + this._dh = this.frameBounds.height; + + this._sx = Math.round(this._sx); + this._sy = Math.round(this._sy); + this._sw = Math.round(this._sw); + this._sh = Math.round(this._sh); + this._dw = Math.round(this._dw); + this._dh = Math.round(this._dh); + + var i; + + for (i in this.particles) + { + //this._dx = cameraOffsetX + (this.frameBounds.topLeft.x - camera.worldView.x); + //this._dy = cameraOffsetY + (this.frameBounds.topLeft.y - camera.worldView.y); + this._dx = this.particles[i].pos.x - this._hw; + this._dy = this.particles[i].pos.y - this._hh; + this._dx = Math.round(this._dx); + this._dy = Math.round(this._dy); + + context.drawImage( + this._texture, // Source Image + this._sx, // Source X (location within the source image) + this._sy, // Source Y + this._sw, // Source Width + this._sh, // Source Height + this._dx, // Destination X (where on the canvas it'll be drawn) + this._dy, // Destination Y + this._dw, // Destination Width (always same as Source Width unless scaled) + this._dh // Destination Height (always same as Source Height unless scaled) + ); + + } + + } + public pin(index, pos?=null) { if (pos == null) diff --git a/Phaser/verlet/DistanceConstraint.ts b/Phaser/verlet/DistanceConstraint.ts index f2aa0eec..881c2503 100644 --- a/Phaser/verlet/DistanceConstraint.ts +++ b/Phaser/verlet/DistanceConstraint.ts @@ -62,6 +62,7 @@ module Phaser.Verlet { ctx.lineTo(this.b.pos.x, this.b.pos.y); ctx.strokeStyle = "#d8dde2"; ctx.stroke(); + ctx.closePath(); } } diff --git a/README.md b/README.md index ba4131d6..142085d2 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,12 @@ V0.9.6 * Added Input.startPointer, Input.updatePointer and Input.stopPointer. * Phaser Input now confirmed working on Windows Phone 8 (Nokia Lumia 920). * Added Input.maxPointers to allow you to limit the number of fingers your game will listen for on multi-touch systems. +* Added Input.addPointer. By default Input will create 5 pointers (+1 for the mouse). Use addPointer() to add up to a maximum of 10. +* Added Input.position - a Vector2 object containing the most recent position of the most recently active Pointer. +* Added Input.getDistance. Find the distance between the two given Pointer objects. +* Added Input.getAngle. Find the angle between the two given Pointer objects. * Pointer.totalTouches value keeps a running total of the number of times the Pointer has been pressed. -* Added Pointer.pointA and pointB - pointA is placed on touch, pointB is moved on update, useful for tracking distance/direction/gestures. +* Added Pointer.position and positionDown. positionDown is placed on touch and position is update on move, useful for tracking distance/direction/gestures. * Added Game.state - now contains a reference to the current state object (if any was given). * Moved the Input start events from the constructors to a single Input.start method. * Added Input.disabled boolean to globally turn off all input event processing. @@ -71,7 +75,7 @@ V0.9.6 * Added Basic.ignoreGlobalUpdate - stops the object being updated as part of the main game loop, you'll need to call update on it yourself * Added Basic.ignoreGlobalRender - stops the object being rendered as part of the main game loop, you'll need to call render on it yourself * Added forceUpdate and forceRender parameters to Group.update and Group.render respectively. Combined with ignoreGlobal you can create custom rendering set-ups -* Fixed Loader.progress calculation so it now accurate passes a value between 0 and 100% to your loader callback +* Fixed Loader.progress calculation so it now accurately passes a value between 0 and 100 to your loader callback * Added a 'hard reset' parameter to Input.reset. A hard reset clears Input signals (such as on a state swap), a soft (such as on game pause) doesn't * Added Device.isConsoleOpen() to check if the browser console is open. Tested on Firefox with Firebug and Chrome with DevTools * Added delay parameter to Tween.to() @@ -91,19 +95,29 @@ V0.9.6 * Added Group.alpha to apply a globalAlpha before the groups children are rendered. Useful to save on alpha calls. * Added Group.globalCompositeOperation to apply a composite operation before all of the groups children are rendered. * Added Camera black list support to Group along with Group.showToCamera, Group.hideFromCamera and Group.clearCameraList - - +* Added GameMath.rotatePoint to allow for point rotation at any angle around a given anchor and distance +* Updated World.setSize() to optionally update the VerletManager dimensions as well +* Added GameObject.setPosition(x, y) +* Added Quad.intersectsRaw(left, right, top, bottom, tolerance) +* Updated Sprite.inCamera to correctly apply the scrollFactor to the camera bounds check +* Added Loader.crossOrigin property which is applied to loaded Images +* Added AnimationManager.destroy() to clear out all local references and objects +* Added the clearAnimations parameter to Sprite.loadGraphic(). Allows you to change animation textures but retain the frame data. +* Added the GameObjectFactory to Game. You now make Sprites like this: game.add.sprite(). Much better separation of game object creation methods now. But you'll have to update ALL code, sorry! +* Added GameObjectFactory methods to add existing objects to the game world, such as existingSprite(), existingTween(), etc. +* Added the GameObjectFactory to Phaser.State * TODO: Check that tween pausing works with the new performance.now * TODO: Game.Time should monitor pause duration * TODO: Investigate bug re: tilemap collision and animation frames * TODO: Update tests that use arrow keys and include touch/mouse support * TODO: GameObject.clipRect -* TODO: Use CollisionMask in Input instead of Circle? * TODO: Polygon geom primitive * TODO: Move GameObject transforms to a single matrix - - +* TODO: this.target.view.style.cursor = "pointer"; ("default") +* TODO: Fix bug in scrollFactor inCamera check where the scrollFactor > 0 and < 1 +* TODO: If the Camera is larger than the Stage size then the rotation offset isn't correct +* TODO: Texture Repeat doesn't scroll, because it's part of the camera not the world, need to think about this more Requirements ------------ diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index c6dd907f..71198992 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -118,6 +118,10 @@ + + + mask animation 1.ts + mask test 1.ts @@ -131,6 +135,10 @@ + + + rope bridge.ts + rotate point 1.ts @@ -190,6 +198,14 @@ mousetrail.ts + + + temp1.ts + + + + temp2.ts + ballscroller.ts diff --git a/Tests/collision/mask animation 1.js b/Tests/collision/mask animation 1.js new file mode 100644 index 00000000..19d7f97e --- /dev/null +++ b/Tests/collision/mask animation 1.js @@ -0,0 +1,31 @@ +/// +(function () { + var myGame = new Phaser.Game(this, 'game', 800, 600, init, create, update); + function init() { + myGame.loader.addImageFile('card', 'assets/sprites/mana_card.png'); + myGame.loader.addTextureAtlas('bot', 'assets/sprites/running_bot.png', 'assets/sprites/running_bot.json'); + myGame.loader.load(); + } + var card; + var bot; + function create() { + card = myGame.createSprite(200, 220, 'card'); + bot = myGame.createSprite(myGame.stage.width - 100, 300, 'bot'); + // The collision mask is much thinner than the animated sprite + bot.collisionMask.offset.x = 16; + bot.collisionMask.width = 32; + bot.renderDebug = true; + bot.animations.add('run'); + bot.animations.play('run', 10, true); + bot.velocity.x = -150; + } + function update() { + if(bot.x < -bot.width) { + bot.x = myGame.stage.width; + bot.velocity.x = -150; + card.x = 200; + card.velocity.x = 0; + } + myGame.collide(card, bot); + } +})(); diff --git a/Tests/collision/mask animation 1.ts b/Tests/collision/mask animation 1.ts new file mode 100644 index 00000000..c07da0b5 --- /dev/null +++ b/Tests/collision/mask animation 1.ts @@ -0,0 +1,50 @@ +/// + +(function () { + + var myGame = new Phaser.Game(this, 'game', 800, 600, init, create, update); + + function init() { + + myGame.loader.addImageFile('card', 'assets/sprites/mana_card.png'); + myGame.loader.addTextureAtlas('bot', 'assets/sprites/running_bot.png', 'assets/sprites/running_bot.json'); + myGame.loader.load(); + + } + + var card: Phaser.Sprite; + var bot: Phaser.Sprite; + + function create() { + + card = myGame.createSprite(200, 220, 'card'); + + bot = myGame.createSprite(myGame.stage.width - 100, 300, 'bot'); + + // The collision mask is much thinner than the animated sprite + bot.collisionMask.offset.x = 16; + bot.collisionMask.width = 32; + bot.renderDebug = true; + + bot.animations.add('run'); + bot.animations.play('run', 10, true); + + bot.velocity.x = -150; + + } + + function update() { + + if (bot.x < -bot.width) + { + bot.x = myGame.stage.width; + bot.velocity.x = -150; + card.x = 200; + card.velocity.x = 0; + } + + myGame.collide(card, bot); + + } + +})(); diff --git a/Tests/collision/mask test 1.js b/Tests/collision/mask test 1.js index e0bdb749..9eb41dfb 100644 --- a/Tests/collision/mask test 1.js +++ b/Tests/collision/mask test 1.js @@ -34,6 +34,5 @@ console.log('Collision!!!!!'); } function render() { - //atari1.ren - } + } })(); diff --git a/Tests/collision/mask test 1.ts b/Tests/collision/mask test 1.ts index 78d35dba..fe608d2e 100644 --- a/Tests/collision/mask test 1.ts +++ b/Tests/collision/mask test 1.ts @@ -59,9 +59,6 @@ } function render() { - - //atari1.ren - } })(); diff --git a/Tests/geometry/rope bridge.js b/Tests/geometry/rope bridge.js new file mode 100644 index 00000000..60a056d1 --- /dev/null +++ b/Tests/geometry/rope bridge.js @@ -0,0 +1,34 @@ +/// +(function () { + var myGame = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + function init() { + myGame.loader.addImageFile('ball5', 'assets/sprites/purple_ball.png'); + myGame.loader.load(); + } + var segment; + function create() { + myGame.verlet.friction = 1; + myGame.verlet.hideNearestEntityCircle = true; + var points = []; + var startX = 100; + var startY = 200; + var spacing = 20; + for(var i = 0; i < 30; i++) { + points.push(new Phaser.Vector2(startX + (i * spacing), startY)); + } + segment = myGame.verlet.createLineSegments(points, 0.5); + segment.loadGraphic('ball5'); + segment.hideConstraints = false; + segment.pin(0); + segment.pin(points.length - 1); + } + function update() { + } + function render() { + myGame.verlet.render(); + //myGame.stage.context.fillStyle = 'rgb(255,255,0)'; + //myGame.stage.context.fillRect(p1.x, p1.y, 4, 4); + //myGame.stage.context.fillStyle = 'rgb(255,0,0)'; + //myGame.stage.context.fillRect(p2.x, p2.y, 4, 4); + } +})(); diff --git a/Tests/geometry/rope bridge.ts b/Tests/geometry/rope bridge.ts new file mode 100644 index 00000000..1d1a437f --- /dev/null +++ b/Tests/geometry/rope bridge.ts @@ -0,0 +1,55 @@ +/// + +(function () { + + var myGame = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + + function init() { + + myGame.loader.addImageFile('ball5', 'assets/sprites/purple_ball.png'); + myGame.loader.load(); + + } + + var segment: Phaser.Verlet.Composite; + + function create() { + + myGame.verlet.friction = 1; + myGame.verlet.hideNearestEntityCircle = true; + + var points: Phaser.Vector2[] = []; + var startX: number = 100; + var startY: number = 200; + var spacing: number = 20; + + for (var i = 0; i < 30; i++) + { + points.push(new Phaser.Vector2(startX + (i * spacing), startY)); + } + + segment = myGame.verlet.createLineSegments(points, 0.5); + segment.loadGraphic('ball5'); + segment.hideConstraints = false; + + segment.pin(0); + segment.pin(points.length - 1); + + } + + function update() { + } + + function render() { + + myGame.verlet.render(); + + //myGame.stage.context.fillStyle = 'rgb(255,255,0)'; + //myGame.stage.context.fillRect(p1.x, p1.y, 4, 4); + + //myGame.stage.context.fillStyle = 'rgb(255,0,0)'; + //myGame.stage.context.fillRect(p2.x, p2.y, 4, 4); + + } + +})(); diff --git a/Tests/geometry/verlet 1.js b/Tests/geometry/verlet 1.js index c951b1b7..ac4a3816 100644 --- a/Tests/geometry/verlet 1.js +++ b/Tests/geometry/verlet 1.js @@ -22,9 +22,5 @@ } function render() { myGame.verlet.render(); - //myGame.stage.context.fillStyle = 'rgb(255,255,0)'; - //myGame.stage.context.fillRect(p1.x, p1.y, 4, 4); - //myGame.stage.context.fillStyle = 'rgb(255,0,0)'; - //myGame.stage.context.fillRect(p2.x, p2.y, 4, 4); - } + } })(); diff --git a/Tests/geometry/verlet 1.ts b/Tests/geometry/verlet 1.ts index 455ad974..d6265c00 100644 --- a/Tests/geometry/verlet 1.ts +++ b/Tests/geometry/verlet 1.ts @@ -28,12 +28,6 @@ myGame.verlet.render(); - //myGame.stage.context.fillStyle = 'rgb(255,255,0)'; - //myGame.stage.context.fillRect(p1.x, p1.y, 4, 4); - - //myGame.stage.context.fillStyle = 'rgb(255,0,0)'; - //myGame.stage.context.fillRect(p2.x, p2.y, 4, 4); - } })(); diff --git a/Tests/geometry/verlet sprites.js b/Tests/geometry/verlet sprites.js index 93dbc2c1..52f41605 100644 --- a/Tests/geometry/verlet sprites.js +++ b/Tests/geometry/verlet sprites.js @@ -1,11 +1,6 @@ /// (function () { var myGame = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); - var cube; - var b1; - var b2; - var b3; - var b4; function init() { myGame.loader.addImageFile('ball0', 'assets/sprites/yellow_ball.png'); myGame.loader.addImageFile('ball1', 'assets/sprites/aqua_ball.png'); @@ -15,33 +10,23 @@ myGame.loader.addImageFile('ball5', 'assets/sprites/purple_ball.png'); myGame.loader.load(); } + var wheel; + var diamond; + var triangle; + var cube; function create() { myGame.verlet.friction = 1; myGame.verlet.step = 32; - //var wheel = myGame.verlet.createTire(new Phaser.Vector2(200,50), 100, 30, 0.3, 0.9); - //var tire2 = myGame.verlet.createTire(new Phaser.Vector2(400,50), 70, 7, 0.1, 0.2); - cube = myGame.verlet.createTire(new Phaser.Vector2(300, 50), 100, 4, 1, 1); - //var tri = myGame.verlet.createTire(new Phaser.Vector2(700,50), 100, 3, 1, 1); - var dc = new Phaser.Verlet.DistanceConstraint(cube.particles[0], cube.particles[1], 1); - cube.constraints.push(dc); - var dc2 = new Phaser.Verlet.DistanceConstraint(cube.particles[1], cube.particles[2], 1); - cube.constraints.push(dc2); - var dc3 = new Phaser.Verlet.DistanceConstraint(cube.particles[2], cube.particles[3], 1); - cube.constraints.push(dc3); - b1 = myGame.createSprite(cube.particles[0].pos.x, cube.particles[0].pos.y, 'ball0'); - b2 = myGame.createSprite(cube.particles[1].pos.x, cube.particles[1].pos.y, 'ball1'); - b3 = myGame.createSprite(cube.particles[2].pos.x, cube.particles[2].pos.y, 'ball2'); - b4 = myGame.createSprite(cube.particles[3].pos.x, cube.particles[3].pos.y, 'ball3'); + wheel = myGame.verlet.createTire(new Phaser.Vector2(200, 50), 100, 30, 0.3, 0.9); + wheel.loadGraphic('ball0'); + diamond = myGame.verlet.createTire(new Phaser.Vector2(400, 50), 70, 7, 0.1, 0.2); + diamond.loadGraphic('ball1'); + triangle = myGame.verlet.createTire(new Phaser.Vector2(600, 50), 100, 3, 1, 1); + triangle.loadGraphic('ball2'); + cube = myGame.verlet.createTire(new Phaser.Vector2(300, 50), 100, 4, 0.3, 0.9); + cube.loadGraphic('ball3'); } function update() { - b1.x = cube.particles[0].pos.x - 8; - b1.y = cube.particles[0].pos.y - 8; - b2.x = cube.particles[1].pos.x - 8; - b2.y = cube.particles[1].pos.y - 8; - b3.x = cube.particles[2].pos.x - 8; - b3.y = cube.particles[2].pos.y - 8; - b4.x = cube.particles[3].pos.x - 8; - b4.y = cube.particles[3].pos.y - 8; } function render() { myGame.verlet.render(); diff --git a/Tests/geometry/verlet sprites.ts b/Tests/geometry/verlet sprites.ts index 30c5dc85..3432b36b 100644 --- a/Tests/geometry/verlet sprites.ts +++ b/Tests/geometry/verlet sprites.ts @@ -4,13 +4,6 @@ var myGame = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); - var cube: Phaser.Verlet.Composite; - - var b1: Phaser.Sprite; - var b2: Phaser.Sprite; - var b3: Phaser.Sprite; - var b4: Phaser.Sprite; - function init() { myGame.loader.addImageFile('ball0', 'assets/sprites/yellow_ball.png'); @@ -24,46 +17,31 @@ } + var wheel: Phaser.Verlet.Composite; + var diamond: Phaser.Verlet.Composite; + var triangle: Phaser.Verlet.Composite; + var cube: Phaser.Verlet.Composite; + function create() { myGame.verlet.friction = 1; myGame.verlet.step = 32; - //var wheel = myGame.verlet.createTire(new Phaser.Vector2(200,50), 100, 30, 0.3, 0.9); - //var tire2 = myGame.verlet.createTire(new Phaser.Vector2(400,50), 70, 7, 0.1, 0.2); - cube = myGame.verlet.createTire(new Phaser.Vector2(300, 50), 100, 4, 1, 1); - //var tri = myGame.verlet.createTire(new Phaser.Vector2(700,50), 100, 3, 1, 1); - - var dc: Phaser.Verlet.DistanceConstraint = new Phaser.Verlet.DistanceConstraint(cube.particles[0], cube.particles[1], 1); - cube.constraints.push(dc); - - var dc2: Phaser.Verlet.DistanceConstraint = new Phaser.Verlet.DistanceConstraint(cube.particles[1], cube.particles[2], 1); - cube.constraints.push(dc2); - - var dc3: Phaser.Verlet.DistanceConstraint = new Phaser.Verlet.DistanceConstraint(cube.particles[2], cube.particles[3], 1); - cube.constraints.push(dc3); - - b1 = myGame.createSprite(cube.particles[0].pos.x, cube.particles[0].pos.y, 'ball0'); - b2 = myGame.createSprite(cube.particles[1].pos.x, cube.particles[1].pos.y, 'ball1'); - b3 = myGame.createSprite(cube.particles[2].pos.x, cube.particles[2].pos.y, 'ball2'); - b4 = myGame.createSprite(cube.particles[3].pos.x, cube.particles[3].pos.y, 'ball3'); + wheel = myGame.verlet.createTire(new Phaser.Vector2(200,50), 100, 30, 0.3, 0.9); + wheel.loadGraphic('ball0'); + + diamond = myGame.verlet.createTire(new Phaser.Vector2(400,50), 70, 7, 0.1, 0.2); + diamond.loadGraphic('ball1'); + + triangle = myGame.verlet.createTire(new Phaser.Vector2(600,50), 100, 3, 1, 1); + triangle.loadGraphic('ball2'); + + cube = myGame.verlet.createTire(new Phaser.Vector2(300, 50), 100, 4, 0.3, 0.9); + cube.loadGraphic('ball3'); } function update() { - - b1.x = cube.particles[0].pos.x - 8; - b1.y = cube.particles[0].pos.y - 8; - - b2.x = cube.particles[1].pos.x - 8; - b2.y = cube.particles[1].pos.y - 8; - - b3.x = cube.particles[2].pos.x - 8; - b3.y = cube.particles[2].pos.y - 8; - - b4.x = cube.particles[3].pos.x - 8; - b4.y = cube.particles[3].pos.y - 8; - } function render() { diff --git a/Tests/phaser.js b/Tests/phaser.js index 1d5e6f54..e4136383 100644 --- a/Tests/phaser.js +++ b/Tests/phaser.js @@ -504,8 +504,6 @@ var Phaser; this.last.y = this.y; }; CollisionMask.prototype.update = function () { - //this.quad.x = this._parent.x + this.offset.x; - //this.quad.y = this._parent.y + this.offset.y; this._ref.x = this._parent.x + this.offset.x; this._ref.y = this._parent.y + this.offset.y; }; @@ -518,8 +516,7 @@ var Phaser; function (camera, cameraOffsetX, cameraOffsetY) { var _dx = cameraOffsetX + (this.x - camera.worldView.x); var _dy = cameraOffsetY + (this.y - camera.worldView.y); - //this._parent.context.fillStyle = this._parent.renderDebugColor; - this._parent.context.fillStyle = 'rgba(255,0,0,0.4)'; + this._parent.context.fillStyle = this._parent.renderDebugColor; if(this.type == CollisionMask.QUAD) { this._parent.context.fillRect(_dx, _dy, this.width, this.height); } else if(this.type == CollisionMask.CIRCLE) { @@ -543,6 +540,15 @@ var Phaser; this.line = null; this.offset = null; }; + CollisionMask.prototype.intersectsRaw = function (left, right, top, bottom) { + //if ((objBounds.x + objBounds.width > x) && (objBounds.x < x + width) && (objBounds.y + objBounds.height > y) && (objBounds.y < y + height)) + return true; + }; + CollisionMask.prototype.intersectsVector = function (vector) { + if(this.type == CollisionMask.QUAD) { + return this.quad.contains(vector.x, vector.y); + } + }; CollisionMask.prototype.intersects = /** * Gives a basic boolean response to a geometric collision. * If you need the details of the collision use the Collision functions instead and inspect the IntersectResult object. @@ -556,7 +562,6 @@ var Phaser; } // Circle vs. Circle if(this.type == CollisionMask.CIRCLE && source.type == CollisionMask.CIRCLE) { - console.log('c vs c'); return Phaser.Collision.circleToCircle(this.circle, source.circle).result; } // Circle vs. Rect @@ -889,7 +894,6 @@ var Phaser; this.alpha = 1; this.scale = new Phaser.MicroPoint(1, 1); this.last = new Phaser.MicroPoint(x, y); - //this.origin = new MicroPoint(this.frameBounds.halfWidth, this.frameBounds.halfHeight); this.align = GameObject.ALIGN_TOP_LEFT; this.mass = 1; this.elasticity = 0; @@ -1273,6 +1277,10 @@ var Phaser; */ function () { }; + GameObject.prototype.setPosition = function (x, y) { + this.x = x; + this.y = y; + }; Object.defineProperty(GameObject.prototype, "x", { get: function () { return this.frameBounds.x; @@ -1554,7 +1562,7 @@ var Phaser; this.fx.preUpdate(); if(this._target !== null) { if(this.deadzone == null) { - this.focusOnXY(this._target.x + this._target.origin.x, this._target.y + this._target.origin.y); + this.focusOnXY(this._target.x, this._target.y); } else { var edge; var targetX = this._target.x + ((this._target.x > 0) ? 0.0000001 : -0.0000001); @@ -1893,9 +1901,14 @@ var Phaser; Sprite.prototype.loadGraphic = /** * Load graphic for this sprite. (graphic can be SpriteSheet or Texture) * @param key {string} Key of the graphic you want to load for this sprite. + * @param clearAnimations {boolean} If this Sprite has a set of animation data already loaded you can choose to keep or clear it with this boolean * @return {Sprite} Sprite instance itself. */ - function (key) { + function (key, clearAnimations) { + if (typeof clearAnimations === "undefined") { clearAnimations = true; } + if(clearAnimations && this.animations.frameData !== null) { + this.animations.destroy(); + } if(this._game.cache.getImage(key) !== null) { if(this._game.cache.isSpriteSheet(key) == false) { this._texture = this._game.cache.getImage(key); @@ -1906,9 +1919,9 @@ var Phaser; } else { this._texture = this._game.cache.getImage(key); this.animations.loadFrameData(this._game.cache.getFrameData(key)); - //this.collisionMask.width = this._texture.width; - //this.collisionMask.height = this._texture.height; - } + this.collisionMask.width = this.animations.currentFrame.width; + this.collisionMask.height = this.animations.currentFrame.height; + } this._dynamicTexture = false; } return this; @@ -1947,14 +1960,20 @@ var Phaser; * @return {boolean} Return true if bounds of this sprite intersects the given rectangle, otherwise return false. */ function (camera) { - if(this.scrollFactor.x !== 1.0 || this.scrollFactor.y !== 1.0) { - this._dx = this.frameBounds.x - (camera.x * this.scrollFactor.x); - this._dy = this.frameBounds.y - (camera.y * this.scrollFactor.x); + // Object fixed in place regardless of the camera scrolling? Then it's always visible + if(this.scrollFactor.x == 0 && this.scrollFactor.y == 0) { + return true; + } + // Otherwise, if it's scrolling perfectly in sync with the camera (1 to 1) then it's a simple bounds check on world coordinates + if(this.scrollFactor.x == 1 && this.scrollFactor.y == 1) { + return camera.intersects(this.frameBounds, this.frameBounds.length); + } else { + // Else apply the offsets + this._dx = (this.frameBounds.x - camera.x) * this.scrollFactor.x; + this._dy = (this.frameBounds.y - camera.y) * this.scrollFactor.y; this._dw = this.frameBounds.width * this.scale.x; this._dh = this.frameBounds.height * this.scale.y; return (camera.right > this._dx) && (camera.x < this._dx + this._dw) && (camera.bottom > this._dy) && (camera.y < this._dy + this._dh); - } else { - return camera.intersects(this.frameBounds, this.frameBounds.length); } }; Sprite.prototype.postUpdate = /** @@ -2005,8 +2024,8 @@ var Phaser; this._sy = 0; this._sw = this.frameBounds.width; this._sh = this.frameBounds.height; - this._dx = cameraOffsetX + (this.frameBounds.topLeft.x - camera.worldView.x); - this._dy = cameraOffsetY + (this.frameBounds.topLeft.y - camera.worldView.y); + this._dx = (cameraOffsetX * this.scrollFactor.x) + this.frameBounds.topLeft.x - (camera.worldView.x * this.scrollFactor.x); + this._dy = (cameraOffsetY * this.scrollFactor.y) + this.frameBounds.topLeft.y - (camera.worldView.y * this.scrollFactor.y); this._dw = this.frameBounds.width * this.scale.x; this._dh = this.frameBounds.height * this.scale.y; if(this.align == Phaser.GameObject.ALIGN_TOP_CENTER) { @@ -2039,10 +2058,10 @@ var Phaser; } } // Apply camera difference - if(this.scrollFactor.x !== 1.0 || this.scrollFactor.y !== 1.0) { - this._dx -= (camera.worldView.x * this.scrollFactor.x); - this._dy -= (camera.worldView.y * this.scrollFactor.y); - } + if(this.scrollFactor.x !== 1 || this.scrollFactor.y !== 1) { + //this._dx -= (camera.worldView.x * this.scrollFactor.x); + //this._dy -= (camera.worldView.y * this.scrollFactor.y); + } // Rotation - needs to work from origin point really, but for now from center if(this.angle !== 0 || this.rotationOffset !== 0 || this.flipped == true) { this.context.save(); @@ -2711,7 +2730,11 @@ var Phaser; }); Object.defineProperty(AnimationManager.prototype, "frameTotal", { get: function () { - return this._frameData.total; + if(this._frameData) { + return this._frameData.total; + } else { + return -1; + } }, enumerable: true, configurable: true @@ -2746,6 +2769,17 @@ var Phaser; enumerable: true, configurable: true }); + AnimationManager.prototype.destroy = /** + * Removes all related references + */ + function () { + this._anims = { + }; + this._frameData = null; + this._frameIndex = 0; + this.currentAnim = null; + this.currentFrame = null; + }; return AnimationManager; })(); Phaser.AnimationManager = AnimationManager; @@ -2971,9 +3005,6 @@ var Phaser; * * Your game only has one CameraManager instance and it's responsible for looking after, creating and destroying * all of the cameras in the world. -* -* TODO: If the Camera is larger than the Stage size then the rotation offset isn't correct -* TODO: Texture Repeat doesn't scroll, because it's part of the camera not the world, need to think about this more */ var Phaser; (function (Phaser) { @@ -4086,6 +4117,54 @@ var Phaser; })(Phaser || (Phaser = {})); /// /** +* Phaser - Polygon +* +*/ +var Phaser; +(function (Phaser) { + var Polygon = (function () { + /** + * A *convex* clockwise polygon + * @class Polygon + * @constructor + * @param {Vector2} pos A vector representing the origin of the polygon (all other points are relative to this one) + * @param {Array.} points An Array of vectors representing the points in the polygon, in clockwise order. + **/ + function Polygon(pos, points, parent) { + if (typeof pos === "undefined") { pos = new Phaser.Vector2(); } + if (typeof points === "undefined") { points = []; } + if (typeof parent === "undefined") { parent = null; } + this.pos = pos; + this.points = points; + this.parent = parent; + this.recalc(); + } + Polygon.prototype.recalc = /** + * Recalculate the edges and normals of the polygon. This + * MUST be called if the points array is modified at all and + * the edges or normals are to be accessed. + */ + function () { + var points = this.points; + var len = points.length; + this.edges = []; + this.normals = []; + for(var i = 0; i < len; i++) { + var p1 = points[i]; + var p2 = i < len - 1 ? points[i + 1] : points[0]; + var e = new Phaser.Vector2().copyFrom(p2).sub(p1); + var n = new Phaser.Vector2().copyFrom(e).perp().normalize(); + this.edges.push(e); + this.normals.push(n); + } + }; + return Polygon; + })(); + Phaser.Polygon = Polygon; +})(Phaser || (Phaser = {})); +/// +/// +/** * Phaser - Quad * * A Quad object is an area defined by its position, as indicated by its top-left corner (x,y) and width and height. @@ -4181,6 +4260,20 @@ var Phaser; if (typeof tolerance === "undefined") { tolerance = 0; } return !(quad.left > this.right + tolerance || quad.right < this.left - tolerance || quad.top > this.bottom + tolerance || quad.bottom < this.top - tolerance); }; + Quad.prototype.intersectsRaw = /** + * Determines whether the object specified intersects (overlaps) with the given values. + * @method intersectsProps + * @param {Number} left + * @param {Number} right + * @param {Number} top + * @param {Number} bottomt + * @param {Number} tolerance A tolerance value to allow for an intersection test with padding, default to 0 + * @return {Boolean} A value of true if the specified object intersects with this Quad; otherwise false. + **/ + function (left, right, top, bottom, tolerance) { + if (typeof tolerance === "undefined") { tolerance = 0; } + return !(left > this.right + tolerance || right < this.left - tolerance || top > this.bottom + tolerance || bottom < this.top - tolerance); + }; Quad.prototype.contains = /** * Determines whether the specified coordinates are contained within the region defined by this Quad object. * @method contains @@ -4212,6 +4305,19 @@ var Phaser; function (target) { return target.copyFrom(this); }; + Quad.prototype.toPolygon = /** + * Creates and returns a Polygon that is the same as this Quad. + * @method toPolygon + * @return {Polygon} A new Polygon that represents this quad. + **/ + function () { + return new Phaser.Polygon(new Phaser.Vector2(this.x, this.y), [ + new Phaser.Vector2(), + new Phaser.Vector2(this.width, 0), + new Phaser.Vector2(this.width, this.height), + new Phaser.Vector2(0, this.height) + ]); + }; Quad.prototype.toString = /** * Returns a string representation of this object. * @method toString @@ -4260,8 +4366,21 @@ var Phaser; * @type Number **/ this.y = 0; + this._pos = new Phaser.Vector2(); this.setTo(x, y, diameter); } + Object.defineProperty(Circle.prototype, "pos", { + get: /** + * The position of this Circle object represented by a Vector2 + * @property pos + * @type Vector2 + **/ + function () { + return this._pos.setTo(this.x, this.y); + }, + enumerable: true, + configurable: true + }); Object.defineProperty(Circle.prototype, "diameter", { get: /** * The diameter of the circle. The largest distance between any two points on the circle. The same as the radius * 2. @@ -4952,6 +5071,414 @@ var Phaser; Phaser.IntersectResult = IntersectResult; })(Phaser || (Phaser = {})); /// +/// +/** +* Phaser - Response +* +*/ +var Phaser; +(function (Phaser) { + var Response = (function () { + /** + * An object representing the result of an intersection. Contain information about: + * - The two objects participating in the intersection + * - The vector representing the minimum change necessary to extract the first object + * from the second one. + * - Whether the first object is entirely inside the second, or vice versa. + * + * @constructor + */ + function Response() { + this.a = null; + this.b = null; + this.overlapN = new Phaser.Vector2(); + this.overlapV = new Phaser.Vector2(); + this.clear(); + } + Response.prototype.clear = /** + * Set some values of the response back to their defaults. Call this between tests if + * you are going to reuse a single Response object for multiple intersection tests (recommented) + * + * @return {Response} This for chaining + */ + function () { + this.aInB = true; + this.bInA = true; + this.overlap = Number.MAX_VALUE; + return this; + }; + return Response; + })(); + Phaser.Response = Response; +})(Phaser || (Phaser = {})); +/// +/** +* Phaser - Vector2 +* +* A two dimensional vector. +* Contains methods and ideas from verlet-js by Sub Protocol, SAT.js by Jim Riecken and N by Metanet Software. +*/ +var Phaser; +(function (Phaser) { + var Vector2 = (function () { + /** + * Creates a new Vector2 object. + * @class Vector2 + * @constructor + * @param {Number} x The x position of the vector + * @param {Number} y The y position of the vector + * @return {Vector2} This object + **/ + function Vector2(x, y) { + if (typeof x === "undefined") { x = 0; } + if (typeof y === "undefined") { y = 0; } + this.x = x; + this.y = y; + } + Vector2.prototype.setTo = function (x, y) { + this.x = x; + this.y = y; + return this; + }; + Vector2.prototype.add = /** + * Add this vector to the given one and return the result. + * + * @param {Vector2} v The other Vector. + * @param {Vector2} The output Vector. + * @return {Vector2} The new Vector + */ + function (v, output) { + if (typeof output === "undefined") { output = new Vector2(); } + return output.setTo(this.x + v.x, this.y + v.y); + }; + Vector2.prototype.sub = /** + * Subtract this vector to the given one and return the result. + * + * @param {Vector2} v The other Vector. + * @param {Vector2} The output Vector. + * @return {Vector2} The new Vector + */ + function (v, output) { + if (typeof output === "undefined") { output = new Vector2(); } + return output.setTo(this.x - v.x, this.y - v.y); + }; + Vector2.prototype.mul = /** + * Multiply this vector with the given one and return the result. + * + * @param {Vector2} v The other Vector. + * @param {Vector2} The output Vector. + * @return {Vector2} The new Vector + */ + function (v, output) { + if (typeof output === "undefined") { output = new Vector2(); } + return output.setTo(this.x * v.x, this.y * v.y); + }; + Vector2.prototype.div = /** + * Divide this vector by the given one and return the result. + * + * @param {Vector2} v The other Vector. + * @param {Vector2} The output Vector. + * @return {Vector2} The new Vector + */ + function (v, output) { + if (typeof output === "undefined") { output = new Vector2(); } + return output.setTo(this.x / v.x, this.y / v.y); + }; + Vector2.prototype.scale = /** + * Scale this vector by the given values and return the result. + * + * @param {number} x The scaling factor in the x direction. + * @param {?number=} y The scaling factor in the y direction. If this + * is not specified, the x scaling factor will be used. + * @return {Vector} The new Vector + */ + function (x, y, output) { + if (typeof y === "undefined") { y = null; } + if (typeof output === "undefined") { output = new Vector2(); } + if(y === null) { + y = x; + } + return output.setTo(this.x * x, this.y * y); + }; + Vector2.prototype.perp = /** + * Rotate this vector by 90 degrees + * + * @return {Vector} This for chaining. + */ + function (output) { + if (typeof output === "undefined") { output = this; } + var x = this.x; + return output.setTo(this.y, -x); + }; + Vector2.prototype.mutableSet = // Same as copyFrom, used by VerletManager + function (v) { + this.x = v.x; + this.y = v.y; + return this; + }; + Vector2.prototype.mutableAdd = /** + * Add another vector to this one. + * + * @param {Vector} other The other Vector. + * @return {Vector} This for chaining. + */ + function (v) { + this.x += v.x; + this.y += v.y; + return this; + }; + Vector2.prototype.mutableSub = /** + * Subtract another vector from this one. + * + * @param {Vector} other The other Vector. + * @return {Vector} This for chaining. + */ + function (v) { + this.x -= v.x; + this.y -= v.y; + return this; + }; + Vector2.prototype.mutableMul = /** + * Multiply another vector with this one. + * + * @param {Vector} other The other Vector. + * @return {Vector} This for chaining. + */ + function (v) { + this.x *= v.x; + this.y *= v.y; + return this; + }; + Vector2.prototype.mutableDiv = /** + * Divide this vector by another one. + * + * @param {Vector} other The other Vector. + * @return {Vector} This for chaining. + */ + function (v) { + this.x /= v.x; + this.y /= v.y; + return this; + }; + Vector2.prototype.mutableScale = /** + * Scale this vector. + * + * @param {number} x The scaling factor in the x direction. + * @param {?number=} y The scaling factor in the y direction. If this + * is not specified, the x scaling factor will be used. + * @return {Vector} This for chaining. + */ + function (x, y) { + this.x *= x; + this.y *= y || x; + return this; + }; + Vector2.prototype.reverse = /** + * Reverse this vector. + * + * @return {Vector} This for chaining. + */ + function () { + this.x = -this.x; + this.y = -this.y; + return this; + }; + Vector2.prototype.edge = function (v, output) { + if (typeof output === "undefined") { output = new Vector2(); } + return this.sub(v, output); + }; + Vector2.prototype.equals = function (v) { + return this.x == v.x && this.y == v.y; + }; + Vector2.prototype.epsilonEquals = function (v, epsilon) { + return Math.abs(this.x - v.x) <= epsilon && Math.abs(this.y - v.y) <= epsilon; + }; + Vector2.prototype.length = /** + * Get the length of this vector. + * + * @return {number} The length of this vector. + */ + function () { + return Math.sqrt((this.x * this.x) + (this.y * this.y)); + }; + Vector2.prototype.length2 = /** + * Get the length^2 of this vector. + * + * @return {number} The length^2 of this vector. + */ + function () { + return (this.x * this.x) + (this.y * this.y); + }; + Vector2.prototype.distance = /** + * Get the distance between this vector and the given vector. + * + * @return {Vector2} v The vector to check + */ + function (v) { + return Math.sqrt(this.distance2(v)); + }; + Vector2.prototype.distance2 = /** + * Get the distance^2 between this vector and the given vector. + * + * @return {Vector2} v The vector to check + */ + function (v) { + return ((v.x - this.x) * (v.x - this.x)) + ((v.y - this.y) * (v.y - this.y)); + }; + Vector2.prototype.project = /** + * Project this vector on to another vector. + * + * @param {Vector} other The vector to project onto. + * @return {Vector} This for chaining. + */ + function (other) { + var amt = this.dot(other) / other.length2(); + if(amt != 0) { + this.x = amt * other.x; + this.y = amt * other.y; + } + return this; + }; + Vector2.prototype.projectN = /** + * Project this vector onto a vector of unit length. + * + * @param {Vector} other The unit vector to project onto. + * @return {Vector} This for chaining. + */ + function (other) { + var amt = this.dot(other); + if(amt != 0) { + this.x = amt * other.x; + this.y = amt * other.y; + } + return this; + }; + Vector2.prototype.reflect = /** + * Reflect this vector on an arbitrary axis. + * + * @param {Vector} axis The vector representing the axis. + * @return {Vector} This for chaining. + */ + function (axis) { + var x = this.x; + var y = this.y; + this.project(axis).scale(2); + this.x -= x; + this.y -= y; + return this; + }; + Vector2.prototype.reflectN = /** + * Reflect this vector on an arbitrary axis (represented by a unit vector) + * + * @param {Vector} axis The unit vector representing the axis. + * @return {Vector} This for chaining. + */ + function (axis) { + var x = this.x; + var y = this.y; + this.projectN(axis).scale(2); + this.x -= x; + this.y -= y; + return this; + }; + Vector2.prototype.getProjectionMagnitude = function (v) { + var den = v.dot(v); + if(den == 0) { + return 0; + } else { + return Math.abs(this.dot(v) / den); + } + }; + Vector2.prototype.direction = function (output) { + if (typeof output === "undefined") { output = new Vector2(); } + output.copyFrom(this); + return this.normalize(output); + }; + Vector2.prototype.normalRightHand = function (output) { + if (typeof output === "undefined") { output = this; } + return output.setTo(this.y * -1, this.x); + }; + Vector2.prototype.normalize = /** + * Normalize (make unit length) this vector. + * + * @return {Vector} This for chaining. + */ + function (output) { + if (typeof output === "undefined") { output = this; } + var m = this.length(); + if(m != 0) { + output.setTo(this.x / m, this.y / m); + } + return output; + }; + Vector2.prototype.getMagnitude = function () { + return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2)); + }; + Vector2.prototype.dot = /** + * Get the dot product of this vector against another. + * + * @param {Vector} other The vector to dot this one against. + * @return {number} The dot product. + */ + function (v) { + return ((this.x * v.x) + (this.y * v.y)); + }; + Vector2.prototype.cross = /** + * Get the cross product of this vector against another. + * + * @param {Vector} other The vector to cross this one against. + * @return {number} The cross product. + */ + function (v) { + return ((this.x * v.y) - (this.y * v.x)); + }; + Vector2.prototype.angle = /** + * Get the angle between this vector and the given vector. + * + * @return {Vector2} v The vector to check + */ + function (v) { + return Math.atan2(this.x * v.y - this.y * v.x, this.x * v.x + this.y * v.y); + }; + Vector2.prototype.angle2 = function (vLeft, vRight) { + return vLeft.sub(this).angle(vRight.sub(this)); + }; + Vector2.prototype.rotate = /** + * Rotate this vector around the origin to the given angle (theta) and return the result in a new Vector + * + * @return {Vector2} v The vector to check + */ + function (origin, theta, output) { + if (typeof output === "undefined") { output = new Vector2(); } + var x = this.x - origin.x; + var y = this.y - origin.y; + return output.setTo(x * Math.cos(theta) - y * Math.sin(theta) + origin.x, x * Math.sin(theta) + y * Math.cos(theta) + origin.y); + }; + Vector2.prototype.clone = function (output) { + if (typeof output === "undefined") { output = new Vector2(); } + return output.setTo(this.x, this.y); + }; + Vector2.prototype.copyFrom = function (v) { + this.x = v.x; + this.y = v.y; + return this; + }; + Vector2.prototype.copyTo = function (v) { + return v.setTo(this.x, this.y); + }; + Vector2.prototype.toString = /** + * Returns a string representation of this object. + * @method toString + * @return {string} a string representation of the object. + **/ + function () { + return "[{Vector2 (x=" + this.x + " y=" + this.y + ")}]"; + }; + return Vector2; + })(); + Phaser.Vector2 = Vector2; +})(Phaser || (Phaser = {})); +/// /** * Phaser - LinkedList * @@ -5338,6 +5865,8 @@ var Phaser; /// /// /// +/// +/// /// /** * Phaser - Collision @@ -5353,6 +5882,14 @@ var Phaser; */ function Collision(game) { this._game = game; + Collision.T_VECTORS = []; + for(var i = 0; i < 10; i++) { + Collision.T_VECTORS.push(new Phaser.Vector2()); + } + Collision.T_ARRAYS = []; + for(var i = 0; i < 5; i++) { + Collision.T_ARRAYS.push([]); + } } Collision.LEFT = 0x0001; Collision.RIGHT = 0x0010; @@ -5703,20 +6240,7 @@ var Phaser; output.result = ((circle1.radius + circle2.radius) * (circle1.radius + circle2.radius)) >= Collision.distanceSquared(circle1.x, circle1.y, circle2.x, circle2.y); return output; }; - Collision.circleToRectangle = /* - public static circleToQuad(circle: Circle, quad: Quad): bool { - - // Check if the center of the circle is within the Quad - if (quad.contains(circle.x, circle.y)) - { - return true; - } - - // Failing that let's check each line of the quad against the circle - return false; - } - */ - /** + Collision.circleToRectangle = /** * Checks if the Circle object intersects with the Rectangle and returns the result in an IntersectResult object. * @param circle The Circle object to check * @param rect The Rectangle object to check @@ -5779,7 +6303,6 @@ var Phaser; * @returns {boolean} Returns true if the objects were separated, otherwise false. */ function separate(object1, object2) { - console.log('sep o'); object1.collisionMask.update(); object2.collisionMask.update(); var separatedX = Collision.separateX(object1, object2); @@ -5793,6 +6316,7 @@ var Phaser; * @returns {boolean} Whether the objects in fact touched and were separated */ function separateTile(object, x, y, width, height, mass, collideLeft, collideRight, collideUp, collideDown, separateX, separateY) { + object.collisionMask.update(); var separatedX = Collision.separateTileX(object, x, y, width, height, mass, collideLeft, collideRight, separateX); var separatedY = Collision.separateTileY(object, x, y, width, height, mass, collideUp, collideDown, separateY); return separatedX || separatedY; @@ -5811,9 +6335,11 @@ var Phaser; // First, get the object delta var overlap = 0; var objDelta = object.x - object.last.x; + //var objDelta: number = object.collisionMask.deltaX; if(objDelta != 0) { // Check if the X hulls actually overlap var objDeltaAbs = (objDelta > 0) ? objDelta : -objDelta; + //var objDeltaAbs: number = object.collisionMask.deltaXAbs; var objBounds = new Phaser.Quad(object.x - ((objDelta > 0) ? objDelta : 0), object.last.y, object.width + ((objDelta > 0) ? objDelta : -objDelta), object.height); if((objBounds.x + objBounds.width > x) && (objBounds.x < x + width) && (objBounds.y + objBounds.height > y) && (objBounds.y < y + height)) { var maxOverlap = objDeltaAbs + Collision.OVERLAP_BIAS; @@ -5838,6 +6364,7 @@ var Phaser; // Then adjust their positions and velocities accordingly (if there was any overlap) if(overlap != 0) { if(separate == true) { + //console.log(' object.x = object.x - overlap; object.velocity.x = -(object.velocity.x * object.elasticity); } @@ -5898,6 +6425,113 @@ var Phaser; return false; } }; + Collision.NEWseparateTileX = /** + * Separates the two objects on their x axis + * @param object The GameObject to separate + * @param tile The Tile to separate + * @returns {boolean} Whether the objects in fact touched and were separated along the X axis. + */ + function NEWseparateTileX(object, x, y, width, height, mass, collideLeft, collideRight, separate) { + // Can't separate two immovable objects (tiles are always immovable) + if(object.immovable) { + return false; + } + // First, get the object delta + var overlap = 0; + if(object.collisionMask.deltaX != 0) { + // Check if the X hulls actually overlap + //var objDeltaAbs: number = (objDelta > 0) ? objDelta : -objDelta; + //var objBounds: Quad = new Quad(object.x - ((objDelta > 0) ? objDelta : 0), object.last.y, object.width + ((objDelta > 0) ? objDelta : -objDelta), object.height); + //if ((objBounds.x + objBounds.width > x) && (objBounds.x < x + width) && (objBounds.y + objBounds.height > y) && (objBounds.y < y + height)) + if(object.collisionMask.intersectsRaw(x, x + width, y, y + height)) { + var maxOverlap = object.collisionMask.deltaXAbs + Collision.OVERLAP_BIAS; + // If they did overlap (and can), figure out by how much and flip the corresponding flags + if(object.collisionMask.deltaX > 0) { + //overlap = object.x + object.width - x; + overlap = object.collisionMask.right - x; + if((overlap > maxOverlap) || !(object.allowCollisions & Collision.RIGHT) || collideLeft == false) { + overlap = 0; + } else { + object.touching |= Collision.RIGHT; + } + } else if(object.collisionMask.deltaX < 0) { + //overlap = object.x - width - x; + overlap = object.collisionMask.x - width - x; + if((-overlap > maxOverlap) || !(object.allowCollisions & Collision.LEFT) || collideRight == false) { + overlap = 0; + } else { + object.touching |= Collision.LEFT; + } + } + } + } + // Then adjust their positions and velocities accordingly (if there was any overlap) + if(overlap != 0) { + if(separate == true) { + object.x = object.x - overlap; + object.velocity.x = -(object.velocity.x * object.elasticity); + } + Collision.TILE_OVERLAP = true; + return true; + } else { + return false; + } + }; + Collision.NEWseparateTileY = /** + * Separates the two objects on their y axis + * @param object The first GameObject to separate + * @param tile The second GameObject to separate + * @returns {boolean} Whether the objects in fact touched and were separated along the Y axis. + */ + function NEWseparateTileY(object, x, y, width, height, mass, collideUp, collideDown, separate) { + // Can't separate two immovable objects (tiles are always immovable) + if(object.immovable) { + return false; + } + // First, get the two object deltas + var overlap = 0; + //var objDelta: number = object.y - object.last.y; + if(object.collisionMask.deltaY != 0) { + // Check if the Y hulls actually overlap + //var objDeltaAbs: number = (objDelta > 0) ? objDelta : -objDelta; + //var objBounds: Quad = new Quad(object.x, object.y - ((objDelta > 0) ? objDelta : 0), object.width, object.height + objDeltaAbs); + //if ((objBounds.x + objBounds.width > x) && (objBounds.x < x + width) && (objBounds.y + objBounds.height > y) && (objBounds.y < y + height)) + if(object.collisionMask.intersectsRaw(x, x + width, y, y + height)) { + //var maxOverlap: number = objDeltaAbs + Collision.OVERLAP_BIAS; + var maxOverlap = object.collisionMask.deltaYAbs + Collision.OVERLAP_BIAS; + // If they did overlap (and can), figure out by how much and flip the corresponding flags + if(object.collisionMask.deltaY > 0) { + //overlap = object.y + object.height - y; + overlap = object.collisionMask.bottom - y; + if((overlap > maxOverlap) || !(object.allowCollisions & Collision.DOWN) || collideUp == false) { + overlap = 0; + } else { + object.touching |= Collision.DOWN; + } + } else if(object.collisionMask.deltaY < 0) { + //overlap = object.y - height - y; + overlap = object.collisionMask.y - height - y; + if((-overlap > maxOverlap) || !(object.allowCollisions & Collision.UP) || collideDown == false) { + overlap = 0; + } else { + object.touching |= Collision.UP; + } + } + } + } + // TODO - with super low velocities you get lots of stuttering, set some kind of base minimum here + // Then adjust their positions and velocities accordingly (if there was any overlap) + if(overlap != 0) { + if(separate == true) { + object.y = object.y - overlap; + object.velocity.y = -(object.velocity.y * object.elasticity); + } + Collision.TILE_OVERLAP = true; + return true; + } else { + return false; + } + }; Collision.separateX = /** * Separates the two objects on their x axis * @param object1 The first GameObject to separate @@ -6033,159 +6667,6 @@ var Phaser; return false; } }; - Collision.OLDseparateX = /** - * Separates the two objects on their x axis - * @param object1 The first GameObject to separate - * @param object2 The second GameObject to separate - * @returns {boolean} Whether the objects in fact touched and were separated along the X axis. - */ - function OLDseparateX(object1, object2) { - // Can't separate two immovable objects - if(object1.immovable && object2.immovable) { - return false; - } - // First, get the two object deltas - var overlap = 0; - var obj1Delta = object1.x - object1.last.x; - var obj2Delta = object2.x - object2.last.x; - if(obj1Delta != obj2Delta) { - // Check if the X hulls actually overlap - var obj1DeltaAbs = (obj1Delta > 0) ? obj1Delta : -obj1Delta; - var obj2DeltaAbs = (obj2Delta > 0) ? obj2Delta : -obj2Delta; - var obj1Bounds = new Phaser.Quad(object1.x - ((obj1Delta > 0) ? obj1Delta : 0), object1.last.y, object1.width + ((obj1Delta > 0) ? obj1Delta : -obj1Delta), object1.height); - var obj2Bounds = new Phaser.Quad(object2.x - ((obj2Delta > 0) ? obj2Delta : 0), object2.last.y, object2.width + ((obj2Delta > 0) ? obj2Delta : -obj2Delta), object2.height); - if((obj1Bounds.x + obj1Bounds.width > obj2Bounds.x) && (obj1Bounds.x < obj2Bounds.x + obj2Bounds.width) && (obj1Bounds.y + obj1Bounds.height > obj2Bounds.y) && (obj1Bounds.y < obj2Bounds.y + obj2Bounds.height)) { - var maxOverlap = obj1DeltaAbs + obj2DeltaAbs + Collision.OVERLAP_BIAS; - // If they did overlap (and can), figure out by how much and flip the corresponding flags - if(obj1Delta > obj2Delta) { - overlap = object1.x + object1.width - object2.x; - if((overlap > maxOverlap) || !(object1.allowCollisions & Collision.RIGHT) || !(object2.allowCollisions & Collision.LEFT)) { - overlap = 0; - } else { - object1.touching |= Collision.RIGHT; - object2.touching |= Collision.LEFT; - } - } else if(obj1Delta < obj2Delta) { - overlap = object1.x - object2.width - object2.x; - if((-overlap > maxOverlap) || !(object1.allowCollisions & Collision.LEFT) || !(object2.allowCollisions & Collision.RIGHT)) { - overlap = 0; - } else { - object1.touching |= Collision.LEFT; - object2.touching |= Collision.RIGHT; - } - } - } - } - // Then adjust their positions and velocities accordingly (if there was any overlap) - if(overlap != 0) { - var obj1Velocity = object1.velocity.x; - var obj2Velocity = object2.velocity.x; - if(!object1.immovable && !object2.immovable) { - overlap *= 0.5; - object1.x = object1.x - overlap; - object2.x += overlap; - var obj1NewVelocity = Math.sqrt((obj2Velocity * obj2Velocity * object2.mass) / object1.mass) * ((obj2Velocity > 0) ? 1 : -1); - var obj2NewVelocity = Math.sqrt((obj1Velocity * obj1Velocity * object1.mass) / object2.mass) * ((obj1Velocity > 0) ? 1 : -1); - var average = (obj1NewVelocity + obj2NewVelocity) * 0.5; - obj1NewVelocity -= average; - obj2NewVelocity -= average; - object1.velocity.x = average + obj1NewVelocity * object1.elasticity; - object2.velocity.x = average + obj2NewVelocity * object2.elasticity; - } else if(!object1.immovable) { - object1.x = object1.x - overlap; - object1.velocity.x = obj2Velocity - obj1Velocity * object1.elasticity; - } else if(!object2.immovable) { - object2.x += overlap; - object2.velocity.x = obj1Velocity - obj2Velocity * object2.elasticity; - } - return true; - } else { - return false; - } - }; - Collision.OLDseparateY = /** - * Separates the two objects on their y axis - * @param object1 The first GameObject to separate - * @param object2 The second GameObject to separate - * @returns {boolean} Whether the objects in fact touched and were separated along the Y axis. - */ - function OLDseparateY(object1, object2) { - // Can't separate two immovable objects - if(object1.immovable && object2.immovable) { - return false; - } - // First, get the two object deltas - var overlap = 0; - var obj1Delta = object1.y - object1.last.y; - var obj2Delta = object2.y - object2.last.y; - if(obj1Delta != obj2Delta) { - // Check if the Y hulls actually overlap - var obj1DeltaAbs = (obj1Delta > 0) ? obj1Delta : -obj1Delta; - var obj2DeltaAbs = (obj2Delta > 0) ? obj2Delta : -obj2Delta; - var obj1Bounds = new Phaser.Quad(object1.x, object1.y - ((obj1Delta > 0) ? obj1Delta : 0), object1.width, object1.height + obj1DeltaAbs); - var obj2Bounds = new Phaser.Quad(object2.x, object2.y - ((obj2Delta > 0) ? obj2Delta : 0), object2.width, object2.height + obj2DeltaAbs); - console.log(obj1Bounds.toString(), obj2Bounds.toString()); - if((obj1Bounds.x + obj1Bounds.width > obj2Bounds.x) && (obj1Bounds.x < obj2Bounds.x + obj2Bounds.width) && (obj1Bounds.y + obj1Bounds.height > obj2Bounds.y) && (obj1Bounds.y < obj2Bounds.y + obj2Bounds.height)) { - var maxOverlap = obj1DeltaAbs + obj2DeltaAbs + Collision.OVERLAP_BIAS; - console.log('max33', maxOverlap, obj1Delta, obj2Delta, obj1DeltaAbs, obj2DeltaAbs); - // If they did overlap (and can), figure out by how much and flip the corresponding flags - if(obj1Delta > obj2Delta) { - overlap = object1.y + object1.height - object2.y; - if((overlap > maxOverlap) || !(object1.allowCollisions & Collision.DOWN) || !(object2.allowCollisions & Collision.UP)) { - overlap = 0; - } else { - object1.touching |= Collision.DOWN; - object2.touching |= Collision.UP; - } - } else if(obj1Delta < obj2Delta) { - overlap = object1.y - object2.height - object2.y; - if((-overlap > maxOverlap) || !(object1.allowCollisions & Collision.UP) || !(object2.allowCollisions & Collision.DOWN)) { - overlap = 0; - } else { - object1.touching |= Collision.UP; - object2.touching |= Collision.DOWN; - } - } - } - } - // Then adjust their positions and velocities accordingly (if there was any overlap) - if(overlap != 0) { - console.log('y overlap', overlap); - var obj1Velocity = object1.velocity.y; - var obj2Velocity = object2.velocity.y; - if(!object1.immovable && !object2.immovable) { - overlap *= 0.5; - object1.y = object1.y - overlap; - object2.y += overlap; - var obj1NewVelocity = Math.sqrt((obj2Velocity * obj2Velocity * object2.mass) / object1.mass) * ((obj2Velocity > 0) ? 1 : -1); - var obj2NewVelocity = Math.sqrt((obj1Velocity * obj1Velocity * object1.mass) / object2.mass) * ((obj1Velocity > 0) ? 1 : -1); - var average = (obj1NewVelocity + obj2NewVelocity) * 0.5; - obj1NewVelocity -= average; - obj2NewVelocity -= average; - object1.velocity.y = average + obj1NewVelocity * object1.elasticity; - object2.velocity.y = average + obj2NewVelocity * object2.elasticity; - } else if(!object1.immovable) { - object1.y = object1.y - overlap; - object1.velocity.y = obj2Velocity - obj1Velocity * object1.elasticity; - // This is special case code that handles things like horizontal moving platforms you can ride - if(object2.active && object2.moves && (obj1Delta > obj2Delta)) { - object1.x += object2.x - object2.last.x; - } - console.log('y2', object1.y, object1.velocity.y); - } else if(!object2.immovable) { - object2.y += overlap; - object2.velocity.y = obj1Velocity - obj2Velocity * object2.elasticity; - // This is special case code that handles things like horizontal moving platforms you can ride - if(object1.active && object1.moves && (obj1Delta < obj2Delta)) { - object2.x += object1.x - object1.last.x; - } - console.log('y3', object2.y, object2.velocity.y); - } - return true; - } else { - return false; - } - }; Collision.distance = /** * Returns the distance between the two given coordinates. * @param x1 The X value of the first coordinate @@ -6208,6 +6689,369 @@ var Phaser; function distanceSquared(x1, y1, x2, y2) { return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); }; + Collision.flattenPointsOn = // SAT + /** + * Flattens the specified array of points onto a unit vector axis, + * resulting in a one dimensional range of the minimum and + * maximum value on that axis. + * + * @param {Array.} points The points to flatten. + * @param {Vector} normal The unit vector axis to flatten on. + * @param {Array.} result An array. After calling this function, + * result[0] will be the minimum value, + * result[1] will be the maximum value. + */ + function flattenPointsOn(points, normal, result) { + var min = Number.MAX_VALUE; + var max = -Number.MAX_VALUE; + var len = points.length; + for(var i = 0; i < len; i++) { + // Get the magnitude of the projection of the point onto the normal + var dot = points[i].dot(normal); + if(dot < min) { + min = dot; + } + if(dot > max) { + max = dot; + } + } + result[0] = min; + result[1] = max; + }; + Collision.isSeparatingAxis = /** + * Check whether two convex clockwise polygons are separated by the specified + * axis (must be a unit vector). + * + * @param {Vector} aPos The position of the first polygon. + * @param {Vector} bPos The position of the second polygon. + * @param {Array.} aPoints The points in the first polygon. + * @param {Array.} bPoints The points in the second polygon. + * @param {Vector} axis The axis (unit sized) to test against. The points of both polygons + * will be projected onto this axis. + * @param {Response=} response A Response object (optional) which will be populated + * if the axis is not a separating axis. + * @return {boolean} true if it is a separating axis, false otherwise. If false, + * and a response is passed in, information about how much overlap and + * the direction of the overlap will be populated. + */ + function isSeparatingAxis(aPos, bPos, aPoints, bPoints, axis, response) { + if (typeof response === "undefined") { response = null; } + var rangeA = Collision.T_ARRAYS.pop(); + var rangeB = Collision.T_ARRAYS.pop(); + // Get the magnitude of the offset between the two polygons + var offsetV = Collision.T_VECTORS.pop().copyFrom(bPos).sub(aPos); + var projectedOffset = offsetV.dot(axis); + // Project the polygons onto the axis. + Collision.flattenPointsOn(aPoints, axis, rangeA); + Collision.flattenPointsOn(bPoints, axis, rangeB); + // Move B's range to its position relative to A. + rangeB[0] += projectedOffset; + rangeB[1] += projectedOffset; + // Check if there is a gap. If there is, this is a separating axis and we can stop + if(rangeA[0] > rangeB[1] || rangeB[0] > rangeA[1]) { + Collision.T_VECTORS.push(offsetV); + Collision.T_ARRAYS.push(rangeA); + Collision.T_ARRAYS.push(rangeB); + return true; + } + // If we're calculating a response, calculate the overlap. + if(response) { + var overlap = 0; + // A starts further left than B + if(rangeA[0] < rangeB[0]) { + response.aInB = false; + // A ends before B does. We have to pull A out of B + if(rangeA[1] < rangeB[1]) { + overlap = rangeA[1] - rangeB[0]; + response.bInA = false; + // B is fully inside A. Pick the shortest way out. + } else { + var option1 = rangeA[1] - rangeB[0]; + var option2 = rangeB[1] - rangeA[0]; + overlap = option1 < option2 ? option1 : -option2; + } + // B starts further left than A + } else { + response.bInA = false; + // B ends before A ends. We have to push A out of B + if(rangeA[1] > rangeB[1]) { + overlap = rangeA[0] - rangeB[1]; + response.aInB = false; + // A is fully inside B. Pick the shortest way out. + } else { + var option1 = rangeA[1] - rangeB[0]; + var option2 = rangeB[1] - rangeA[0]; + overlap = option1 < option2 ? option1 : -option2; + } + } + // If this is the smallest amount of overlap we've seen so far, set it as the minimum overlap. + var absOverlap = Math.abs(overlap); + if(absOverlap < response.overlap) { + response.overlap = absOverlap; + response.overlapN.copyFrom(axis); + if(overlap < 0) { + response.overlapN.reverse(); + } + } + } + Collision.T_VECTORS.push(offsetV); + Collision.T_ARRAYS.push(rangeA); + Collision.T_ARRAYS.push(rangeB); + return false; + }; + Collision.LEFT_VORNOI_REGION = -1; + Collision.MIDDLE_VORNOI_REGION = 0; + Collision.RIGHT_VORNOI_REGION = 1; + Collision.vornoiRegion = /** + * Calculates which Vornoi region a point is on a line segment. + * It is assumed that both the line and the point are relative to (0, 0) + * + * | (0) | + * (-1) [0]--------------[1] (1) + * | (0) | + * + * @param {Vector} line The line segment. + * @param {Vector} point The point. + * @return {number} LEFT_VORNOI_REGION (-1) if it is the left region, + * MIDDLE_VORNOI_REGION (0) if it is the middle region, + * RIGHT_VORNOI_REGION (1) if it is the right region. + */ + function vornoiRegion(line, point) { + var len2 = line.length2(); + var dp = point.dot(line); + if(dp < 0) { + return Collision.LEFT_VORNOI_REGION; + } else if(dp > len2) { + return Collision.RIGHT_VORNOI_REGION; + } else { + return Collision.MIDDLE_VORNOI_REGION; + } + }; + Collision.testCircleCircle = /** + * Check if two circles intersect. + * + * @param {Circle} a The first circle. + * @param {Circle} b The second circle. + * @param {Response=} response Response object (optional) that will be populated if + * the circles intersect. + * @return {boolean} true if the circles intersect, false if they don't. + */ + function testCircleCircle(a, b, response) { + if (typeof response === "undefined") { response = null; } + var differenceV = Collision.T_VECTORS.pop().copyFrom(b.pos).sub(a.pos); + var totalRadius = a.radius + b.radius; + var totalRadiusSq = totalRadius * totalRadius; + var distanceSq = differenceV.length2(); + if(distanceSq > totalRadiusSq) { + // They do not intersect + Collision.T_VECTORS.push(differenceV); + return false; + } + // They intersect. If we're calculating a response, calculate the overlap. + if(response) { + var dist = Math.sqrt(distanceSq); + response.a = a; + response.b = b; + response.overlap = totalRadius - dist; + response.overlapN.copyFrom(differenceV.normalize()); + response.overlapV.copyFrom(differenceV).scale(response.overlap); + response.aInB = a.radius <= b.radius && dist <= b.radius - a.radius; + response.bInA = b.radius <= a.radius && dist <= a.radius - b.radius; + } + Collision.T_VECTORS.push(differenceV); + return true; + }; + Collision.testPolygonCircle = /** + * Check if a polygon and a circle intersect. + * + * @param {Polygon} polygon The polygon. + * @param {Circle} circle The circle. + * @param {Response=} response Response object (optional) that will be populated if + * they interset. + * @return {boolean} true if they intersect, false if they don't. + */ + function testPolygonCircle(polygon, circle, response) { + if (typeof response === "undefined") { response = null; } + var circlePos = Collision.T_VECTORS.pop().copyFrom(circle.pos).sub(polygon.pos); + var radius = circle.radius; + var radius2 = radius * radius; + var points = polygon.points; + var len = points.length; + var edge = Collision.T_VECTORS.pop(); + var point = Collision.T_VECTORS.pop(); + // For each edge in the polygon + for(var i = 0; i < len; i++) { + var next = i === len - 1 ? 0 : i + 1; + var prev = i === 0 ? len - 1 : i - 1; + var overlap = 0; + var overlapN = null; + // Get the edge + edge.copyFrom(polygon.edges[i]); + // Calculate the center of the cirble relative to the starting point of the edge + point.copyFrom(circlePos).sub(points[i]); + // If the distance between the center of the circle and the point + // is bigger than the radius, the polygon is definitely not fully in + // the circle. + if(response && point.length2() > radius2) { + response.aInB = false; + } + // Calculate which Vornoi region the center of the circle is in. + var region = Collision.vornoiRegion(edge, point); + if(region === Collision.LEFT_VORNOI_REGION) { + // Need to make sure we're in the RIGHT_VORNOI_REGION of the previous edge. + edge.copyFrom(polygon.edges[prev]); + // Calculate the center of the circle relative the starting point of the previous edge + var point2 = Collision.T_VECTORS.pop().copyFrom(circlePos).sub(points[prev]); + region = Collision.vornoiRegion(edge, point2); + if(region === Collision.RIGHT_VORNOI_REGION) { + // It's in the region we want. Check if the circle intersects the point. + var dist = point.length2(); + if(dist > radius) { + // No intersection + Collision.T_VECTORS.push(circlePos); + Collision.T_VECTORS.push(edge); + Collision.T_VECTORS.push(point); + Collision.T_VECTORS.push(point2); + return false; + } else if(response) { + // It intersects, calculate the overlap + response.bInA = false; + overlapN = point.normalize(); + overlap = radius - dist; + } + } + Collision.T_VECTORS.push(point2); + } else if(region === Collision.RIGHT_VORNOI_REGION) { + // Need to make sure we're in the left region on the next edge + edge.copyFrom(polygon.edges[next]); + // Calculate the center of the circle relative to the starting point of the next edge + point.copyFrom(circlePos).sub(points[next]); + region = Collision.vornoiRegion(edge, point); + if(region === Collision.LEFT_VORNOI_REGION) { + // It's in the region we want. Check if the circle intersects the point. + var dist = point.length2(); + if(dist > radius) { + // No intersection + Collision.T_VECTORS.push(circlePos); + Collision.T_VECTORS.push(edge); + Collision.T_VECTORS.push(point); + return false; + } else if(response) { + // It intersects, calculate the overlap + response.bInA = false; + overlapN = point.normalize(); + overlap = radius - dist; + } + } + // MIDDLE_VORNOI_REGION + } else { + // Need to check if the circle is intersecting the edge, + // Change the edge into its "edge normal". + var normal = edge.perp().normalize(); + // Find the perpendicular distance between the center of the + // circle and the edge. + var dist = point.dot(normal); + var distAbs = Math.abs(dist); + // If the circle is on the outside of the edge, there is no intersection + if(dist > 0 && distAbs > radius) { + Collision.T_VECTORS.push(circlePos); + Collision.T_VECTORS.push(normal); + Collision.T_VECTORS.push(point); + return false; + } else if(response) { + // It intersects, calculate the overlap. + overlapN = normal; + overlap = radius - dist; + // If the center of the circle is on the outside of the edge, or part of the + // circle is on the outside, the circle is not fully inside the polygon. + if(dist >= 0 || overlap < 2 * radius) { + response.bInA = false; + } + } + } + // If this is the smallest overlap we've seen, keep it. + // (overlapN may be null if the circle was in the wrong Vornoi region) + if(overlapN && response && Math.abs(overlap) < Math.abs(response.overlap)) { + response.overlap = overlap; + response.overlapN.copyFrom(overlapN); + } + } + // Calculate the final overlap vector - based on the smallest overlap. + if(response) { + response.a = polygon; + response.b = circle; + response.overlapV.copyFrom(response.overlapN).scale(response.overlap); + } + Collision.T_VECTORS.push(circlePos); + Collision.T_VECTORS.push(edge); + Collision.T_VECTORS.push(point); + return true; + }; + Collision.testCirclePolygon = /** + * Check if a circle and a polygon intersect. + * + * NOTE: This runs slightly slower than polygonCircle as it just + * runs polygonCircle and reverses everything at the end. + * + * @param {Circle} circle The circle. + * @param {Polygon} polygon The polygon. + * @param {Response=} response Response object (optional) that will be populated if + * they interset. + * @return {boolean} true if they intersect, false if they don't. + */ + function testCirclePolygon(circle, polygon, response) { + if (typeof response === "undefined") { response = null; } + var result = Collision.testPolygonCircle(polygon, circle, response); + if(result && response) { + // Swap A and B in the response. + var a = response.a; + var aInB = response.aInB; + response.overlapN.reverse(); + response.overlapV.reverse(); + response.a = response.b; + response.b = a; + response.aInB = response.bInA; + response.bInA = aInB; + } + return result; + }; + Collision.testPolygonPolygon = /** + * Checks whether two convex, clockwise polygons intersect. + * + * @param {Polygon} a The first polygon. + * @param {Polygon} b The second polygon. + * @param {Response=} response Response object (optional) that will be populated if + * they interset. + * @return {boolean} true if they intersect, false if they don't. + */ + function testPolygonPolygon(a, b, response) { + if (typeof response === "undefined") { response = null; } + var aPoints = a.points; + var aLen = aPoints.length; + var bPoints = b.points; + var bLen = bPoints.length; + // If any of the edge normals of A is a separating axis, no intersection. + for(var i = 0; i < aLen; i++) { + if(Collision.isSeparatingAxis(a.pos, b.pos, aPoints, bPoints, a.normals[i], response)) { + return false; + } + } + // If any of the edge normals of B is a separating axis, no intersection. + for(var i = 0; i < bLen; i++) { + if(Collision.isSeparatingAxis(a.pos, b.pos, aPoints, bPoints, b.normals[i], response)) { + return false; + } + } + // Since none of the edge normals of A or B are a separating axis, there is an intersection + // and we've already calculated the smallest overlap (in isSeparatingAxis). Calculate the + // final overlap vector. + if(response) { + response.a = a; + response.b = b; + response.overlapV.copyFrom(response.overlapN).scale(response.overlap); + } + return true; + }; return Collision; })(); Phaser.Collision = Collision; @@ -7345,26 +8189,233 @@ var Phaser; return Math.sqrt(dx * dx + dy * dy); }; GameMath.prototype.rotatePoint = /** - * Rotates a point around the x/y coordinates given to the desired angle + * Rotates the point around the x/y coordinates given to the desired angle and distance + * @param point {Object} Any object with exposed x and y properties * @param x {number} The x coordinate of the anchor point * @param y {number} The y coordinate of the anchor point - * @param angle {number} The angle of the rotation in radians - * @param point {Point} The point object to perform the rotation on + * @param {Number} angle The angle in radians (unless asDegrees is true) to return the point from. + * @param {Boolean} asDegrees Is the given angle in radians (false) or degrees (true)? + * @param {Number} distance An optional distance constraint between the point and the anchor * @return The modified point object */ - function (x, y, angle, point) { - var s = Math.sin(angle); - var c = Math.cos(angle); - point.x -= x; - point.y -= y; - var newX = point.x * c - point.y * s; - var newY = point.x * s - point.y * c; - return point.setTo(newX + x, newY + y); + function (point, x1, y1, angle, asDegrees, distance) { + if (typeof asDegrees === "undefined") { asDegrees = false; } + if (typeof distance === "undefined") { distance = null; } + if(asDegrees) { + angle = angle * GameMath.DEG_TO_RAD; + } + // Get distance from origin to the point + if(distance === null) { + distance = Math.sqrt(((x1 - point.x) * (x1 - point.x)) + ((y1 - point.y) * (y1 - point.y))); + } + point.x = x1 + distance * Math.cos(angle); + point.y = y1 + distance * Math.sin(angle); + return point; }; return GameMath; })(); Phaser.GameMath = GameMath; })(Phaser || (Phaser = {})); +/// +/** +* Phaser - GameObjectFactory +* +* A quick way to create new world objects and add existing objects to the current world. +*/ +var Phaser; +(function (Phaser) { + var GameObjectFactory = (function () { + /** + * GameObjectFactory constructor + * @param game {Game} A reference to the current Game. + */ + function GameObjectFactory(game) { + this._game = game; + this._world = this._game.world; + } + GameObjectFactory.prototype.camera = /** + * Create a new camera with specific position and size. + * + * @param x {number} X position of the new camera. + * @param y {number} Y position of the new camera. + * @param width {number} Width of the new camera. + * @param height {number} Height of the new camera. + * @returns {Camera} The newly created camera object. + */ + function (x, y, width, height) { + return this._world.createCamera(x, y, width, height); + }; + GameObjectFactory.prototype.geomSprite = /** + * Create a new GeomSprite with specific position. + * + * @param x {number} X position of the new geom sprite. + * @param y {number} Y position of the new geom sprite. + * @returns {GeomSprite} The newly created geom sprite object. + */ + function (x, y) { + return this._world.createGeomSprite(x, y); + }; + GameObjectFactory.prototype.sprite = /** + * Create a new Sprite with specific position and sprite sheet key. + * + * @param x {number} X position of the new sprite. + * @param y {number} Y position of the new sprite. + * @param key {string} Optional, key for the sprite sheet you want it to use. + * @returns {Sprite} The newly created sprite object. + */ + function (x, y, key) { + if (typeof key === "undefined") { key = ''; } + return this._world.createSprite(x, y, key); + }; + GameObjectFactory.prototype.dynamicTexture = /** + * Create a new DynamicTexture with specific size. + * + * @param width {number} Width of the texture. + * @param height {number} Height of the texture. + * @returns {DynamicTexture} The newly created dynamic texture object. + */ + function (width, height) { + return this._world.createDynamicTexture(width, height); + }; + GameObjectFactory.prototype.group = /** + * Create a new object container. + * + * @param maxSize {number} Optional, capacity of this group. + * @returns {Group} The newly created group. + */ + function (maxSize) { + if (typeof maxSize === "undefined") { maxSize = 0; } + return this._world.createGroup(maxSize); + }; + GameObjectFactory.prototype.particle = /** + * Create a new Particle. + * + * @return {Particle} The newly created particle object. + */ + function () { + return this._world.createParticle(); + }; + GameObjectFactory.prototype.emitter = /** + * Create a new Emitter. + * + * @param x {number} Optional, x position of the emitter. + * @param y {number} Optional, y position of the emitter. + * @param size {number} Optional, size of this emitter. + * @return {Emitter} The newly created emitter object. + */ + function (x, y, size) { + if (typeof x === "undefined") { x = 0; } + if (typeof y === "undefined") { y = 0; } + if (typeof size === "undefined") { size = 0; } + return this._world.createEmitter(x, y, size); + }; + GameObjectFactory.prototype.scrollZone = /** + * Create a new ScrollZone object with image key, position and size. + * + * @param key {string} Key to a image you wish this object to use. + * @param x {number} X position of this object. + * @param y {number} Y position of this object. + * @param width number} Width of this object. + * @param height {number} Height of this object. + * @returns {ScrollZone} The newly created scroll zone object. + */ + function (key, x, y, width, height) { + if (typeof x === "undefined") { x = 0; } + if (typeof y === "undefined") { y = 0; } + if (typeof width === "undefined") { width = 0; } + if (typeof height === "undefined") { height = 0; } + return this._world.createScrollZone(key, x, y, width, height); + }; + GameObjectFactory.prototype.tilemap = /** + * Create a new Tilemap. + * + * @param key {string} Key for tileset image. + * @param mapData {string} Data of this tilemap. + * @param format {number} Format of map data. (Tilemap.FORMAT_CSV or Tilemap.FORMAT_TILED_JSON) + * @param [resizeWorld] {boolean} resize the world to make same as tilemap? + * @param [tileWidth] {number} width of each tile. + * @param [tileHeight] {number} height of each tile. + * @return {Tilemap} The newly created tilemap object. + */ + function (key, mapData, format, resizeWorld, tileWidth, tileHeight) { + if (typeof resizeWorld === "undefined") { resizeWorld = true; } + if (typeof tileWidth === "undefined") { tileWidth = 0; } + if (typeof tileHeight === "undefined") { tileHeight = 0; } + return this._world.createTilemap(key, mapData, format, resizeWorld, tileWidth, tileHeight); + }; + GameObjectFactory.prototype.tween = /** + * Create a tween object for a specific object. + * + * @param obj Object you wish the tween will affect. + * @return {Phaser.Tween} The newly created tween object. + */ + function (obj) { + return this._game.tweens.create(obj); + }; + GameObjectFactory.prototype.existingSprite = /** + * Add an existing Sprite to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param sprite The Sprite to add to the Game World + * @return {Phaser.Sprite} The Sprite object + */ + function (sprite) { + return this._world.group.add(sprite); + }; + GameObjectFactory.prototype.existingGeomSprite = /** + * Add an existing GeomSprite to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param sprite The GeomSprite to add to the Game World + * @return {Phaser.GeomSprite} The GeomSprite object + */ + function (sprite) { + return this._world.group.add(sprite); + }; + GameObjectFactory.prototype.existingEmitter = /** + * Add an existing Emitter to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param emitter The Emitter to add to the Game World + * @return {Phaser.Emitter} The Emitter object + */ + function (emitter) { + return this._world.group.add(emitter); + }; + GameObjectFactory.prototype.existingScrollZone = /** + * Add an existing ScrollZone to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param scrollZone The ScrollZone to add to the Game World + * @return {Phaser.ScrollZone} The ScrollZone object + */ + function (scrollZone) { + return this._world.group.add(scrollZone); + }; + GameObjectFactory.prototype.existingTilemap = /** + * Add an existing Tilemap to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param tilemap The Tilemap to add to the Game World + * @return {Phaser.Tilemap} The Tilemap object + */ + function (tilemap) { + return this._world.group.add(tilemap); + }; + GameObjectFactory.prototype.existingTween = /** + * Add an existing Tween to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param tween The Tween to add to the Game World + * @return {Phaser.Tween} The Tween object + */ + function (tween) { + return this._game.tweens.add(tween); + }; + return GameObjectFactory; + })(); + Phaser.GameObjectFactory = GameObjectFactory; +})(Phaser || (Phaser = {})); /// /// /** @@ -7398,9 +8449,36 @@ var Phaser; this._maxSize = MaxSize; this._marker = 0; this._sortIndex = null; + this.cameraBlacklist = []; } Group.ASCENDING = -1; Group.DESCENDING = 1; + Group.prototype.hideFromCamera = /** + * If you do not wish this object to be visible to a specific camera, pass the camera here. + * + * @param camera {Camera} The specific camera. + */ + function (camera) { + if(this.cameraBlacklist.indexOf(camera.ID) == -1) { + this.cameraBlacklist.push(camera.ID); + } + }; + Group.prototype.showToCamera = /** + * Make this object only visible to a specific camera. + * + * @param camera {Camera} The camera you wish it to be visible. + */ + function (camera) { + if(this.cameraBlacklist.indexOf(camera.ID) !== -1) { + this.cameraBlacklist.slice(this.cameraBlacklist.indexOf(camera.ID), 1); + } + }; + Group.prototype.clearCameraList = /** + * This clears the camera black list, making the GameObject visible to all cameras. + */ + function () { + this.cameraBlacklist.length = 0; + }; Group.prototype.destroy = /** * Override this function to handle any deleting or "shutdown" type operations you might need, * such as removing traditional Flash children like Basic objects. @@ -7443,6 +8521,9 @@ var Phaser; */ function (camera, cameraOffsetX, cameraOffsetY, forceRender) { if (typeof forceRender === "undefined") { forceRender = false; } + if(this.cameraBlacklist.indexOf(camera.ID) !== -1) { + return; + } if(this.ignoreGlobalRender && forceRender == false) { return; } @@ -7945,6 +9026,11 @@ var Phaser; * @param callback {function} This will be called when assets completely loaded. */ function Loader(game, callback) { + /** + * The crossOrigin value applied to loaded images + * @type {string} + */ + this.crossOrigin = ''; this._game = game; this._gameCreateComplete = callback; this._keys = []; @@ -8174,6 +9260,7 @@ var Phaser; file.data.onerror = function () { return _this.fileError(file.key); }; + file.data.crossOrigin = this.crossOrigin; file.data.src = file.url; break; case 'audio': @@ -8844,7 +9931,7 @@ var Phaser; /** * Phaser * -* v0.9.5 - April 28th 2013 +* v0.9.6 - May 21st 2013 * * A small and feature-packed 2D canvas game framework born from the firey pits of Flixel and Kiwi. * @@ -8858,7 +9945,7 @@ var Phaser; */ var Phaser; (function (Phaser) { - Phaser.VERSION = 'Phaser version 0.9.5'; + Phaser.VERSION = 'Phaser version 0.9.6'; })(Phaser || (Phaser = {})); /// /** @@ -9185,7 +10272,7 @@ var Phaser; * Start color fading cycle. */ function () { - this._fade = this._game.createTween(this._color2); + this._fade = this._game.add.tween(this._color2); this._fade.to({ r: Math.random() * 250, g: Math.random() * 250, @@ -9274,7 +10361,7 @@ var Phaser; * Start fadeOut effect. */ function () { - this._fade = this._game.createTween(this._color); + this._fade = this._game.add.tween(this._color); this._fade.to({ r: 50, g: 50, @@ -9287,7 +10374,7 @@ var Phaser; * Start fadeIn effect. */ function () { - this._fade = this._game.createTween(this._color); + this._fade = this._game.add.tween(this._color); this._fade.to({ r: 255, g: 255, @@ -9414,18 +10501,24 @@ var Phaser; } if(event.type == 'blur' || document['hidden'] == true || document['webkitHidden'] == true) { if(this._game.paused == false) { - this._pauseScreen.onPaused(); - this.saveCanvasValues(); - this._game.paused = true; + this.pauseGame(); } } else { if(this._game.paused == true) { - this._pauseScreen.onResume(); - this._game.paused = false; - this.restoreCanvasValues(); + this.resumeGame(); } } }; + Stage.prototype.pauseGame = function () { + this._pauseScreen.onPaused(); + this.saveCanvasValues(); + this._game.paused = true; + }; + Stage.prototype.resumeGame = function () { + this._pauseScreen.onResume(); + this.restoreCanvasValues(); + this._game.paused = false; + }; Stage.prototype.getOffset = /** * Get the DOM offset values of the given element */ @@ -10357,134 +11450,6 @@ var Phaser; })(); Phaser.TweenManager = TweenManager; })(Phaser || (Phaser = {})); -/// -/** -* Phaser - Vector2 -* -* A simple 2-dimensional vector class. Based on the one included with verlet-js by Sub Protocol released under MIT -*/ -var Phaser; -(function (Phaser) { - var Vector2 = (function () { - /** - * Creates a new Vector2 object. - * @class Vector2 - * @constructor - * @param {Number} x The x coordinate of vector2 - * @param {Number} y The y coordinate of vector2 - * @return {Vector2} This object - **/ - function Vector2(x, y) { - if (typeof x === "undefined") { x = 0; } - if (typeof y === "undefined") { y = 0; } - this.x = x; - this.y = y; - } - Vector2.prototype.setTo = function (x, y) { - this.x = x; - this.y = y; - return this; - }; - Vector2.prototype.add = function (v, output) { - if (typeof output === "undefined") { output = new Vector2(); } - return output.setTo(this.x + v.x, this.y + v.y); - }; - Vector2.prototype.sub = function (v, output) { - if (typeof output === "undefined") { output = new Vector2(); } - return output.setTo(this.x - v.x, this.y - v.y); - }; - Vector2.prototype.mul = function (v, output) { - if (typeof output === "undefined") { output = new Vector2(); } - return output.setTo(this.x * v.x, this.y * v.y); - }; - Vector2.prototype.div = function (v, output) { - if (typeof output === "undefined") { output = new Vector2(); } - return output.setTo(this.x / v.x, this.y / v.y); - }; - Vector2.prototype.scale = function (coef, output) { - if (typeof output === "undefined") { output = new Vector2(); } - return output.setTo(this.x * coef, this.y * coef); - }; - Vector2.prototype.mutableSet = function (v) { - this.x = v.x; - this.y = v.y; - return this; - }; - Vector2.prototype.mutableAdd = function (v) { - this.x += v.x; - this.y += v.y; - return this; - }; - Vector2.prototype.mutableSub = function (v) { - this.x -= v.x; - this.y -= v.y; - return this; - }; - Vector2.prototype.mutableMul = function (v) { - this.x *= v.x; - this.y *= v.y; - return this; - }; - Vector2.prototype.mutableDiv = function (v) { - this.x /= v.x; - this.y /= v.y; - return this; - }; - Vector2.prototype.mutableScale = function (coef) { - this.x *= coef; - this.y *= coef; - return this; - }; - Vector2.prototype.equals = function (v) { - return this.x == v.x && this.y == v.y; - }; - Vector2.prototype.epsilonEquals = function (v, epsilon) { - return Math.abs(this.x - v.x) <= epsilon && Math.abs(this.y - v.y) <= epsilon; - }; - Vector2.prototype.length = function () { - return Math.sqrt(this.x * this.x + this.y * this.y); - }; - Vector2.prototype.length2 = function () { - return this.x * this.x + this.y * this.y; - }; - Vector2.prototype.dist = function (v) { - return Math.sqrt(this.dist2(v)); - }; - Vector2.prototype.dist2 = function (v) { - return ((v.x - this.x) * (v.x - this.x)) + ((v.y - this.y) * (v.y - this.y)); - }; - Vector2.prototype.normal = function (output) { - if (typeof output === "undefined") { output = new Vector2(); } - var m = Math.sqrt(this.x * this.x + this.y * this.y); - return output.setTo(this.x / m, this.y / m); - }; - Vector2.prototype.dot = function (v) { - return this.x * v.x + this.y * v.y; - }; - Vector2.prototype.angle = function (v) { - return Math.atan2(this.x * v.y - this.y * v.x, this.x * v.x + this.y * v.y); - }; - Vector2.prototype.angle2 = function (vLeft, vRight) { - return vLeft.sub(this).angle(vRight.sub(this)); - }; - Vector2.prototype.rotate = function (origin, theta, output) { - if (typeof output === "undefined") { output = new Vector2(); } - var x = this.x - origin.x; - var y = this.y - origin.y; - return output.setTo(x * Math.cos(theta) - y * Math.sin(theta) + origin.x, x * Math.sin(theta) + y * Math.cos(theta) + origin.y); - }; - Vector2.prototype.toString = /** - * Returns a string representation of this object. - * @method toString - * @return {string} a string representation of the object. - **/ - function () { - return "[{Vector2 (x=" + this.x + " y=" + this.y + ")}]"; - }; - return Vector2; - })(); - Phaser.Vector2 = Vector2; -})(Phaser || (Phaser = {})); var Phaser; (function (Phaser) { /// @@ -10581,13 +11546,32 @@ var Phaser; * @return {Composite} This object **/ function Composite(game) { + /** + * Texture of the particles to be rendered. + */ + this._texture = null; + // local rendering related temp vars to help avoid gc spikes + this._sx = 0; + this._sy = 0; + this._sw = 0; + this._sh = 0; + this._dx = 0; + this._dy = 0; + this._dw = 0; + this._dh = 0; + this._hw = 0; + this._hh = 0; this.drawParticles = null; this.drawConstraints = null; + this.hideConstraints = true; + this.constraintLineColor = 'rgba(200,200,200,1)'; this._game = game; + this.sprites = []; this.particles = []; this.constraints = []; + this.frameBounds = new Phaser.Quad(); } - Composite.prototype.createDistanceConstraint = // Map sprites to particles + Composite.prototype.createDistanceConstraint = // Create Constraints function (a, b, stiffness, distance) { if (typeof distance === "undefined") { distance = null; } this.constraints.push(new Phaser.Verlet.DistanceConstraint(a, b, stiffness, distance)); @@ -10601,6 +11585,74 @@ var Phaser; this.constraints.push(new Phaser.Verlet.PinConstraint(a, pos)); return this.constraints[this.constraints.length - 1]; }; + Composite.prototype.loadGraphic = /** + * Load a graphic for this Composite. The graphic cannot be a SpriteSheet yet. + * @param key {string} Key of the graphic you want to load for this sprite. + * @return {Composite} This object + */ + function (key) { + if(this._game.cache.getImage(key) !== null) { + if(this._game.cache.isSpriteSheet(key) == false) { + this._texture = this._game.cache.getImage(key); + this.frameBounds.width = this._texture.width; + this.frameBounds.height = this._texture.height; + this._hw = Math.floor(this.frameBounds.width / 2); + this._hh = Math.floor(this.frameBounds.width / 2); + this.drawParticles = this.render; + this.drawConstraints = this.renderConstraints; + } + } + return this; + }; + Composite.prototype.renderConstraints = function (context) { + if(this.hideConstraints == true || this.constraints.length == 0) { + return; + } + var i; + context.beginPath(); + for(i in this.constraints) { + if(this.constraints[i].b) { + context.moveTo(this.constraints[i].a.pos.x, this.constraints[i].a.pos.y); + context.lineTo(this.constraints[i].b.pos.x, this.constraints[i].b.pos.y); + } + } + context.strokeStyle = this.constraintLineColor; + context.stroke(); + context.closePath(); + }; + Composite.prototype.render = function (context) { + this._sx = 0; + this._sy = 0; + this._sw = this.frameBounds.width; + this._sh = this.frameBounds.height; + this._dw = this.frameBounds.width; + this._dh = this.frameBounds.height; + this._sx = Math.round(this._sx); + this._sy = Math.round(this._sy); + this._sw = Math.round(this._sw); + this._sh = Math.round(this._sh); + this._dw = Math.round(this._dw); + this._dh = Math.round(this._dh); + var i; + for(i in this.particles) { + //this._dx = cameraOffsetX + (this.frameBounds.topLeft.x - camera.worldView.x); + //this._dy = cameraOffsetY + (this.frameBounds.topLeft.y - camera.worldView.y); + this._dx = this.particles[i].pos.x - this._hw; + this._dy = this.particles[i].pos.y - this._hh; + this._dx = Math.round(this._dx); + this._dy = Math.round(this._dy); + context.drawImage(this._texture, // Source Image + this._sx, // Source X (location within the source image) + this._sy, // Source Y + this._sw, // Source Width + this._sh, // Source Height + this._dx, // Destination X (where on the canvas it'll be drawn) + this._dy, // Destination Y + this._dw, // Destination Width (always same as Source Width unless scaled) + this._dh); + // Destination Height (always same as Source Height unless scaled) + } + }; Composite.prototype.pin = function (index, pos) { if (typeof pos === "undefined") { pos = null; } if(pos == null) { @@ -10660,6 +11712,7 @@ var Phaser; ctx.lineTo(this.b.pos.x, this.b.pos.y); ctx.strokeStyle = "#d8dde2"; ctx.stroke(); + ctx.closePath(); }; return DistanceConstraint; })(); @@ -10750,12 +11803,13 @@ var Phaser; * @return {Vector2} This object **/ function VerletManager(game, width, height) { + this._v = new Phaser.Vector2(); this.composites = []; - this.step = 32; + this.step = 16; this.selectionRadius = 20; this.draggedEntity = null; this.highlightColor = '#4f545c'; - this.v = new Phaser.Vector2(); + this.hideNearestEntityCircle = false; this._game = game; this.width = width; this.height = height; @@ -10770,10 +11824,10 @@ var Phaser; VerletManager.prototype.intersectionTime = /** * Computes time of intersection of a particle with a wall * - * @param {Vec2} line wall's root position - * @param {Vec2} p particle's position - * @param {Vec2} dir walls's direction - * @param {Vec2} v particle's velocity + * @param {Vec2} line walls root position + * @param {Vec2} p particle position + * @param {Vec2} dir walls direction + * @param {Vec2} v particles velocity */ function (wall, p, dir, v) { if(dir.x != 0) { @@ -10799,35 +11853,16 @@ var Phaser; return new Phaser.Vector2(p.x + v.x * t, p.y + v.y * t); }; VerletManager.prototype.bounds = function (particle) { - this.v.mutableSet(particle.pos); - this.v.mutableSub(particle.lastPos); + this._v.mutableSet(particle.pos); + this._v.mutableSub(particle.lastPos); if(particle.pos.y > this.height - 1) { - particle.pos.mutableSet(this.intersectionPoint(new Phaser.Vector2(0, this.height - 1), particle.lastPos, new Phaser.Vector2(1, 0), this.v)); + particle.pos.mutableSet(this.intersectionPoint(new Phaser.Vector2(0, this.height - 1), particle.lastPos, new Phaser.Vector2(1, 0), this._v)); } if(particle.pos.x < 0) { - particle.pos.mutableSet(this.intersectionPoint(new Phaser.Vector2(0, 0), particle.pos, new Phaser.Vector2(0, 1), this.v)); + particle.pos.mutableSet(this.intersectionPoint(new Phaser.Vector2(0, 0), particle.pos, new Phaser.Vector2(0, 1), this._v)); } if(particle.pos.x > this.width - 1) { - particle.pos.mutableSet(this.intersectionPoint(new Phaser.Vector2(this.width - 1, 0), particle.pos, new Phaser.Vector2(0, 1), this.v)); - } - }; - VerletManager.prototype.OLDbounds = function (particle) { - if(particle.pos.y > this.height - 1) { - particle.pos.y = this.height - 1; - } - if(particle.pos.x < 0) { - var vx = particle.pos.x - particle.lastPos.x; - var vy = particle.pos.y - particle.lastPos.y; - if(vx == 0) { - particle.pos.x = 0; - } else { - var t = -particle.lastPos.x / vx; - particle.pos.x = particle.lastPos.x + t * vx; - particle.pos.y = particle.lastPos.y + t * vy; - } - } - if(particle.pos.x > this.width - 1) { - particle.pos.x = this.width - 1; + particle.pos.mutableSet(this.intersectionPoint(new Phaser.Vector2(this.width - 1, 0), particle.pos, new Phaser.Vector2(0, 1), this._v)); } }; VerletManager.prototype.createPoint = function (pos) { @@ -10837,8 +11872,8 @@ var Phaser; return composite; }; VerletManager.prototype.createLineSegments = function (vertices, stiffness) { - var i; var composite = new Phaser.Verlet.Composite(this._game); + var i; for(i in vertices) { composite.particles.push(new Phaser.Verlet.Particle(vertices[i])); if(i > 0) { @@ -10852,7 +11887,8 @@ var Phaser; var composite = new Phaser.Verlet.Composite(this._game); var xStride = width / segments; var yStride = height / segments; - var x, y; + var x; + var y; for(y = 0; y < segments; ++y) { for(x = 0; x < segments; ++x) { var px = origin.x + x * xStride - width / 2 + xStride / 2; @@ -10921,7 +11957,7 @@ var Phaser; } // handle dragging of entities if(this.draggedEntity) { - this.draggedEntity.pos.mutableSet(new Phaser.Vector2(this._game.input.x, this._game.input.y)); + this.draggedEntity.pos.mutableSet(this._game.input.position); } // relax var stepCoef = 1 / this.step; @@ -10959,7 +11995,7 @@ var Phaser; for(c in this.composites) { var particles = this.composites[c].particles; for(i in particles) { - var d2 = particles[i].pos.dist2(new Phaser.Vector2(this._game.input.x, this._game.input.y)); + var d2 = particles[i].pos.distance2(this._game.input.position); if(d2 <= this.selectionRadius * this.selectionRadius && (entity == null || d2 < d2Nearest)) { entity = particles[i]; constraintsNearest = this.composites[c].constraints; @@ -10999,11 +12035,12 @@ var Phaser; } // highlight nearest / dragged entity var nearest = this.draggedEntity || this.nearestEntity(); - if(nearest) { + if(nearest && this.hideNearestEntityCircle == false) { this.context.beginPath(); this.context.arc(nearest.pos.x, nearest.pos.y, 8, 0, 2 * Math.PI); this.context.strokeStyle = this.highlightColor; this.context.stroke(); + this.context.closePath(); } }; return VerletManager; @@ -11065,19 +12102,25 @@ var Phaser; World.prototype.setSize = // World methods /** * Update size of this world with specific width and height. - * You can choose update camera bounds automatically or not. + * You can choose update camera bounds and verlet manager automatically or not. * * @param width {number} New width of the world. * @param height {number} New height of the world. * @param [updateCameraBounds] {boolean} update camera bounds automatically or not. Default to true. + * @param [updateVerletBounds] {boolean} update verlet bounds automatically or not. Default to true. */ - function (width, height, updateCameraBounds) { + function (width, height, updateCameraBounds, updateVerletBounds) { if (typeof updateCameraBounds === "undefined") { updateCameraBounds = true; } + if (typeof updateVerletBounds === "undefined") { updateVerletBounds = true; } this.bounds.width = width; this.bounds.height = height; if(updateCameraBounds == true) { this._game.camera.setBounds(0, 0, width, height); } + if(updateVerletBounds == true) { + this._game.verlet.width = width; + this._game.verlet.height = height; + } }; Object.defineProperty(World.prototype, "width", { get: function () { @@ -12030,6 +13073,7 @@ var Phaser; Phaser.RequestAnimationFrame = RequestAnimationFrame; })(Phaser || (Phaser = {})); /// +/// /** * Phaser - Pointer * @@ -12066,17 +13110,17 @@ var Phaser; */ this._nextDrop = 0; /** - * A Point object representing the x/y screen coordinates of the Pointer. - * @property pointA - * @type {Point} + * A Vector object containing the initial position when the Pointer was engaged with the screen. + * @property positionDown + * @type {Vector2} **/ - this.pointA = null; + this.positionDown = null; /** - * A Point object representing the x/y screen coordinates of the Pointer. - * @property pointB - * @type {Point} + * A Vector object containing the current position of the Pointer on the screen. + * @property position + * @type {Vector2} **/ - this.pointB = null; + this.position = null; /** * A Circle object centered on the x/y screen coordinates of the Pointer. * Default size of 44px (Apple's recommended "finger tip" size) @@ -12183,8 +13227,8 @@ var Phaser; this._game = game; this.id = id; this.active = false; - this.pointA = new Phaser.Point(); - this.pointB = new Phaser.Point(); + this.position = new Phaser.Vector2(); + this.positionDown = new Phaser.Vector2(); this.circle = new Phaser.Circle(0, 0, 44); if(id == 0) { this.isMouse = true; @@ -12232,9 +13276,14 @@ var Phaser; if(event.button) { this.button = event.button; } + // Fix to stop rogue browser plugins from blocking the visibility state event + if(this._game.paused == true) { + this._game.stage.resumeGame(); + return this; + } this._history.length = 0; this.move(event); - this.pointA.setTo(this.x, this.y); + this.positionDown.setTo(this.x, this.y); this.active = true; this.withinGame = true; this.isDown = true; @@ -12264,8 +13313,8 @@ var Phaser; if(this._game.input.recordPointerHistory && this._game.time.now >= this._nextDrop) { this._nextDrop = this._game.time.now + this._game.input.recordRate; this._history.push({ - x: this.pointB.x, - y: this.pointB.y + x: this.position.x, + y: this.position.y }); if(this._history.length > this._game.input.recordLimit) { this._history.shift(); @@ -12290,13 +13339,13 @@ var Phaser; this.screenY = event.screenY; this.x = this.pageX - this._game.stage.offset.x; this.y = this.pageY - this._game.stage.offset.y; - this.pointB.setTo(this.x, this.y); + this.position.setTo(this.x, this.y); this.circle.x = this.x; this.circle.y = this.y; if(this._game.input.multiInputOverride == Phaser.Input.MOUSE_OVERRIDES_TOUCH || this._game.input.multiInputOverride == Phaser.Input.MOUSE_TOUCH_COMBINE || (this._game.input.multiInputOverride == Phaser.Input.TOUCH_OVERRIDES_MOUSE && this._game.input.currentPointers == 0)) { this._game.input.x = this.x * this._game.input.scaleX; this._game.input.y = this.y * this._game.input.scaleY; - this._game.input.point.setTo(this._game.input.x, this._game.input.y); + this._game.input.position.setTo(this._game.input.x, this._game.input.y); this._game.input.circle.x = this._game.input.x; this._game.input.circle.y = this._game.input.y; } @@ -12405,8 +13454,8 @@ var Phaser; this._game.stage.context.closePath(); // Render the points this._game.stage.context.beginPath(); - this._game.stage.context.moveTo(this.pointA.x, this.pointA.y); - this._game.stage.context.lineTo(this.pointB.x, this.pointB.y); + this._game.stage.context.moveTo(this.positionDown.x, this.positionDown.y); + this._game.stage.context.lineTo(this.position.x, this.position.y); this._game.stage.context.lineWidth = 2; this._game.stage.context.stroke(); this._game.stage.context.closePath(); @@ -12588,11 +13637,11 @@ var Phaser; */ this.multiInputOverride = Input.MOUSE_TOUCH_COMBINE; /** - * A Point object representing the x/y screen coordinates of the Pointer. - * @property point - * @type {Point} + * A vector object representing the current position of the Pointer. + * @property vector + * @type {Vector2} **/ - this.point = null; + this.position = null; /** * A Circle object centered on the x/y screen coordinates of the Input. * Default size of 44px (Apples recommended "finger tip" size) but can be changed to anything @@ -12684,6 +13733,36 @@ var Phaser; * @type {Number} */ this.recordLimit = 100; + /** + * A Pointer object + * @property pointer6 + * @type {Pointer} + **/ + this.pointer6 = null; + /** + * A Pointer object + * @property pointer7 + * @type {Pointer} + **/ + this.pointer7 = null; + /** + * A Pointer object + * @property pointer8 + * @type {Pointer} + **/ + this.pointer8 = null; + /** + * A Pointer object + * @property pointer9 + * @type {Pointer} + **/ + this.pointer9 = null; + /** + * A Pointer object + * @property pointer10 + * @type {Pointer} + **/ + this.pointer10 = null; this._game = game; this.mousePointer = new Phaser.Pointer(this._game, 0); this.pointer1 = new Phaser.Pointer(this._game, 1); @@ -12691,11 +13770,6 @@ var Phaser; this.pointer3 = new Phaser.Pointer(this._game, 3); this.pointer4 = new Phaser.Pointer(this._game, 4); this.pointer5 = new Phaser.Pointer(this._game, 5); - this.pointer6 = new Phaser.Pointer(this._game, 6); - this.pointer7 = new Phaser.Pointer(this._game, 7); - this.pointer8 = new Phaser.Pointer(this._game, 8); - this.pointer9 = new Phaser.Pointer(this._game, 9); - this.pointer10 = new Phaser.Pointer(this._game, 10); this.mouse = new Phaser.Mouse(this._game); this.keyboard = new Phaser.Keyboard(this._game); this.touch = new Phaser.Touch(this._game); @@ -12705,7 +13779,7 @@ var Phaser; this.onUp = new Phaser.Signal(); this.onTap = new Phaser.Signal(); this.onHold = new Phaser.Signal(); - this.point = new Phaser.Point(); + this.position = new Phaser.Vector2(); this.circle = new Phaser.Circle(0, 0, 44); this.currentPointers = 0; } @@ -12742,25 +13816,74 @@ var Phaser; enumerable: true, configurable: true }); - Input.prototype.start = function () { + Input.prototype.addPointer = /** + * Add a new Pointer object to the Input Manager. By default Input creates 5 pointer objects for you. If you need more + * use this to create a new one, up to a maximum of 10. + * @method addPointer + * @return {Pointer} A reference to the new Pointer object + **/ + function () { + var next = 0; + if(this.pointer10 === null) { + next = 10; + } + if(this.pointer9 === null) { + next = 9; + } + if(this.pointer8 === null) { + next = 8; + } + if(this.pointer7 === null) { + next = 7; + } + if(this.pointer6 === null) { + next = 6; + } + if(next == 0) { + throw new Error("You can only have 10 Pointer objects"); + return null; + } else { + this['pointer' + next] = new Phaser.Pointer(this._game, next); + return this['pointer' + next]; + } + }; + Input.prototype.start = /** + * Starts the Input Manager running + * @method start + **/ + function () { this.mouse.start(); this.keyboard.start(); this.touch.start(); this.mspointer.start(); this.gestures.start(); }; - Input.prototype.update = function () { + Input.prototype.update = /** + * Updates the Input Manager. Called by the core Game loop. + * @method update + **/ + function () { this.mousePointer.update(); this.pointer1.update(); this.pointer2.update(); this.pointer3.update(); this.pointer4.update(); this.pointer5.update(); - this.pointer6.update(); - this.pointer7.update(); - this.pointer8.update(); - this.pointer9.update(); - this.pointer10.update(); + if(this.pointer6) { + this.pointer6.update(); + } + if(this.pointer7) { + this.pointer7.update(); + } + if(this.pointer8) { + this.pointer8.update(); + } + if(this.pointer9) { + this.pointer9.update(); + } + if(this.pointer10) { + this.pointer10.update(); + } }; Input.prototype.reset = /** * Reset all of the Pointers and Input states @@ -12775,11 +13898,21 @@ var Phaser; this.pointer3.reset(); this.pointer4.reset(); this.pointer5.reset(); - this.pointer6.reset(); - this.pointer7.reset(); - this.pointer8.reset(); - this.pointer9.reset(); - this.pointer10.reset(); + if(this.pointer6) { + this.pointer6.reset(); + } + if(this.pointer7) { + this.pointer7.reset(); + } + if(this.pointer8) { + this.pointer8.reset(); + } + if(this.pointer9) { + this.pointer9.reset(); + } + if(this.pointer10) { + this.pointer10.reset(); + } this.currentPointers = 0; if(hard == true) { this.onDown = new Phaser.Signal(); @@ -12818,15 +13951,15 @@ var Phaser; this.currentPointers++; } else if(this.pointer5.active == true) { this.currentPointers++; - } else if(this.pointer6.active == true) { + } else if(this.pointer6 && this.pointer6.active == true) { this.currentPointers++; - } else if(this.pointer7.active == true) { + } else if(this.pointer7 && this.pointer7.active == true) { this.currentPointers++; - } else if(this.pointer8.active == true) { + } else if(this.pointer8 && this.pointer8.active == true) { this.currentPointers++; - } else if(this.pointer9.active == true) { + } else if(this.pointer9 && this.pointer9.active == true) { this.currentPointers++; - } else if(this.pointer10.active == true) { + } else if(this.pointer10 && this.pointer10.active == true) { this.currentPointers++; } return this.currentPointers; @@ -12855,15 +13988,15 @@ var Phaser; return this.pointer4.start(event); } else if(this.pointer5.active == false) { return this.pointer5.start(event); - } else if(this.pointer6.active == false) { + } else if(this.pointer6 && this.pointer6.active == false) { return this.pointer6.start(event); - } else if(this.pointer7.active == false) { + } else if(this.pointer7 && this.pointer7.active == false) { return this.pointer7.start(event); - } else if(this.pointer8.active == false) { + } else if(this.pointer8 && this.pointer8.active == false) { return this.pointer8.start(event); - } else if(this.pointer9.active == false) { + } else if(this.pointer9 && this.pointer9.active == false) { return this.pointer9.start(event); - } else if(this.pointer10.active == false) { + } else if(this.pointer10 && this.pointer10.active == false) { return this.pointer10.start(event); } return null; @@ -12886,15 +14019,15 @@ var Phaser; return this.pointer4.move(event); } else if(this.pointer5.active == true && this.pointer5.identifier == event.identifier) { return this.pointer5.move(event); - } else if(this.pointer6.active == true && this.pointer6.identifier == event.identifier) { + } else if(this.pointer6 && this.pointer6.active == true && this.pointer6.identifier == event.identifier) { return this.pointer6.move(event); - } else if(this.pointer7.active == true && this.pointer7.identifier == event.identifier) { + } else if(this.pointer7 && this.pointer7.active == true && this.pointer7.identifier == event.identifier) { return this.pointer7.move(event); - } else if(this.pointer8.active == true && this.pointer8.identifier == event.identifier) { + } else if(this.pointer8 && this.pointer8.active == true && this.pointer8.identifier == event.identifier) { return this.pointer8.move(event); - } else if(this.pointer9.active == true && this.pointer9.identifier == event.identifier) { + } else if(this.pointer9 && this.pointer9.active == true && this.pointer9.identifier == event.identifier) { return this.pointer9.move(event); - } else if(this.pointer10.active == true && this.pointer10.identifier == event.identifier) { + } else if(this.pointer10 && this.pointer10.active == true && this.pointer10.identifier == event.identifier) { return this.pointer10.move(event); } return null; @@ -12917,15 +14050,15 @@ var Phaser; return this.pointer4.stop(event); } else if(this.pointer5.active == true && this.pointer5.identifier == event.identifier) { return this.pointer5.stop(event); - } else if(this.pointer6.active == true && this.pointer6.identifier == event.identifier) { + } else if(this.pointer6 && this.pointer6.active == true && this.pointer6.identifier == event.identifier) { return this.pointer6.stop(event); - } else if(this.pointer7.active == true && this.pointer7.identifier == event.identifier) { + } else if(this.pointer7 && this.pointer7.active == true && this.pointer7.identifier == event.identifier) { return this.pointer7.stop(event); - } else if(this.pointer8.active == true && this.pointer8.identifier == event.identifier) { + } else if(this.pointer8 && this.pointer8.active == true && this.pointer8.identifier == event.identifier) { return this.pointer8.stop(event); - } else if(this.pointer9.active == true && this.pointer9.identifier == event.identifier) { + } else if(this.pointer9 && this.pointer9.active == true && this.pointer9.identifier == event.identifier) { return this.pointer9.stop(event); - } else if(this.pointer10.active == true && this.pointer10.identifier == event.identifier) { + } else if(this.pointer10 && this.pointer10.active == true && this.pointer10.identifier == event.identifier) { return this.pointer10.stop(event); } return null; @@ -12949,15 +14082,15 @@ var Phaser; return this.pointer4; } else if(this.pointer5.active == state) { return this.pointer5; - } else if(this.pointer6.active == state) { + } else if(this.pointer6 && this.pointer6.active == state) { return this.pointer6; - } else if(this.pointer7.active == state) { + } else if(this.pointer7 && this.pointer7.active == state) { return this.pointer7; - } else if(this.pointer8.active == state) { + } else if(this.pointer8 && this.pointer8.active == state) { return this.pointer8; - } else if(this.pointer9.active == state) { + } else if(this.pointer9 && this.pointer9.active == state) { return this.pointer9; - } else if(this.pointer10.active == state) { + } else if(this.pointer10 && this.pointer10.active == state) { return this.pointer10; } return null; @@ -12980,15 +14113,15 @@ var Phaser; return this.pointer4; } else if(this.pointer5.identifier == identifier) { return this.pointer5; - } else if(this.pointer6.identifier == identifier) { + } else if(this.pointer6 && this.pointer6.identifier == identifier) { return this.pointer6; - } else if(this.pointer7.identifier == identifier) { + } else if(this.pointer7 && this.pointer7.identifier == identifier) { return this.pointer7; - } else if(this.pointer8.identifier == identifier) { + } else if(this.pointer8 && this.pointer8.identifier == identifier) { return this.pointer8; - } else if(this.pointer9.identifier == identifier) { + } else if(this.pointer9 && this.pointer9.identifier == identifier) { return this.pointer9; - } else if(this.pointer10.identifier == identifier) { + } else if(this.pointer10 && this.pointer10.identifier == identifier) { return this.pointer10; } return null; @@ -13021,34 +14154,28 @@ var Phaser; this._game.stage.context.fillText('World X: ' + this.getWorldX() + ' World Y: ' + this.getWorldY(), x, y + 28); this._game.stage.context.fillText('Scale X: ' + this.scaleX.toFixed(1) + ' Scale Y: ' + this.scaleY.toFixed(1), x, y + 42); }; + Input.prototype.getDistance = /** + * Get the distance between two Pointer objects + * @method getDistance + * @param {Pointer} pointer1 + * @param {Pointer} pointer2 + **/ + function (pointer1, pointer2) { + return pointer1.position.distance(pointer2.position); + }; + Input.prototype.getAngle = /** + * Get the angle between two Pointer objects + * @method getAngle + * @param {Pointer} pointer1 + * @param {Pointer} pointer2 + **/ + function (pointer1, pointer2) { + return pointer1.position.angle(pointer2.position); + }; return Input; })(); Phaser.Input = Input; - /** - * - * @method calculateDistance - * @param {Finger} finger1 - * @param {Finger} finger2 - **/ - //public calculateDistance(finger1: Finger, finger2: Finger) { - //} - /** - * - * @method calculateAngle - * @param {Finger} finger1 - * @param {Finger} finger2 - **/ - //public calculateAngle(finger1: Finger, finger2: Finger) { - //} - /** - * - * @method checkOverlap - * @param {Finger} finger1 - * @param {Finger} finger2 - **/ - //public checkOverlap(finger1: Finger, finger2: Finger) { - //} - })(Phaser || (Phaser = {})); +})(Phaser || (Phaser = {})); /// /** * Phaser - Keyboard @@ -13822,6 +14949,7 @@ var Phaser; Phaser.Emitter = Emitter; })(Phaser || (Phaser = {})); /// +/// /** * Phaser - GeomSprite * @@ -13888,6 +15016,7 @@ var Phaser; GeomSprite.LINE = 2; GeomSprite.POINT = 3; GeomSprite.RECTANGLE = 4; + GeomSprite.POLYGON = 5; GeomSprite.prototype.loadCircle = /** * Just like Sprite.loadGraphic(), this will load a circle and set its shape to Circle. * @param circle {Circle} Circle geometry define. @@ -13982,6 +15111,20 @@ var Phaser; this.frameBounds.copyFrom(this.rect); return this; }; + GeomSprite.prototype.createPolygon = /** + * Create a polygon object + * @param width {Number} Width of the rectangle + * @param height {Number} Height of the rectangle + * @return {GeomSprite} GeomSprite instance. + */ + function (points) { + if (typeof points === "undefined") { points = []; } + this.refresh(); + this.polygon = new Phaser.Polygon(new Phaser.Vector2(this.x, this.y), points); + this.type = GeomSprite.POLYGON; + //this.frameBounds.copyFrom(this.rect); + return this; + }; GeomSprite.prototype.refresh = /** * Destroy all geom shapes of this sprite. */ @@ -15572,6 +16715,7 @@ var Phaser; /// /// /// +/// /// /// /// @@ -15751,6 +16895,7 @@ var Phaser; this.math = new Phaser.GameMath(this); this.stage = new Phaser.Stage(this, parent, width, height); this.world = new Phaser.World(this, width, height); + this.add = new Phaser.GameObjectFactory(this); this.sound = new Phaser.SoundManager(this); this.cache = new Phaser.Cache(this); this.collision = new Phaser.Collision(this); @@ -15989,126 +17134,6 @@ var Phaser; enumerable: true, configurable: true }); - Game.prototype.createCamera = // Handy Proxy methods - /** - * Create a new camera with specific position and size. - * - * @param x {number} X position of the new camera. - * @param y {number} Y position of the new camera. - * @param width {number} Width of the new camera. - * @param height {number} Height of the new camera. - * @returns {Camera} The newly created camera object. - */ - function (x, y, width, height) { - return this.world.createCamera(x, y, width, height); - }; - Game.prototype.createGeomSprite = /** - * Create a new GeomSprite with specific position. - * - * @param x {number} X position of the new geom sprite. - * @param y {number} Y position of the new geom sprite. - * @returns {GeomSprite} The newly created geom sprite object. - */ - function (x, y) { - return this.world.createGeomSprite(x, y); - }; - Game.prototype.createSprite = /** - * Create a new Sprite with specific position and sprite sheet key. - * - * @param x {number} X position of the new sprite. - * @param y {number} Y position of the new sprite. - * @param key {string} Optional, key for the sprite sheet you want it to use. - * @returns {Sprite} The newly created sprite object. - */ - function (x, y, key) { - if (typeof key === "undefined") { key = ''; } - return this.world.createSprite(x, y, key); - }; - Game.prototype.createDynamicTexture = /** - * Create a new DynamicTexture with specific size. - * - * @param width {number} Width of the texture. - * @param height {number} Height of the texture. - * @returns {DynamicTexture} The newly created dynamic texture object. - */ - function (width, height) { - return this.world.createDynamicTexture(width, height); - }; - Game.prototype.createGroup = /** - * Create a new object container. - * - * @param maxSize {number} Optional, capacity of this group. - * @returns {Group} The newly created group. - */ - function (maxSize) { - if (typeof maxSize === "undefined") { maxSize = 0; } - return this.world.createGroup(maxSize); - }; - Game.prototype.createParticle = /** - * Create a new Particle. - * - * @return {Particle} The newly created particle object. - */ - function () { - return this.world.createParticle(); - }; - Game.prototype.createEmitter = /** - * Create a new Emitter. - * - * @param x {number} Optional, x position of the emitter. - * @param y {number} Optional, y position of the emitter. - * @param size {number} Optional, size of this emitter. - * @return {Emitter} The newly created emitter object. - */ - function (x, y, size) { - if (typeof x === "undefined") { x = 0; } - if (typeof y === "undefined") { y = 0; } - if (typeof size === "undefined") { size = 0; } - return this.world.createEmitter(x, y, size); - }; - Game.prototype.createScrollZone = /** - * Create a new ScrollZone object with image key, position and size. - * - * @param key {string} Key to a image you wish this object to use. - * @param x {number} X position of this object. - * @param y {number} Y position of this object. - * @param width number} Width of this object. - * @param height {number} Height of this object. - * @returns {ScrollZone} The newly created scroll zone object. - */ - function (key, x, y, width, height) { - if (typeof x === "undefined") { x = 0; } - if (typeof y === "undefined") { y = 0; } - if (typeof width === "undefined") { width = 0; } - if (typeof height === "undefined") { height = 0; } - return this.world.createScrollZone(key, x, y, width, height); - }; - Game.prototype.createTilemap = /** - * Create a new Tilemap. - * - * @param key {string} Key for tileset image. - * @param mapData {string} Data of this tilemap. - * @param format {number} Format of map data. (Tilemap.FORMAT_CSV or Tilemap.FORMAT_TILED_JSON) - * @param [resizeWorld] {boolean} resize the world to make same as tilemap? - * @param [tileWidth] {number} width of each tile. - * @param [tileHeight] {number} height of each tile. - * @return {Tilemap} The newly created tilemap object. - */ - function (key, mapData, format, resizeWorld, tileWidth, tileHeight) { - if (typeof resizeWorld === "undefined") { resizeWorld = true; } - if (typeof tileWidth === "undefined") { tileWidth = 0; } - if (typeof tileHeight === "undefined") { tileHeight = 0; } - return this.world.createTilemap(key, mapData, format, resizeWorld, tileWidth, tileHeight); - }; - Game.prototype.createTween = /** - * Create a tween object for a specific object. - * - * @param obj Object you wish the tween will affect. - * @return {Phaser.Tween} The newly created tween object. - */ - function (obj) { - return this.tweens.create(obj); - }; Game.prototype.collide = /** * Checks for overlaps between two objects using the world QuadTree. Can be GameObject vs. GameObject, GameObject vs. Group or Group vs. Group. * Note: Does not take the objects scrollFactor into account. All overlaps are check in world space. @@ -16302,6 +17327,7 @@ var Phaser; */ function State(game) { this.game = game; + this.add = game.add; this.camera = game.camera; this.cache = game.cache; this.collision = game.collision; @@ -16348,126 +17374,6 @@ var Phaser; */ function () { }; - State.prototype.createCamera = // Handy Proxy methods - /** - * Create a new camera with specific position and size. - * - * @param x {number} X position of the new camera. - * @param y {number} Y position of the new camera. - * @param width {number} Width of the new camera. - * @param height {number} Height of the new camera. - * @returns {Camera} The newly created camera object. - */ - function (x, y, width, height) { - return this.game.world.createCamera(x, y, width, height); - }; - State.prototype.createGeomSprite = /** - * Create a new GeomSprite with specific position. - * - * @param x {number} X position of the new geom sprite. - * @param y {number} Y position of the new geom sprite. - * @returns {GeomSprite} The newly created geom sprite object. - */ - function (x, y) { - return this.world.createGeomSprite(x, y); - }; - State.prototype.createSprite = /** - * Create a new Sprite with specific position and sprite sheet key. - * - * @param x {number} X position of the new sprite. - * @param y {number} Y position of the new sprite. - * @param key {string} [optional] key for the sprite sheet you want it to use. - * @returns {Sprite} The newly created sprite object. - */ - function (x, y, key) { - if (typeof key === "undefined") { key = ''; } - return this.game.world.createSprite(x, y, key); - }; - State.prototype.createDynamicTexture = /** - * Create a new DynamicTexture with specific size. - * - * @param width {number} Width of the texture. - * @param height {number} Height of the texture. - * @returns {DynamicTexture} The newly created dynamic texture object. - */ - function (width, height) { - return this.game.world.createDynamicTexture(width, height); - }; - State.prototype.createGroup = /** - * Create a new object container. - * - * @param maxSize {number} [optional] capacity of this group. - * @returns {Group} The newly created group. - */ - function (maxSize) { - if (typeof maxSize === "undefined") { maxSize = 0; } - return this.game.world.createGroup(maxSize); - }; - State.prototype.createParticle = /** - * Create a new Particle. - * - * @return {Particle} The newly created particle object. - */ - function () { - return this.game.world.createParticle(); - }; - State.prototype.createEmitter = /** - * Create a new Emitter. - * - * @param x {number} [optional] x position of the emitter. - * @param y {number} [optional] y position of the emitter. - * @param size {number} [optional] size of this emitter. - * @return {Emitter} The newly created emitter object. - */ - function (x, y, size) { - if (typeof x === "undefined") { x = 0; } - if (typeof y === "undefined") { y = 0; } - if (typeof size === "undefined") { size = 0; } - return this.game.world.createEmitter(x, y, size); - }; - State.prototype.createScrollZone = /** - * Create a new ScrollZone object with image key, position and size. - * - * @param key {string} Key to a image you wish this object to use. - * @param x {number} X position of this object. - * @param y {number} Y position of this object. - * @param width {number} Width of this object. - * @param height {number} Height of this object. - * @returns {ScrollZone} The newly created scroll zone object. - */ - function (key, x, y, width, height) { - if (typeof x === "undefined") { x = 0; } - if (typeof y === "undefined") { y = 0; } - if (typeof width === "undefined") { width = 0; } - if (typeof height === "undefined") { height = 0; } - return this.game.world.createScrollZone(key, x, y, width, height); - }; - State.prototype.createTilemap = /** - * Create a new Tilemap. - * - * @param key {string} Key for tileset image. - * @param mapData {string} Data of this tilemap. - * @param format {number} Format of map data. (Tilemap.FORMAT_CSV or Tilemap.FORMAT_TILED_JSON) - * @param resizeWorld {boolean} [optional] resize the world to make same as tilemap? - * @param tileWidth {number} [optional] width of each tile. - * @param tileHeight number} [optional] height of each tile. - * @return {Tilemap} The newly created tilemap object. - */ - function (key, mapData, format, resizeWorld, tileWidth, tileHeight) { - if (typeof resizeWorld === "undefined") { resizeWorld = true; } - if (typeof tileWidth === "undefined") { tileWidth = 0; } - if (typeof tileHeight === "undefined") { tileHeight = 0; } - return this.game.world.createTilemap(key, mapData, format, resizeWorld, tileWidth, tileHeight); - }; - State.prototype.createTween = /** - * Create a tween object for a specific object. - * - * @param obj Object you wish the tween will affect. - * @return {Phaser.Tween} The newly created tween object. - */ - function (obj) { - return this.game.tweens.create(obj); - }; State.prototype.collide = /** * Call this method to see if one object collids another. * @return {boolean} Whether the given objects or groups collids. diff --git a/Tests/physics/temp1.js b/Tests/physics/temp1.js new file mode 100644 index 00000000..9b6e962b --- /dev/null +++ b/Tests/physics/temp1.js @@ -0,0 +1,113 @@ +/// +var Physics = (function () { + function Physics() { + this.max_bodies = 512; + this.max_vertices = 1024; + this.max_edges = 1024; + this.max_body_vertices = 64; + this.max_body_edges = 64; + this.vertices = []; + this.edges = []; + this.bodies = []; + } + Physics.prototype.updateForces = // Sets the force on each vertex to the gravity force. You could of course apply other forces like magnetism etc. + function () { + for(var i = 0; i < this.vertexCount; i++) { + this.vertices[i].acceleration = this.gravity; + } + }; + Physics.prototype.updateVerlet = // Updates the vertex position + function () { + for(var i = 0; i < this.vertexCount; i++) { + var v = this.vertices[i]; + var temp = v.position; + //v.position.mutableAdd( + //v.position += v.position - v.oldPosition + v.acceleration * this.timestep * this.timestep; + } + }; + Physics.prototype.updateEdges = function () { + }; + Physics.prototype.iterateCollisions = function () { + }; + Physics.prototype.detectCollision = function (body1, body2) { + }; + Physics.prototype.processCollision = function () { + }; + Physics.prototype.intervalDistance = function (minA, maxA, minB, maxB) { + }; + Physics.prototype.bodiesOverlap = function (body1, body2) { + }; + Physics.prototype.update = // CollisionInfo + // depth, normal, edge, vertex + function () { + }; + Physics.prototype.render = function () { + }; + Physics.prototype.addBody = function (body) { + this.bodies.push(body); + this.bodyCount = this.bodies.length; + }; + Physics.prototype.addEdge = function (edge) { + this.edges.push(edge); + this.edgeCount = this.edges.length; + }; + Physics.prototype.addVertex = function (vertex) { + this.vertices.push(vertex); + this.vertexCount = this.vertices.length; + }; + Physics.prototype.findVertex = function (x, y) { + }; + return Physics; +})(); +var PhysicsBody = (function () { + function PhysicsBody() { + this.vertices = []; + this.edges = []; + } + PhysicsBody.prototype.addEdge = function (edge) { + }; + PhysicsBody.prototype.addVertex = function (vertex) { + }; + PhysicsBody.prototype.projectToAxis = function (axis, min, max) { + }; + PhysicsBody.prototype.calculateCenter = function () { + }; + PhysicsBody.prototype.createBox = function (x, y, width, height) { + }; + return PhysicsBody; +})(); +var Vertex = (function () { + function Vertex(body, posX, posY) { + } + return Vertex; +})(); +var Edge = (function () { + function Edge(body, pV1, pV2, pBoundary) { + } + return Edge; +})(); +(function () { + var myGame = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + function init() { + myGame.loader.addImageFile('atari1', 'assets/sprites/atari130xe.png'); + myGame.loader.load(); + } + function create() { + var p = new Physics(); + //p.max_bodies + } + function update() { + } + function render() { + //myGame.stage.context.strokeStyle = 'rgb(0,255,0)'; + //myGame.stage.context.beginPath(); + //myGame.stage.context.moveTo(poly1.points[0].x + poly1.pos.x, poly1.points[0].y + poly1.pos.y); + //for (var i = 1; i < poly1.points.length; i++) + //{ + // myGame.stage.context.lineTo(poly1.points[i].x + poly1.pos.x, poly1.points[i].y + poly1.pos.y); + //} + //myGame.stage.context.lineTo(poly1.points[0].x + poly1.pos.x, poly1.points[0].y + poly1.pos.y); + //myGame.stage.context.stroke(); + //myGame.stage.context.closePath(); + } +})(); diff --git a/Tests/physics/temp1.ts b/Tests/physics/temp1.ts new file mode 100644 index 00000000..bd379200 --- /dev/null +++ b/Tests/physics/temp1.ts @@ -0,0 +1,200 @@ +/// + +class Physics { + + constructor() { + + this.vertices = []; + this.edges = []; + this.bodies = []; + + } + + public gravity: Phaser.Vector2; + + public max_bodies: number = 512; + public max_vertices: number = 1024; + public max_edges: number = 1024; + public max_body_vertices: number = 64; + public max_body_edges: number = 64; + + public bodyCount: number; + public vertexCount: number; + public edgeCount: number; + public timestep: number; + public iterations; + + public vertices: Vertex[]; + public edges: Edge[]; + public bodies: PhysicsBody[]; + + // Sets the force on each vertex to the gravity force. You could of course apply other forces like magnetism etc. + public updateForces() { + + for (var i:number = 0; i < this.vertexCount; i++) + { + this.vertices[i].acceleration = this.gravity; + } + } + + // Updates the vertex position + public updateVerlet() { + + for (var i:number = 0; i < this.vertexCount; i++) + { + var v:Vertex = this.vertices[i]; + var temp: Phaser.Vector2 = v.position; + //v.position.mutableAdd( + //v.position += v.position - v.oldPosition + v.acceleration * this.timestep * this.timestep; + } + + } + + public updateEdges() { + } + + public iterateCollisions() { + } + + public detectCollision(body1, body2) { + } + + public processCollision() { + } + + public intervalDistance(minA, maxA, minB, maxB) { + } + + public bodiesOverlap(body1, body2) { + } + + // CollisionInfo + // depth, normal, edge, vertex + + public update() { + } + + public render() { + } + + public addBody(body:PhysicsBody) { + this.bodies.push(body); + this.bodyCount = this.bodies.length; + } + + public addEdge(edge:Edge) { + this.edges.push(edge); + this.edgeCount = this.edges.length; + } + + public addVertex(vertex:Vertex) { + this.vertices.push(vertex); + this.vertexCount = this.vertices.length; + } + + public findVertex(x, y) { + } + +} + +class PhysicsBody { + + constructor() { + } + + center: Phaser.Vector2; + minX; + minY; + maxX; + maxY; + vertextCount; + edgeCount; + + vertices = []; + edges = []; + + public addEdge(edge) { + } + + public addVertex(vertex) { + + } + + public projectToAxis(axis, min, max) { + } + + public calculateCenter() { + } + + public createBox(x, y, width, height) { + } + +} + +class Vertex { + + constructor(body, posX, posY) { + } + + position: Phaser.Vector2; + oldPosition: Phaser.Vector2; + acceleration: Phaser.Vector2; + parent: PhysicsBody; +} + +class Edge { + + constructor(body, pV1, pV2, pBoundary) { + } + + v1: Vertex; + v2: Vertex; + length; + boundary; + parent: PhysicsBody; + +} + +(function () { + + + var myGame = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + + function init() { + + myGame.loader.addImageFile('atari1', 'assets/sprites/atari130xe.png'); + myGame.loader.load(); + + } + + function create() { + + var p = new Physics(); + //p.max_bodies + + } + + function update() { + + + } + + function render() { + + //myGame.stage.context.strokeStyle = 'rgb(0,255,0)'; + //myGame.stage.context.beginPath(); + //myGame.stage.context.moveTo(poly1.points[0].x + poly1.pos.x, poly1.points[0].y + poly1.pos.y); + + //for (var i = 1; i < poly1.points.length; i++) + //{ + // myGame.stage.context.lineTo(poly1.points[i].x + poly1.pos.x, poly1.points[i].y + poly1.pos.y); + //} + + //myGame.stage.context.lineTo(poly1.points[0].x + poly1.pos.x, poly1.points[0].y + poly1.pos.y); + + //myGame.stage.context.stroke(); + //myGame.stage.context.closePath(); + + } + +})(); diff --git a/Tests/physics/temp2.js b/Tests/physics/temp2.js new file mode 100644 index 00000000..6eaf2b5e --- /dev/null +++ b/Tests/physics/temp2.js @@ -0,0 +1,1218 @@ +/// +var NPhysics = (function () { + function NPhysics() { + this.grav = 0.2; + this.drag = 1; + this.bounce = 0.3; + this.friction = 0.05; + this.min_f = 0; + this.max_f = 1; + this.min_b = 0; + this.max_b = 1; + this.min_g = 0; + this.max_g = 1; + this.xmin = 0; + this.xmax = 800; + this.ymin = 0; + this.ymax = 600; + this.objrad = 24; + this.tilerad = 24 * 2; + this.objspeed = 0.2; + this.maxspeed = 20; + } + NPhysics.prototype.update = function () { + // demoObj.Verlet(); + // demoObj.CollideVsWorldBounds(); + }; + return NPhysics; +})(); +var AABB = (function () { + function AABB(pos, xw, yw) { + this.type = 0; + this.pos = pos.clone(); + this.oldpos = pos.clone(); + this.xw = Math.abs(xw); + this.yw = Math.abs(yw); + } + return AABB; +})(); +var TileMapCell = (function () { + function TileMapCell(x, y, xw, yw) { + this.ID = TileMapCell.TID_EMPTY//all tiles start empty + ; + this.CTYPE = TileMapCell.CTYPE_EMPTY; + this.pos = new Phaser.Vector2(x, y)//setup collision properties + ; + this.xw = xw; + this.yw = yw; + this.minx = this.pos.x - this.xw; + this.maxx = this.pos.x + this.xw; + this.miny = this.pos.y - this.yw; + this.maxy = this.pos.y + this.yw; + //this stores tile-specific collision information + this.signx = 0; + this.signy = 0; + this.sx = 0; + this.sy = 0; + } + TileMapCell.TID_EMPTY = 0; + TileMapCell.TID_FULL = 1; + TileMapCell.TID_45DEGpn = 2; + TileMapCell.TID_45DEGnn = 3; + TileMapCell.TID_45DEGnp = 4; + TileMapCell.TID_45DEGpp = 5; + TileMapCell.TID_CONCAVEpn = 6; + TileMapCell.TID_CONCAVEnn = 7; + TileMapCell.TID_CONCAVEnp = 8; + TileMapCell.TID_CONCAVEpp = 9; + TileMapCell.TID_CONVEXpn = 10; + TileMapCell.TID_CONVEXnn = 11; + TileMapCell.TID_CONVEXnp = 12; + TileMapCell.TID_CONVEXpp = 13; + TileMapCell.TID_22DEGpnS = 14; + TileMapCell.TID_22DEGnnS = 15; + TileMapCell.TID_22DEGnpS = 16; + TileMapCell.TID_22DEGppS = 17; + TileMapCell.TID_22DEGpnB = 18; + TileMapCell.TID_22DEGnnB = 19; + TileMapCell.TID_22DEGnpB = 20; + TileMapCell.TID_22DEGppB = 21; + TileMapCell.TID_67DEGpnS = 22; + TileMapCell.TID_67DEGnnS = 23; + TileMapCell.TID_67DEGnpS = 24; + TileMapCell.TID_67DEGppS = 25; + TileMapCell.TID_67DEGpnB = 26; + TileMapCell.TID_67DEGnnB = 27; + TileMapCell.TID_67DEGnpB = 28; + TileMapCell.TID_67DEGppB = 29; + TileMapCell.TID_HALFd = 30; + TileMapCell.TID_HALFr = 31; + TileMapCell.TID_HALFu = 32; + TileMapCell.TID_HALFl = 33; + TileMapCell.CTYPE_EMPTY = 0; + TileMapCell.CTYPE_FULL = 1; + TileMapCell.CTYPE_45DEG = 2; + TileMapCell.CTYPE_CONCAVE = 6; + TileMapCell.CTYPE_CONVEX = 10; + TileMapCell.CTYPE_22DEGs = 14; + TileMapCell.CTYPE_22DEGb = 18; + TileMapCell.CTYPE_67DEGs = 22; + TileMapCell.CTYPE_67DEGb = 26; + TileMapCell.CTYPE_HALF = 30; + TileMapCell.prototype.SetState = //these functions are used to update the cell + //note: ID is assumed to NOT be "empty" state.. + //if it IS the empty state, the tile clears itself + function (ID) { + if(ID == TileMapCell.TID_EMPTY) { + this.Clear(); + } else { + //set tile state to a non-emtpy value, and update it's edges and those of the neighbors + this.ID = ID; + this.UpdateType(); + //this.Draw(); + } + }; + TileMapCell.prototype.Clear = function () { + //tile was on, turn it off + this.ID = TileMapCell.TID_EMPTY; + this.UpdateType(); + //this.Draw(); + }; + TileMapCell.prototype.render = function (context) { + context.beginPath(); + context.strokeStyle = 'rgb(255,255,0)'; + context.strokeRect(this.minx, this.miny, this.xw * 2, this.yw * 2); + context.strokeRect(this.pos.x, this.pos.y, 2, 2); + context.closePath(); + }; + TileMapCell.prototype.UpdateType = //this converts a tile from implicitly-defined (via ID), to explicit (via properties) + function () { + if(0 < this.ID) { + //tile is non-empty; collide + if(this.ID < TileMapCell.CTYPE_45DEG) { + //TID_FULL + this.CTYPE = TileMapCell.CTYPE_FULL; + this.signx = 0; + this.signy = 0; + this.sx = 0; + this.sy = 0; + } else if(this.ID < TileMapCell.CTYPE_CONCAVE) { + //45deg + this.CTYPE = TileMapCell.CTYPE_45DEG; + if(this.ID == TileMapCell.TID_45DEGpn) { + console.log('set tile as 45deg pn'); + this.signx = 1; + this.signy = -1; + this.sx = this.signx / Math.SQRT2//get slope _unit_ normal + ; + this.sy = this.signy / Math.SQRT2//since normal is (1,-1), length is sqrt(1*1 + -1*-1) = sqrt(2) + ; + } else if(this.ID == TileMapCell.TID_45DEGnn) { + this.signx = -1; + this.signy = -1; + this.sx = this.signx / Math.SQRT2//get slope _unit_ normal + ; + this.sy = this.signy / Math.SQRT2//since normal is (1,-1), length is sqrt(1*1 + -1*-1) = sqrt(2) + ; + } else if(this.ID == TileMapCell.TID_45DEGnp) { + this.signx = -1; + this.signy = 1; + this.sx = this.signx / Math.SQRT2//get slope _unit_ normal + ; + this.sy = this.signy / Math.SQRT2//since normal is (1,-1), length is sqrt(1*1 + -1*-1) = sqrt(2) + ; + } else if(this.ID == TileMapCell.TID_45DEGpp) { + this.signx = 1; + this.signy = 1; + this.sx = this.signx / Math.SQRT2//get slope _unit_ normal + ; + this.sy = this.signy / Math.SQRT2//since normal is (1,-1), length is sqrt(1*1 + -1*-1) = sqrt(2) + ; + } else { + //trace("BAAAD TILE!!!!!: ID=" + this.ID + " ("+ t.i + "," + t.j + ")"); + return false; + } + } else if(this.ID < TileMapCell.CTYPE_CONVEX) { + //concave + this.CTYPE = TileMapCell.CTYPE_CONCAVE; + if(this.ID == TileMapCell.TID_CONCAVEpn) { + this.signx = 1; + this.signy = -1; + this.sx = 0; + this.sy = 0; + } else if(this.ID == TileMapCell.TID_CONCAVEnn) { + this.signx = -1; + this.signy = -1; + this.sx = 0; + this.sy = 0; + } else if(this.ID == TileMapCell.TID_CONCAVEnp) { + this.signx = -1; + this.signy = 1; + this.sx = 0; + this.sy = 0; + } else if(this.ID == TileMapCell.TID_CONCAVEpp) { + this.signx = 1; + this.signy = 1; + this.sx = 0; + this.sy = 0; + } else { + //trace("BAAAD TILE!!!!!: ID=" + this.ID + " ("+ t.i + "," + t.j + ")"); + return false; + } + } else if(this.ID < TileMapCell.CTYPE_22DEGs) { + //convex + this.CTYPE = TileMapCell.CTYPE_CONVEX; + if(this.ID == TileMapCell.TID_CONVEXpn) { + this.signx = 1; + this.signy = -1; + this.sx = 0; + this.sy = 0; + } else if(this.ID == TileMapCell.TID_CONVEXnn) { + this.signx = -1; + this.signy = -1; + this.sx = 0; + this.sy = 0; + } else if(this.ID == TileMapCell.TID_CONVEXnp) { + this.signx = -1; + this.signy = 1; + this.sx = 0; + this.sy = 0; + } else if(this.ID == TileMapCell.TID_CONVEXpp) { + this.signx = 1; + this.signy = 1; + this.sx = 0; + this.sy = 0; + } else { + //trace("BAAAD TILE!!!!!: ID=" + this.ID + " ("+ t.i + "," + t.j + ")"); + return false; + } + } else if(this.ID < TileMapCell.CTYPE_22DEGb) { + //22deg small + this.CTYPE = TileMapCell.CTYPE_22DEGs; + if(this.ID == TileMapCell.TID_22DEGpnS) { + this.signx = 1; + this.signy = -1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 1) / slen; + this.sy = (this.signy * 2) / slen; + } else if(this.ID == TileMapCell.TID_22DEGnnS) { + this.signx = -1; + this.signy = -1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 1) / slen; + this.sy = (this.signy * 2) / slen; + } else if(this.ID == TileMapCell.TID_22DEGnpS) { + this.signx = -1; + this.signy = 1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 1) / slen; + this.sy = (this.signy * 2) / slen; + } else if(this.ID == TileMapCell.TID_22DEGppS) { + this.signx = 1; + this.signy = 1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 1) / slen; + this.sy = (this.signy * 2) / slen; + } else { + //trace("BAAAD TILE!!!!!: ID=" + this.ID + " ("+ t.i + "," + t.j + ")"); + return false; + } + } else if(this.ID < TileMapCell.CTYPE_67DEGs) { + //22deg big + this.CTYPE = TileMapCell.CTYPE_22DEGb; + if(this.ID == TileMapCell.TID_22DEGpnB) { + this.signx = 1; + this.signy = -1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 1) / slen; + this.sy = (this.signy * 2) / slen; + } else if(this.ID == TileMapCell.TID_22DEGnnB) { + this.signx = -1; + this.signy = -1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 1) / slen; + this.sy = (this.signy * 2) / slen; + } else if(this.ID == TileMapCell.TID_22DEGnpB) { + this.signx = -1; + this.signy = 1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 1) / slen; + this.sy = (this.signy * 2) / slen; + } else if(this.ID == TileMapCell.TID_22DEGppB) { + this.signx = 1; + this.signy = 1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 1) / slen; + this.sy = (this.signy * 2) / slen; + } else { + //trace("BAAAD TILE!!!!!: ID=" + this.ID + " ("+ t.i + "," + t.j + ")"); + return false; + } + } else if(this.ID < TileMapCell.CTYPE_67DEGb) { + //67deg small + this.CTYPE = TileMapCell.CTYPE_67DEGs; + if(this.ID == TileMapCell.TID_67DEGpnS) { + this.signx = 1; + this.signy = -1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 2) / slen; + this.sy = (this.signy * 1) / slen; + } else if(this.ID == TileMapCell.TID_67DEGnnS) { + this.signx = -1; + this.signy = -1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 2) / slen; + this.sy = (this.signy * 1) / slen; + } else if(this.ID == TileMapCell.TID_67DEGnpS) { + this.signx = -1; + this.signy = 1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 2) / slen; + this.sy = (this.signy * 1) / slen; + } else if(this.ID == TileMapCell.TID_67DEGppS) { + this.signx = 1; + this.signy = 1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 2) / slen; + this.sy = (this.signy * 1) / slen; + } else { + //trace("BAAAD TILE!!!!!: ID=" + this.ID + " ("+ t.i + "," + t.j + ")"); + return false; + } + } else if(this.ID < TileMapCell.CTYPE_HALF) { + //67deg big + this.CTYPE = TileMapCell.CTYPE_67DEGb; + if(this.ID == TileMapCell.TID_67DEGpnB) { + this.signx = 1; + this.signy = -1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 2) / slen; + this.sy = (this.signy * 1) / slen; + } else if(this.ID == TileMapCell.TID_67DEGnnB) { + this.signx = -1; + this.signy = -1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 2) / slen; + this.sy = (this.signy * 1) / slen; + } else if(this.ID == TileMapCell.TID_67DEGnpB) { + this.signx = -1; + this.signy = 1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 2) / slen; + this.sy = (this.signy * 1) / slen; + } else if(this.ID == TileMapCell.TID_67DEGppB) { + this.signx = 1; + this.signy = 1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 2) / slen; + this.sy = (this.signy * 1) / slen; + } else { + //trace("BAAAD TILE!!!!!: ID=" + this.ID + " ("+ t.i + "," + t.j + ")"); + return false; + } + } else { + //half-full tile + this.CTYPE = TileMapCell.CTYPE_HALF; + if(this.ID == TileMapCell.TID_HALFd) { + this.signx = 0; + this.signy = -1; + this.sx = this.signx; + this.sy = this.signy; + } else if(this.ID == TileMapCell.TID_HALFu) { + this.signx = 0; + this.signy = 1; + this.sx = this.signx; + this.sy = this.signy; + } else if(this.ID == TileMapCell.TID_HALFl) { + this.signx = 1; + this.signy = 0; + this.sx = this.signx; + this.sy = this.signy; + } else if(this.ID == TileMapCell.TID_HALFr) { + this.signx = -1; + this.signy = 0; + this.sx = this.signx; + this.sy = this.signy; + } else { + //trace("BAAD TILE!!!: ID=" + this.ID + " ("+ t.i + "," + t.j + ")"); + return false; + } + } + } else { + //TID_EMPTY + this.CTYPE = TileMapCell.CTYPE_EMPTY; + this.signx = 0; + this.signy = 0; + this.sx = 0; + this.sy = 0; + } + }; + return TileMapCell; +})(); +var Circle = (function () { + function Circle(x, y, radius) { + this.type = 1; + this.pos = new Phaser.Vector2(x, y); + this.oldpos = this.pos.clone(); + this.radius = radius; + this.circleTileProjections = { + }//hash object to hold tile-specific collision functions + ; + this.circleTileProjections[TileMapCell.CTYPE_FULL] = this.ProjCircle_Full; + this.circleTileProjections[TileMapCell.CTYPE_45DEG] = this.ProjCircle_45Deg; + this.circleTileProjections[TileMapCell.CTYPE_CONCAVE] = this.ProjCircle_Concave; + this.circleTileProjections[TileMapCell.CTYPE_CONVEX] = this.ProjCircle_Convex; + //Proj_CircleTile[CTYPE_22DEGs] = ProjCircle_22DegS; + //Proj_CircleTile[CTYPE_22DEGb] = ProjCircle_22DegB; + //Proj_CircleTile[CTYPE_67DEGs] = ProjCircle_67DegS; + //Proj_CircleTile[CTYPE_67DEGb] = ProjCircle_67DegB; + //Proj_CircleTile[CTYPE_HALF] = ProjCircle_Half; + } + Circle.prototype.IntegrateVerlet = function () { + //var d = DRAG; + //var g = GRAV; + var d = 1; + var g = 0.2; + var p = this.pos; + var o = this.oldpos; + var px, py; + var ox = o.x;//we can't swap buffers since mcs/sticks point directly to vector2s.. + + var oy = o.y; + o.x = px = p.x//get vector values + ; + o.y = py = p.y//p = position + ; + //o = oldposition + //integrate + p.x += (d * px) - (d * ox); + p.y += (d * py) - (d * oy) + g; + }; + Circle.prototype.ReportCollisionVsWorld = function (px, py, dx, dy, obj) { + var p = this.pos; + var o = this.oldpos; + //calc velocity + var vx = p.x - o.x; + var vy = p.y - o.y; + //find component of velocity parallel to collision normal + var dp = (vx * dx + vy * dy); + var nx = dp * dx;//project velocity onto collision normal + + var ny = dp * dy;//nx,ny is normal velocity + + var tx = vx - nx;//px,py is tangent velocity + + var ty = vy - ny; + //we only want to apply collision response forces if the object is travelling into, and not out of, the collision + var b, bx, by, f, fx, fy; + if(dp < 0) { + //f = FRICTION; + f = 0.05; + fx = tx * f; + fy = ty * f; + //b = 1 + BOUNCE;//this bounce constant should be elsewhere, i.e inside the object/tile/etc.. + b = 1 + 0.3//this bounce constant should be elsewhere, i.e inside the object/tile/etc.. + ; + bx = (nx * b); + by = (ny * b); + } else { + //moving out of collision, do not apply forces + bx = by = fx = fy = 0; + } + p.x += px//project object out of collision + ; + p.y += py; + o.x += px + bx + fx//apply bounce+friction impulses which alter velocity + ; + o.y += py + by + fy; + }; + Circle.prototype.CollideCircleVsWorldBounds = function () { + var p = this.pos; + var r = this.radius; + var XMIN = 0; + var XMAX = 800; + var YMIN = 0; + var YMAX = 600; + //collide vs. x-bounds + //test XMIN + var dx = XMIN - (p.x - r); + if(0 < dx) { + //object is colliding with XMIN + this.ReportCollisionVsWorld(dx, 0, 1, 0, null); + } else { + //test XMAX + dx = (p.x + r) - XMAX; + if(0 < dx) { + //object is colliding with XMAX + this.ReportCollisionVsWorld(-dx, 0, -1, 0, null); + } + } + //collide vs. y-bounds + //test YMIN + var dy = YMIN - (p.y - r); + if(0 < dy) { + //object is colliding with YMIN + this.ReportCollisionVsWorld(0, dy, 0, 1, null); + } else { + //test YMAX + dy = (p.y + r) - YMAX; + if(0 < dy) { + //object is colliding with YMAX + this.ReportCollisionVsWorld(0, -dy, 0, -1, null); + } + } + }; + Circle.prototype.render = function (context) { + context.beginPath(); + context.strokeStyle = 'rgb(0,255,0)'; + context.arc(this.pos.x, this.pos.y, this.radius, 0, Math.PI * 2); + context.stroke(); + context.closePath(); + if(this.oH == 1) { + context.beginPath(); + context.strokeStyle = 'rgb(255,0,0)'; + context.moveTo(this.pos.x - this.radius, this.pos.y - this.radius); + context.lineTo(this.pos.x - this.radius, this.pos.y + this.radius); + context.stroke(); + context.closePath(); + } else if(this.oH == -1) { + context.beginPath(); + context.strokeStyle = 'rgb(255,0,0)'; + context.moveTo(this.pos.x + this.radius, this.pos.y - this.radius); + context.lineTo(this.pos.x + this.radius, this.pos.y + this.radius); + context.stroke(); + context.closePath(); + } + if(this.oV == 1) { + context.beginPath(); + context.strokeStyle = 'rgb(255,0,0)'; + context.moveTo(this.pos.x - this.radius, this.pos.y - this.radius); + context.lineTo(this.pos.x + this.radius, this.pos.y - this.radius); + context.stroke(); + context.closePath(); + } else if(this.oV == -1) { + context.beginPath(); + context.strokeStyle = 'rgb(255,0,0)'; + context.moveTo(this.pos.x - this.radius, this.pos.y + this.radius); + context.lineTo(this.pos.x + this.radius, this.pos.y + this.radius); + context.stroke(); + context.closePath(); + } + }; + Circle.prototype.CollideCircleVsTile = function (tile) { + var pos = this.pos; + var r = this.radius; + var c = tile; + var tx = c.pos.x; + var ty = c.pos.y; + var txw = c.xw; + var tyw = c.yw; + var dx = pos.x - tx;//tile->obj delta + + var px = (txw + r) - Math.abs(dx);//penetration depth in x + + if(0 < px) { + var dy = pos.y - ty;//tile->obj delta + + var py = (tyw + r) - Math.abs(dy);//pen depth in y + + if(0 < py) { + //object may be colliding with tile + //determine grid/voronoi region of circle center + this.oH = 0; + this.oV = 0; + if(dx < -txw) { + //circle is on left side of tile + this.oH = -1; + } else if(txw < dx) { + //circle is on right side of tile + this.oH = 1; + } + if(dy < -tyw) { + //circle is on top side of tile + this.oV = -1; + } else if(tyw < dy) { + //circle is on bottom side of tile + this.oV = 1; + } + this.ResolveCircleTile(px, py, this.oH, this.oV, this, c); + } + } + }; + Circle.prototype.ResolveCircleTile = function (x, y, oH, oV, obj, t) { + if(0 < t.ID) { + return this.circleTileProjections[t.CTYPE](x, y, oH, oV, obj, t); + } else { + console.log("ResolveCircleTile() was called with an empty (or unknown) tile!: ID=" + t.ID + " (" + t.i + "," + t.j + ")"); + return false; + } + }; + Circle.COL_NONE = 0; + Circle.COL_AXIS = 1; + Circle.COL_OTHER = 2; + Circle.prototype.ProjCircle_Full = function (x, y, oH, oV, obj, t) { + //if we're colliding vs. the current cell, we need to project along the + //smallest penetration vector. + //if we're colliding vs. horiz. or vert. neighb, we simply project horiz/vert + //if we're colliding diagonally, we need to collide vs. tile corner + if(oH == 0) { + if(oV == 0) { + //collision with current cell + if(x < y) { + //penetration in x is smaller; project in x + var dx = obj.pos.x - t.pos.x;//get sign for projection along x-axis + + //NOTE: should we handle the delta == 0 case?! and how? (project towards oldpos?) + if(dx < 0) { + obj.ReportCollisionVsWorld(-x, 0, -1, 0, t); + return Circle.COL_AXIS; + } else { + obj.ReportCollisionVsWorld(x, 0, 1, 0, t); + return Circle.COL_AXIS; + } + } else { + //penetration in y is smaller; project in y + var dy = obj.pos.y - t.pos.y;//get sign for projection along y-axis + + //NOTE: should we handle the delta == 0 case?! and how? (project towards oldpos?) + if(dy < 0) { + obj.ReportCollisionVsWorld(0, -y, 0, -1, t); + return Circle.COL_AXIS; + } else { + obj.ReportCollisionVsWorld(0, y, 0, 1, t); + return Circle.COL_AXIS; + } + } + } else { + //collision with vertical neighbor + obj.ReportCollisionVsWorld(0, y * oV, 0, oV, t); + return Circle.COL_AXIS; + } + } else if(oV == 0) { + //collision with horizontal neighbor + obj.ReportCollisionVsWorld(x * oH, 0, oH, 0, t); + return Circle.COL_AXIS; + } else { + //diagonal collision + //get diag vertex position + var vx = t.pos.x + (oH * t.xw); + var vy = t.pos.y + (oV * t.yw); + var dx = obj.pos.x - vx;//calc vert->circle vector + + var dy = obj.pos.y - vy; + var len = Math.sqrt(dx * dx + dy * dy); + var pen = obj.radius - len; + if(0 < pen) { + //vertex is in the circle; project outward + if(len == 0) { + //project out by 45deg + dx = oH / Math.SQRT2; + dy = oV / Math.SQRT2; + } else { + dx /= len; + dy /= len; + } + obj.ReportCollisionVsWorld(dx * pen, dy * pen, dx, dy, t); + return Circle.COL_OTHER; + } + } + return Circle.COL_NONE; + }; + Circle.prototype.ProjCircle_45Deg = function (x, y, oH, oV, obj, t) { + //if we're colliding diagonally: + // -if obj is in the diagonal pointed to by the slope normal: we can't collide, do nothing + // -else, collide vs. the appropriate vertex + //if obj is in this tile: perform collision as for aabb-ve-45deg + //if obj is horiz OR very neighb in direction of slope: collide only vs. slope + //if obj is horiz or vert neigh against direction of slope: collide vs. face + var signx = t.signx; + var signy = t.signy; + var lenP; + if(oH == 0) { + if(oV == 0) { + //colliding with current tile + var sx = t.sx; + var sy = t.sy; + var ox = (obj.pos.x - (sx * obj.radius)) - t.pos.x;//this gives is the coordinates of the innermost + + var oy = (obj.pos.y - (sy * obj.radius)) - t.pos.y;//point on the circle, relative to the tile center + + //if the dotprod of (ox,oy) and (sx,sy) is negative, the innermost point is in the slope + //and we need toproject it out by the magnitude of the projection of (ox,oy) onto (sx,sy) + var dp = (ox * sx) + (oy * sy); + if(dp < 0) { + //collision; project delta onto slope and use this as the slope penetration vector + sx *= -dp//(sx,sy) is now the penetration vector + ; + sy *= -dp; + //find the smallest axial projection vector + if(x < y) { + //penetration in x is smaller + lenP = x; + y = 0; + //get sign for projection along x-axis + if((obj.pos.x - t.pos.x) < 0) { + x *= -1; + } + } else { + //penetration in y is smaller + lenP = y; + x = 0; + //get sign for projection along y-axis + if((obj.pos.y - t.pos.y) < 0) { + y *= -1; + } + } + var lenN = Math.sqrt(sx * sx + sy * sy); + if(lenP < lenN) { + obj.ReportCollisionVsWorld(x, y, x / lenP, y / lenP, t); + return Circle.COL_AXIS; + } else { + obj.ReportCollisionVsWorld(sx, sy, t.sx, t.sy, t); + return Circle.COL_OTHER; + } + } + } else { + //colliding vertically + if((signy * oV) < 0) { + //colliding with face/edge + obj.ReportCollisionVsWorld(0, y * oV, 0, oV, t); + return Circle.COL_AXIS; + } else { + //we could only be colliding vs the slope OR a vertex + //look at the vector form the closest vert to the circle to decide + var sx = t.sx; + var sy = t.sy; + var ox = obj.pos.x - (t.pos.x - (signx * t.xw));//this gives is the coordinates of the innermost + + var oy = obj.pos.y - (t.pos.y + (oV * t.yw));//point on the circle, relative to the closest tile vert + + //if the component of (ox,oy) parallel to the normal's righthand normal + //has the same sign as the slope of the slope (the sign of the slope's slope is signx*signy) + //then we project by the vertex, otherwise by the normal. + //note that this is simply a VERY tricky/weird method of determining + //if the circle is in side the slope/face's voronoi region, or that of the vertex. + var perp = (ox * -sy) + (oy * sx); + if(0 < (perp * signx * signy)) { + //collide vs. vertex + var len = Math.sqrt(ox * ox + oy * oy); + var pen = obj.radius - len; + if(0 < pen) { + //note: if len=0, then perp=0 and we'll never reach here, so don't worry about div-by-0 + ox /= len; + oy /= len; + obj.ReportCollisionVsWorld(ox * pen, oy * pen, ox, oy, t); + return Circle.COL_OTHER; + } + } else { + //collide vs. slope + //if the component of (ox,oy) parallel to the normal is less than the circle radius, we're + //penetrating the slope. note that this method of penetration calculation doesn't hold + //in general (i.e it won't work if the circle is in the slope), but works in this case + //because we know the circle is in a neighboring cell + var dp = (ox * sx) + (oy * sy); + var pen = obj.radius - Math.abs(dp);//note: we don't need the abs because we know the dp will be positive, but just in case.. + + if(0 < pen) { + //collision; circle out along normal by penetration amount + obj.ReportCollisionVsWorld(sx * pen, sy * pen, sx, sy, t); + return Circle.COL_OTHER; + } + } + } + } + } else if(oV == 0) { + //colliding horizontally + if((signx * oH) < 0) { + //colliding with face/edge + obj.ReportCollisionVsWorld(x * oH, 0, oH, 0, t); + return Circle.COL_AXIS; + } else { + //we could only be colliding vs the slope OR a vertex + //look at the vector form the closest vert to the circle to decide + var sx = t.sx; + var sy = t.sy; + var ox = obj.pos.x - (t.pos.x + (oH * t.xw));//this gives is the coordinates of the innermost + + var oy = obj.pos.y - (t.pos.y - (signy * t.yw));//point on the circle, relative to the closest tile vert + + //if the component of (ox,oy) parallel to the normal's righthand normal + //has the same sign as the slope of the slope (the sign of the slope's slope is signx*signy) + //then we project by the normal, otherwise by the vertex. + //(NOTE: this is the opposite logic of the vertical case; + // for vertical, if the perp prod and the slope's slope agree, it's outside. + // for horizontal, if the perp prod and the slope's slope agree, circle is inside. + // ..but this is only a property of flahs' coord system (i.e the rules might swap + // in righthanded systems)) + //note that this is simply a VERY tricky/weird method of determining + //if the circle is in side the slope/face's voronio region, or that of the vertex. + var perp = (ox * -sy) + (oy * sx); + if((perp * signx * signy) < 0) { + //collide vs. vertex + var len = Math.sqrt(ox * ox + oy * oy); + var pen = obj.radius - len; + if(0 < pen) { + //note: if len=0, then perp=0 and we'll never reach here, so don't worry about div-by-0 + ox /= len; + oy /= len; + obj.ReportCollisionVsWorld(ox * pen, oy * pen, ox, oy, t); + return Circle.COL_OTHER; + } + } else { + //collide vs. slope + //if the component of (ox,oy) parallel to the normal is less than the circle radius, we're + //penetrating the slope. note that this method of penetration calculation doesn't hold + //in general (i.e it won't work if the circle is in the slope), but works in this case + //because we know the circle is in a neighboring cell + var dp = (ox * sx) + (oy * sy); + var pen = obj.radius - Math.abs(dp);//note: we don't need the abs because we know the dp will be positive, but just in case.. + + if(0 < pen) { + //collision; circle out along normal by penetration amount + obj.ReportCollisionVsWorld(sx * pen, sy * pen, sx, sy, t); + return Circle.COL_OTHER; + } + } + } + } else { + //colliding diagonally + if(0 < ((signx * oH) + (signy * oV))) { + //the dotprod of slope normal and cell offset is strictly positive, + //therefore obj is in the diagonal neighb pointed at by the normal, and + //it cannot possibly reach/touch/penetrate the slope + return Circle.COL_NONE; + } else { + //collide vs. vertex + //get diag vertex position + var vx = t.pos.x + (oH * t.xw); + var vy = t.pos.y + (oV * t.yw); + var dx = obj.pos.x - vx;//calc vert->circle vector + + var dy = obj.pos.y - vy; + var len = Math.sqrt(dx * dx + dy * dy); + var pen = obj.radius - len; + if(0 < pen) { + //vertex is in the circle; project outward + if(len == 0) { + //project out by 45deg + dx = oH / Math.SQRT2; + dy = oV / Math.SQRT2; + } else { + dx /= len; + dy /= len; + } + obj.ReportCollisionVsWorld(dx * pen, dy * pen, dx, dy, t); + return Circle.COL_OTHER; + } + } + } + return Circle.COL_NONE; + }; + Circle.prototype.ProjCircle_Concave = function (x, y, oH, oV, obj, t) { + //if we're colliding diagonally: + // -if obj is in the diagonal pointed to by the slope normal: we can't collide, do nothing + // -else, collide vs. the appropriate vertex + //if obj is in this tile: perform collision as for aabb + //if obj is horiz OR very neighb in direction of slope: collide vs vert + //if obj is horiz or vert neigh against direction of slope: collide vs. face + var signx = t.signx; + var signy = t.signy; + var lenP; + if(oH == 0) { + if(oV == 0) { + //colliding with current tile + var ox = (t.pos.x + (signx * t.xw)) - obj.pos.x;//(ox,oy) is the vector from the circle to + + var oy = (t.pos.y + (signy * t.yw)) - obj.pos.y;//tile-circle's center + + var twid = t.xw * 2; + var trad = Math.sqrt(twid * twid + 0);//this gives us the radius of a circle centered on the tile's corner and extending to the opposite edge of the tile; + + //note that this should be precomputed at compile-time since it's constant + var len = Math.sqrt(ox * ox + oy * oy); + var pen = (len + obj.radius) - trad; + if(0 < pen) { + //find the smallest axial projection vector + if(x < y) { + //penetration in x is smaller + lenP = x; + y = 0; + //get sign for projection along x-axis + if((obj.pos.x - t.pos.x) < 0) { + x *= -1; + } + } else { + //penetration in y is smaller + lenP = y; + x = 0; + //get sign for projection along y-axis + if((obj.pos.y - t.pos.y) < 0) { + y *= -1; + } + } + if(lenP < pen) { + obj.ReportCollisionVsWorld(x, y, x / lenP, y / lenP, t); + return Circle.COL_AXIS; + } else { + //we can assume that len >0, because if we're here then + //(len + obj.radius) > trad, and since obj.radius <= trad + //len MUST be > 0 + ox /= len; + oy /= len; + obj.ReportCollisionVsWorld(ox * pen, oy * pen, ox, oy, t); + return Circle.COL_OTHER; + } + } else { + return Circle.COL_NONE; + } + } else { + //colliding vertically + if((signy * oV) < 0) { + //colliding with face/edge + obj.ReportCollisionVsWorld(0, y * oV, 0, oV, t); + return Circle.COL_AXIS; + } else { + //we could only be colliding vs the vertical tip + //get diag vertex position + var vx = t.pos.x - (signx * t.xw); + var vy = t.pos.y + (oV * t.yw); + var dx = obj.pos.x - vx;//calc vert->circle vector + + var dy = obj.pos.y - vy; + var len = Math.sqrt(dx * dx + dy * dy); + var pen = obj.radius - len; + if(0 < pen) { + //vertex is in the circle; project outward + if(len == 0) { + //project out vertically + dx = 0; + dy = oV; + } else { + dx /= len; + dy /= len; + } + obj.ReportCollisionVsWorld(dx * pen, dy * pen, dx, dy, t); + return Circle.COL_OTHER; + } + } + } + } else if(oV == 0) { + //colliding horizontally + if((signx * oH) < 0) { + //colliding with face/edge + obj.ReportCollisionVsWorld(x * oH, 0, oH, 0, t); + return Circle.COL_AXIS; + } else { + //we could only be colliding vs the horizontal tip + //get diag vertex position + var vx = t.pos.x + (oH * t.xw); + var vy = t.pos.y - (signy * t.yw); + var dx = obj.pos.x - vx;//calc vert->circle vector + + var dy = obj.pos.y - vy; + var len = Math.sqrt(dx * dx + dy * dy); + var pen = obj.radius - len; + if(0 < pen) { + //vertex is in the circle; project outward + if(len == 0) { + //project out horizontally + dx = oH; + dy = 0; + } else { + dx /= len; + dy /= len; + } + obj.ReportCollisionVsWorld(dx * pen, dy * pen, dx, dy, t); + return Circle.COL_OTHER; + } + } + } else { + //colliding diagonally + if(0 < ((signx * oH) + (signy * oV))) { + //the dotprod of slope normal and cell offset is strictly positive, + //therefore obj is in the diagonal neighb pointed at by the normal, and + //it cannot possibly reach/touch/penetrate the slope + return Circle.COL_NONE; + } else { + //collide vs. vertex + //get diag vertex position + var vx = t.pos.x + (oH * t.xw); + var vy = t.pos.y + (oV * t.yw); + var dx = obj.pos.x - vx;//calc vert->circle vector + + var dy = obj.pos.y - vy; + var len = Math.sqrt(dx * dx + dy * dy); + var pen = obj.radius - len; + if(0 < pen) { + //vertex is in the circle; project outward + if(len == 0) { + //project out by 45deg + dx = oH / Math.SQRT2; + dy = oV / Math.SQRT2; + } else { + dx /= len; + dy /= len; + } + obj.ReportCollisionVsWorld(dx * pen, dy * pen, dx, dy, t); + return Circle.COL_OTHER; + } + } + } + return Circle.COL_NONE; + }; + Circle.prototype.ProjCircle_Convex = function (x, y, oH, oV, obj, t) { + //if the object is horiz AND/OR vertical neighbor in the normal (signx,signy) + //direction, collide vs. tile-circle only. + //if we're colliding diagonally: + // -else, collide vs. the appropriate vertex + //if obj is in this tile: perform collision as for aabb + //if obj is horiz or vert neigh against direction of slope: collide vs. face + var signx = t.signx; + var signy = t.signy; + var lenP; + if(oH == 0) { + if(oV == 0) { + //colliding with current tile + var ox = obj.pos.x - (t.pos.x - (signx * t.xw));//(ox,oy) is the vector from the tile-circle to + + var oy = obj.pos.y - (t.pos.y - (signy * t.yw));//the circle's center + + var twid = t.xw * 2; + var trad = Math.sqrt(twid * twid + 0);//this gives us the radius of a circle centered on the tile's corner and extending to the opposite edge of the tile; + + //note that this should be precomputed at compile-time since it's constant + var len = Math.sqrt(ox * ox + oy * oy); + var pen = (trad + obj.radius) - len; + if(0 < pen) { + //find the smallest axial projection vector + if(x < y) { + //penetration in x is smaller + lenP = x; + y = 0; + //get sign for projection along x-axis + if((obj.pos.x - t.pos.x) < 0) { + x *= -1; + } + } else { + //penetration in y is smaller + lenP = y; + x = 0; + //get sign for projection along y-axis + if((obj.pos.y - t.pos.y) < 0) { + y *= -1; + } + } + if(lenP < pen) { + obj.ReportCollisionVsWorld(x, y, x / lenP, y / lenP, t); + return Circle.COL_AXIS; + } else { + //note: len should NEVER be == 0, because if it is, + //projeciton by an axis shoudl always be shorter, and we should + //never arrive here + ox /= len; + oy /= len; + obj.ReportCollisionVsWorld(ox * pen, oy * pen, ox, oy, t); + return Circle.COL_OTHER; + } + } + } else { + //colliding vertically + if((signy * oV) < 0) { + //colliding with face/edge + obj.ReportCollisionVsWorld(0, y * oV, 0, oV, t); + return Circle.COL_AXIS; + } else { + //obj in neighboring cell pointed at by tile normal; + //we could only be colliding vs the tile-circle surface + var ox = obj.pos.x - (t.pos.x - (signx * t.xw));//(ox,oy) is the vector from the tile-circle to + + var oy = obj.pos.y - (t.pos.y - (signy * t.yw));//the circle's center + + var twid = t.xw * 2; + var trad = Math.sqrt(twid * twid + 0);//this gives us the radius of a circle centered on the tile's corner and extending to the opposite edge of the tile; + + //note that this should be precomputed at compile-time since it's constant + var len = Math.sqrt(ox * ox + oy * oy); + var pen = (trad + obj.radius) - len; + if(0 < pen) { + //note: len should NEVER be == 0, because if it is, + //obj is not in a neighboring cell! + ox /= len; + oy /= len; + obj.ReportCollisionVsWorld(ox * pen, oy * pen, ox, oy, t); + return Circle.COL_OTHER; + } + } + } + } else if(oV == 0) { + //colliding horizontally + if((signx * oH) < 0) { + //colliding with face/edge + obj.ReportCollisionVsWorld(x * oH, 0, oH, 0, t); + return Circle.COL_AXIS; + } else { + //obj in neighboring cell pointed at by tile normal; + //we could only be colliding vs the tile-circle surface + var ox = obj.pos.x - (t.pos.x - (signx * t.xw));//(ox,oy) is the vector from the tile-circle to + + var oy = obj.pos.y - (t.pos.y - (signy * t.yw));//the circle's center + + var twid = t.xw * 2; + var trad = Math.sqrt(twid * twid + 0);//this gives us the radius of a circle centered on the tile's corner and extending to the opposite edge of the tile; + + //note that this should be precomputed at compile-time since it's constant + var len = Math.sqrt(ox * ox + oy * oy); + var pen = (trad + obj.radius) - len; + if(0 < pen) { + //note: len should NEVER be == 0, because if it is, + //obj is not in a neighboring cell! + ox /= len; + oy /= len; + obj.ReportCollisionVsWorld(ox * pen, oy * pen, ox, oy, t); + return Circle.COL_OTHER; + } + } + } else { + //colliding diagonally + if(0 < ((signx * oH) + (signy * oV))) { + //obj in diag neighb cell pointed at by tile normal; + //we could only be colliding vs the tile-circle surface + var ox = obj.pos.x - (t.pos.x - (signx * t.xw));//(ox,oy) is the vector from the tile-circle to + + var oy = obj.pos.y - (t.pos.y - (signy * t.yw));//the circle's center + + var twid = t.xw * 2; + var trad = Math.sqrt(twid * twid + 0);//this gives us the radius of a circle centered on the tile's corner and extending to the opposite edge of the tile; + + //note that this should be precomputed at compile-time since it's constant + var len = Math.sqrt(ox * ox + oy * oy); + var pen = (trad + obj.radius) - len; + if(0 < pen) { + //note: len should NEVER be == 0, because if it is, + //obj is not in a neighboring cell! + ox /= len; + oy /= len; + obj.ReportCollisionVsWorld(ox * pen, oy * pen, ox, oy, t); + return Circle.COL_OTHER; + } + } else { + //collide vs. vertex + //get diag vertex position + var vx = t.pos.x + (oH * t.xw); + var vy = t.pos.y + (oV * t.yw); + var dx = obj.pos.x - vx;//calc vert->circle vector + + var dy = obj.pos.y - vy; + var len = Math.sqrt(dx * dx + dy * dy); + var pen = obj.radius - len; + if(0 < pen) { + //vertex is in the circle; project outward + if(len == 0) { + //project out by 45deg + dx = oH / Math.SQRT2; + dy = oV / Math.SQRT2; + } else { + dx /= len; + dy /= len; + } + obj.ReportCollisionVsWorld(dx * pen, dy * pen, dx, dy, t); + return Circle.COL_OTHER; + } + } + } + return Circle.COL_NONE; + }; + return Circle; +})(); +(function () { + var myGame = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + function init() { + myGame.loader.addImageFile('atari1', 'assets/sprites/atari130xe.png'); + myGame.loader.load(); + } + physics: +NPhysics + c: +Circle + t: +TileMapCell + function create() { + this.physics = new NPhysics(); + this.c = new Circle(200, 100, 25); + // pos is center, not upper-left + this.t = new TileMapCell(200, 500, 100, 100); + //this.t.SetState(TileMapCell.TID_FULL); + //this.t.SetState(TileMapCell.TID_45DEGpn); + //this.t.SetState(TileMapCell.TID_CONCAVEpn); + this.t.SetState(TileMapCell.TID_CONVEXpn); + } + function update() { + var fx = 0; + var fy = 0; + if(myGame.input.keyboard.isDown(Phaser.Keyboard.LEFT)) { + fx -= 0.2; + } else if(myGame.input.keyboard.isDown(Phaser.Keyboard.RIGHT)) { + fx += 0.2; + } + if(myGame.input.keyboard.isDown(Phaser.Keyboard.UP)) { + fy -= 0.2 + 0.2; + } else if(myGame.input.keyboard.isDown(Phaser.Keyboard.DOWN)) { + fy += 0.2; + } + var p = this.c.pos; + var o = this.c.oldpos; + var vx = p.x - o.x; + var vy = p.y - o.y; + var newx = Math.min(20, Math.max(-20, vx + fx)); + var newy = Math.min(20, Math.max(-20, vy + fy)); + p.x = o.x + newx; + p.y = o.y + newy; + this.c.IntegrateVerlet(); + this.c.CollideCircleVsTile(this.t); + this.c.CollideCircleVsWorldBounds(); + } + function render() { + this.c.render(myGame.stage.context); + this.t.render(myGame.stage.context); + } +})(); diff --git a/Tests/physics/temp2.ts b/Tests/physics/temp2.ts new file mode 100644 index 00000000..459680c5 --- /dev/null +++ b/Tests/physics/temp2.ts @@ -0,0 +1,1683 @@ +/// + +class NPhysics { + + grav: number = 0.2; + drag: number = 1; + bounce: number = 0.3; + friction: number = 0.05; + + min_f: number = 0; + max_f: number = 1; + + min_b: number = 0; + max_b: number = 1; + + min_g: number = 0; + max_g = 1; + + xmin: number = 0; + xmax: number = 800; + ymin: number = 0; + ymax: number = 600; + + objrad: number = 24; + tilerad: number = 24*2; + objspeed: number = 0.2; + maxspeed: number = 20; + + public update() { + // demoObj.Verlet(); + // demoObj.CollideVsWorldBounds(); + } + +} + +class AABB { + + constructor(pos: Phaser.Vector2, xw, yw) { + this.pos = pos.clone(); + this.oldpos = pos.clone(); + this.xw = Math.abs(xw); + this.yw = Math.abs(yw); + } + + type:number = 0; + pos: Phaser.Vector2; + oldpos: Phaser.Vector2; + xw: number; + yw: number; + +} + +class TileMapCell { + + //TILETYPE ENUMERATION + static TID_EMPTY = 0; + static TID_FULL = 1;//fullAABB tile + static TID_45DEGpn = 2;//45-degree triangle, whose normal is (+ve,-ve) + static TID_45DEGnn = 3;//(+ve,+ve) + static TID_45DEGnp = 4;//(-ve,+ve) + static TID_45DEGpp = 5;//(-ve,-ve) + static TID_CONCAVEpn = 6;//1/4-circle cutout + static TID_CONCAVEnn = 7; + static TID_CONCAVEnp = 8; + static TID_CONCAVEpp = 9; + static TID_CONVEXpn = 10;//1/4/circle + static TID_CONVEXnn = 11; + static TID_CONVEXnp = 12; + static TID_CONVEXpp = 13; + static TID_22DEGpnS = 14;//22.5 degree slope + static TID_22DEGnnS = 15; + static TID_22DEGnpS = 16; + static TID_22DEGppS = 17; + static TID_22DEGpnB = 18; + static TID_22DEGnnB = 19; + static TID_22DEGnpB = 20; + static TID_22DEGppB = 21; + static TID_67DEGpnS = 22;//67.5 degree slope + static TID_67DEGnnS = 23; + static TID_67DEGnpS = 24; + static TID_67DEGppS = 25; + static TID_67DEGpnB = 26; + static TID_67DEGnnB = 27; + static TID_67DEGnpB = 28; + static TID_67DEGppB = 29; + static TID_HALFd = 30;//half-full tiles + static TID_HALFr = 31; + static TID_HALFu = 32; + static TID_HALFl = 33; + + //collision shape "types" + static CTYPE_EMPTY = 0; + static CTYPE_FULL = 1; + static CTYPE_45DEG = 2; + static CTYPE_CONCAVE = 6; + static CTYPE_CONVEX = 10; + static CTYPE_22DEGs = 14; + static CTYPE_22DEGb = 18; + static CTYPE_67DEGs = 22; + static CTYPE_67DEGb = 26; + static CTYPE_HALF = 30; + + ID; + CTYPE; + pos: Phaser.Vector2; + xw; + yw; + minx; + maxx; + miny; + maxy; + signx; + signy; + sx; + sy; + + constructor(x,y,xw,yw) { + + this.ID = TileMapCell.TID_EMPTY; //all tiles start empty + this.CTYPE = TileMapCell.CTYPE_EMPTY; + + this.pos = new Phaser.Vector2(x,y); //setup collision properties + this.xw = xw; + this.yw = yw; + this.minx = this.pos.x - this.xw; + this.maxx = this.pos.x + this.xw; + this.miny = this.pos.y - this.yw; + this.maxy = this.pos.y + this.yw; + + //this stores tile-specific collision information + this.signx = 0; + this.signy = 0; + this.sx = 0; + this.sy = 0; + } + + //these functions are used to update the cell + //note: ID is assumed to NOT be "empty" state.. + //if it IS the empty state, the tile clears itself + SetState(ID) { + if (ID == TileMapCell.TID_EMPTY) + { + this.Clear(); + } + else + { + //set tile state to a non-emtpy value, and update it's edges and those of the neighbors + this.ID = ID; + this.UpdateType(); + //this.Draw(); + } + } + + Clear() { + //tile was on, turn it off + this.ID = TileMapCell.TID_EMPTY + this.UpdateType(); + //this.Draw(); + } + + public render(context: CanvasRenderingContext2D) { + + context.beginPath(); + context.strokeStyle = 'rgb(255,255,0)'; + context.strokeRect(this.minx, this.miny, this.xw * 2, this.yw * 2); + context.strokeRect(this.pos.x, this.pos.y, 2, 2); + context.closePath(); + + } + + //this converts a tile from implicitly-defined (via ID), to explicit (via properties) + UpdateType() { + if (0 < this.ID) + { + //tile is non-empty; collide + if (this.ID < TileMapCell.CTYPE_45DEG) + { + //TID_FULL + this.CTYPE = TileMapCell.CTYPE_FULL; + this.signx = 0; + this.signy = 0; + this.sx = 0; + this.sy = 0; + + } + else if (this.ID < TileMapCell.CTYPE_CONCAVE) + { + + //45deg + this.CTYPE = TileMapCell.CTYPE_45DEG; + if (this.ID == TileMapCell.TID_45DEGpn) + { + console.log('set tile as 45deg pn'); + this.signx = 1; + this.signy = -1; + this.sx = this.signx / Math.SQRT2;//get slope _unit_ normal + this.sy = this.signy / Math.SQRT2;//since normal is (1,-1), length is sqrt(1*1 + -1*-1) = sqrt(2) + + } + else if (this.ID == TileMapCell.TID_45DEGnn) + { + this.signx = -1; + this.signy = -1; + this.sx = this.signx / Math.SQRT2;//get slope _unit_ normal + this.sy = this.signy / Math.SQRT2;//since normal is (1,-1), length is sqrt(1*1 + -1*-1) = sqrt(2) + + } + else if (this.ID == TileMapCell.TID_45DEGnp) + { + this.signx = -1; + this.signy = 1; + this.sx = this.signx / Math.SQRT2;//get slope _unit_ normal + this.sy = this.signy / Math.SQRT2;//since normal is (1,-1), length is sqrt(1*1 + -1*-1) = sqrt(2) + } + else if (this.ID == TileMapCell.TID_45DEGpp) + { + this.signx = 1; + this.signy = 1; + this.sx = this.signx / Math.SQRT2;//get slope _unit_ normal + this.sy = this.signy / Math.SQRT2;//since normal is (1,-1), length is sqrt(1*1 + -1*-1) = sqrt(2) + } + else + { + //trace("BAAAD TILE!!!!!: ID=" + this.ID + " ("+ t.i + "," + t.j + ")"); + return false; + } + } + else if (this.ID < TileMapCell.CTYPE_CONVEX) + { + + //concave + this.CTYPE = TileMapCell.CTYPE_CONCAVE; + if (this.ID == TileMapCell.TID_CONCAVEpn) + { + this.signx = 1; + this.signy = -1; + this.sx = 0; + this.sy = 0; + } + else if (this.ID == TileMapCell.TID_CONCAVEnn) + { + this.signx = -1; + this.signy = -1; + this.sx = 0; + this.sy = 0; + } + else if (this.ID == TileMapCell.TID_CONCAVEnp) + { + this.signx = -1; + this.signy = 1; + this.sx = 0; + this.sy = 0; + } + else if (this.ID == TileMapCell.TID_CONCAVEpp) + { + this.signx = 1; + this.signy = 1; + this.sx = 0; + this.sy = 0; + } + else + { + //trace("BAAAD TILE!!!!!: ID=" + this.ID + " ("+ t.i + "," + t.j + ")"); + return false; + } + } + else if (this.ID < TileMapCell.CTYPE_22DEGs) + { + + //convex + this.CTYPE = TileMapCell.CTYPE_CONVEX; + if (this.ID == TileMapCell.TID_CONVEXpn) + { + this.signx = 1; + this.signy = -1; + this.sx = 0; + this.sy = 0; + } + else if (this.ID == TileMapCell.TID_CONVEXnn) + { + this.signx = -1; + this.signy = -1; + this.sx = 0; + this.sy = 0; + } + else if (this.ID == TileMapCell.TID_CONVEXnp) + { + this.signx = -1; + this.signy = 1; + this.sx = 0; + this.sy = 0; + } + else if (this.ID == TileMapCell.TID_CONVEXpp) + { + this.signx = 1; + this.signy = 1; + this.sx = 0; + this.sy = 0; + } + else + { + //trace("BAAAD TILE!!!!!: ID=" + this.ID + " ("+ t.i + "," + t.j + ")"); + return false; + } + } + else if (this.ID < TileMapCell.CTYPE_22DEGb) + { + + //22deg small + this.CTYPE = TileMapCell.CTYPE_22DEGs; + if (this.ID == TileMapCell.TID_22DEGpnS) + { + this.signx = 1; + this.signy = -1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 1) / slen; + this.sy = (this.signy * 2) / slen; + } + else if (this.ID == TileMapCell.TID_22DEGnnS) + { + this.signx = -1; + this.signy = -1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 1) / slen; + this.sy = (this.signy * 2) / slen; + } + else if (this.ID == TileMapCell.TID_22DEGnpS) + { + this.signx = -1; + this.signy = 1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 1) / slen; + this.sy = (this.signy * 2) / slen; + } + else if (this.ID == TileMapCell.TID_22DEGppS) + { + this.signx = 1; + this.signy = 1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 1) / slen; + this.sy = (this.signy * 2) / slen; + } + else + { + //trace("BAAAD TILE!!!!!: ID=" + this.ID + " ("+ t.i + "," + t.j + ")"); + return false; + } + } + else if (this.ID < TileMapCell.CTYPE_67DEGs) + { + + //22deg big + this.CTYPE = TileMapCell.CTYPE_22DEGb; + if (this.ID == TileMapCell.TID_22DEGpnB) + { + this.signx = 1; + this.signy = -1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 1) / slen; + this.sy = (this.signy * 2) / slen; + } + else if (this.ID == TileMapCell.TID_22DEGnnB) + { + this.signx = -1; + this.signy = -1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 1) / slen; + this.sy = (this.signy * 2) / slen; + } + else if (this.ID == TileMapCell.TID_22DEGnpB) + { + this.signx = -1; + this.signy = 1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 1) / slen; + this.sy = (this.signy * 2) / slen; + } + else if (this.ID == TileMapCell.TID_22DEGppB) + { + this.signx = 1; + this.signy = 1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 1) / slen; + this.sy = (this.signy * 2) / slen; + } + else + { + //trace("BAAAD TILE!!!!!: ID=" + this.ID + " ("+ t.i + "," + t.j + ")"); + return false; + } + } + else if (this.ID < TileMapCell.CTYPE_67DEGb) + { + + //67deg small + this.CTYPE = TileMapCell.CTYPE_67DEGs; + if (this.ID == TileMapCell.TID_67DEGpnS) + { + this.signx = 1; + this.signy = -1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 2) / slen; + this.sy = (this.signy * 1) / slen; + } + else if (this.ID == TileMapCell.TID_67DEGnnS) + { + this.signx = -1; + this.signy = -1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 2) / slen; + this.sy = (this.signy * 1) / slen; + } + else if (this.ID == TileMapCell.TID_67DEGnpS) + { + this.signx = -1; + this.signy = 1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 2) / slen; + this.sy = (this.signy * 1) / slen; + } + else if (this.ID == TileMapCell.TID_67DEGppS) + { + this.signx = 1; + this.signy = 1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 2) / slen; + this.sy = (this.signy * 1) / slen; + } + else + { + //trace("BAAAD TILE!!!!!: ID=" + this.ID + " ("+ t.i + "," + t.j + ")"); + return false; + } + } + else if (this.ID < TileMapCell.CTYPE_HALF) + { + + //67deg big + this.CTYPE = TileMapCell.CTYPE_67DEGb; + if (this.ID == TileMapCell.TID_67DEGpnB) + { + this.signx = 1; + this.signy = -1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 2) / slen; + this.sy = (this.signy * 1) / slen; + } + else if (this.ID == TileMapCell.TID_67DEGnnB) + { + this.signx = -1; + this.signy = -1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 2) / slen; + this.sy = (this.signy * 1) / slen; + } + else if (this.ID == TileMapCell.TID_67DEGnpB) + { + this.signx = -1; + this.signy = 1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 2) / slen; + this.sy = (this.signy * 1) / slen; + } + else if (this.ID == TileMapCell.TID_67DEGppB) + { + this.signx = 1; + this.signy = 1; + var slen = Math.sqrt(2 * 2 + 1 * 1); + this.sx = (this.signx * 2) / slen; + this.sy = (this.signy * 1) / slen; + } + else + { + //trace("BAAAD TILE!!!!!: ID=" + this.ID + " ("+ t.i + "," + t.j + ")"); + return false; + } + } + else + { + //half-full tile + this.CTYPE = TileMapCell.CTYPE_HALF; + if (this.ID == TileMapCell.TID_HALFd) + { + this.signx = 0; + this.signy = -1; + this.sx = this.signx; + this.sy = this.signy; + } + else if (this.ID == TileMapCell.TID_HALFu) + { + this.signx = 0; + this.signy = 1; + this.sx = this.signx; + this.sy = this.signy; + } + else if (this.ID == TileMapCell.TID_HALFl) + { + this.signx = 1; + this.signy = 0; + this.sx = this.signx; + this.sy = this.signy; + } + else if (this.ID == TileMapCell.TID_HALFr) + { + this.signx = -1; + this.signy = 0; + this.sx = this.signx; + this.sy = this.signy; + } + else + { + //trace("BAAD TILE!!!: ID=" + this.ID + " ("+ t.i + "," + t.j + ")"); + return false; + } + + } + + } + else + { + //TID_EMPTY + this.CTYPE = TileMapCell.CTYPE_EMPTY; + this.signx = 0; + this.signy = 0; + this.sx = 0; + this.sy = 0; + } + } + +} + +class Circle { + + constructor(x, y, radius) { + + this.pos = new Phaser.Vector2(x, y); + this.oldpos = this.pos.clone(); + this.radius = radius; + this.circleTileProjections = {};//hash object to hold tile-specific collision functions + this.circleTileProjections[TileMapCell.CTYPE_FULL] = this.ProjCircle_Full; + this.circleTileProjections[TileMapCell.CTYPE_45DEG] = this.ProjCircle_45Deg; + this.circleTileProjections[TileMapCell.CTYPE_CONCAVE] = this.ProjCircle_Concave; + this.circleTileProjections[TileMapCell.CTYPE_CONVEX] = this.ProjCircle_Convex; + + //Proj_CircleTile[CTYPE_22DEGs] = ProjCircle_22DegS; + //Proj_CircleTile[CTYPE_22DEGb] = ProjCircle_22DegB; + //Proj_CircleTile[CTYPE_67DEGs] = ProjCircle_67DegS; + //Proj_CircleTile[CTYPE_67DEGb] = ProjCircle_67DegB; + //Proj_CircleTile[CTYPE_HALF] = ProjCircle_Half; + + } + + type:number = 1; + pos: Phaser.Vector2; + oldpos: Phaser.Vector2; + radius: number; + circleTileProjections; + + public IntegrateVerlet() { + + //var d = DRAG; + //var g = GRAV; + var d = 1; + var g = 0.2; + + var p = this.pos; + var o = this.oldpos; + var px, py; + + var ox = o.x; //we can't swap buffers since mcs/sticks point directly to vector2s.. + var oy = o.y; + o.x = px = p.x; //get vector values + o.y = py = p.y; //p = position + //o = oldposition + + //integrate + p.x += (d * px) - (d * ox); + p.y += (d * py) - (d * oy) + g; + + } + + public ReportCollisionVsWorld(px, py, dx, dy, obj: TileMapCell) { + + var p = this.pos; + var o = this.oldpos; + + //calc velocity + var vx = p.x - o.x; + var vy = p.y - o.y; + + //find component of velocity parallel to collision normal + var dp = (vx * dx + vy * dy); + var nx = dp * dx;//project velocity onto collision normal + + var ny = dp * dy;//nx,ny is normal velocity + + var tx = vx - nx;//px,py is tangent velocity + var ty = vy - ny; + + //we only want to apply collision response forces if the object is travelling into, and not out of, the collision + var b, bx, by, f, fx, fy; + + if (dp < 0) + { + //f = FRICTION; + f = 0.05; + fx = tx * f; + fy = ty * f; + + //b = 1 + BOUNCE;//this bounce constant should be elsewhere, i.e inside the object/tile/etc.. + b = 1 + 0.3;//this bounce constant should be elsewhere, i.e inside the object/tile/etc.. + + bx = (nx * b); + by = (ny * b); + + } + else + { + //moving out of collision, do not apply forces + bx = by = fx = fy = 0; + } + + p.x += px;//project object out of collision + p.y += py; + + o.x += px + bx + fx;//apply bounce+friction impulses which alter velocity + o.y += py + by + fy; + + } + + public CollideCircleVsWorldBounds() { + var p = this.pos; + var r = this.radius; + var XMIN = 0; + var XMAX = 800; + var YMIN = 0; + var YMAX = 600; + + //collide vs. x-bounds + //test XMIN + var dx = XMIN - (p.x - r); + if (0 < dx) + { + //object is colliding with XMIN + this.ReportCollisionVsWorld(dx, 0, 1, 0, null); + } + else + { + //test XMAX + dx = (p.x + r) - XMAX; + if (0 < dx) + { + //object is colliding with XMAX + this.ReportCollisionVsWorld(-dx, 0, -1, 0, null); + } + } + + //collide vs. y-bounds + //test YMIN + var dy = YMIN - (p.y - r); + if (0 < dy) + { + //object is colliding with YMIN + this.ReportCollisionVsWorld(0, dy, 0, 1, null); + } + else + { + //test YMAX + dy = (p.y + r) - YMAX; + if (0 < dy) + { + //object is colliding with YMAX + this.ReportCollisionVsWorld(0, -dy, 0, -1, null); + } + } + } + + public render(context:CanvasRenderingContext2D) { + + context.beginPath(); + context.strokeStyle = 'rgb(0,255,0)'; + context.arc(this.pos.x, this.pos.y, this.radius, 0, Math.PI * 2); + context.stroke(); + context.closePath(); + + if (this.oH == 1) + { + context.beginPath(); + context.strokeStyle = 'rgb(255,0,0)'; + context.moveTo(this.pos.x - this.radius, this.pos.y - this.radius); + context.lineTo(this.pos.x - this.radius, this.pos.y + this.radius); + context.stroke(); + context.closePath(); + } + else if (this.oH == -1) + { + context.beginPath(); + context.strokeStyle = 'rgb(255,0,0)'; + context.moveTo(this.pos.x + this.radius, this.pos.y - this.radius); + context.lineTo(this.pos.x + this.radius, this.pos.y + this.radius); + context.stroke(); + context.closePath(); + } + + if (this.oV == 1) + { + context.beginPath(); + context.strokeStyle = 'rgb(255,0,0)'; + context.moveTo(this.pos.x - this.radius, this.pos.y - this.radius); + context.lineTo(this.pos.x + this.radius, this.pos.y - this.radius); + context.stroke(); + context.closePath(); + } + else if (this.oV == -1) + { + context.beginPath(); + context.strokeStyle = 'rgb(255,0,0)'; + context.moveTo(this.pos.x - this.radius, this.pos.y + this.radius); + context.lineTo(this.pos.x + this.radius, this.pos.y + this.radius); + context.stroke(); + context.closePath(); + } + + } + + public oH: number; + public oV: number; + + public CollideCircleVsTile(tile) { + var pos = this.pos; + var r = this.radius; + var c = tile; + + var tx = c.pos.x; + var ty = c.pos.y; + var txw = c.xw; + var tyw = c.yw; + + var dx = pos.x - tx;//tile->obj delta + var px = (txw + r) - Math.abs(dx);//penetration depth in x + + if (0 < px) + { + var dy = pos.y - ty;//tile->obj delta + var py = (tyw + r) - Math.abs(dy);//pen depth in y + + if (0 < py) + { + //object may be colliding with tile + + //determine grid/voronoi region of circle center + this.oH = 0; + this.oV = 0; + if (dx < -txw) + { + //circle is on left side of tile + this.oH = -1; + } + else if (txw < dx) + { + //circle is on right side of tile + this.oH = 1; + } + + if (dy < -tyw) + { + //circle is on top side of tile + this.oV = -1; + } + else if (tyw < dy) + { + //circle is on bottom side of tile + this.oV = 1; + } + + this.ResolveCircleTile(px, py, this.oH, this.oV, this, c); + + } + } + } + + public ResolveCircleTile(x, y, oH, oV, obj, t) { + + if (0 < t.ID) + { + return this.circleTileProjections[t.CTYPE](x, y, oH, oV, obj, t); + } + else + { + console.log("ResolveCircleTile() was called with an empty (or unknown) tile!: ID=" + t.ID + " (" + t.i + "," + t.j + ")"); + return false; + } + } + + static COL_NONE = 0; + static COL_AXIS = 1; + static COL_OTHER = 2; + + public ProjCircle_Full(x, y, oH, oV, obj:Circle, t:TileMapCell) { + + //if we're colliding vs. the current cell, we need to project along the + //smallest penetration vector. + //if we're colliding vs. horiz. or vert. neighb, we simply project horiz/vert + //if we're colliding diagonally, we need to collide vs. tile corner + + if (oH == 0) + { + if (oV == 0) + { + //collision with current cell + if (x < y) + { + //penetration in x is smaller; project in x + var dx = obj.pos.x - t.pos.x;//get sign for projection along x-axis + + //NOTE: should we handle the delta == 0 case?! and how? (project towards oldpos?) + if (dx < 0) + { + obj.ReportCollisionVsWorld(-x, 0, -1, 0, t); + return Circle.COL_AXIS; + } + else + { + obj.ReportCollisionVsWorld(x, 0, 1, 0, t); + return Circle.COL_AXIS; + } + } + else + { + //penetration in y is smaller; project in y + var dy = obj.pos.y - t.pos.y;//get sign for projection along y-axis + + //NOTE: should we handle the delta == 0 case?! and how? (project towards oldpos?) + if (dy < 0) + { + obj.ReportCollisionVsWorld(0, -y, 0, -1, t); + return Circle.COL_AXIS; + } + else + { + obj.ReportCollisionVsWorld(0, y, 0, 1, t); + return Circle.COL_AXIS; + } + } + } + else + { + //collision with vertical neighbor + obj.ReportCollisionVsWorld(0, y * oV, 0, oV, t); + + return Circle.COL_AXIS; + } + } + else if (oV == 0) + { + //collision with horizontal neighbor + obj.ReportCollisionVsWorld(x * oH, 0, oH, 0, t); + return Circle.COL_AXIS; + } + else + { + //diagonal collision + + //get diag vertex position + var vx = t.pos.x + (oH * t.xw); + var vy = t.pos.y + (oV * t.yw); + + var dx = obj.pos.x - vx;//calc vert->circle vector + var dy = obj.pos.y - vy; + + var len = Math.sqrt(dx * dx + dy * dy); + var pen = obj.radius - len; + if (0 < pen) + { + //vertex is in the circle; project outward + if (len == 0) + { + //project out by 45deg + dx = oH / Math.SQRT2; + dy = oV / Math.SQRT2; + } + else + { + dx /= len; + dy /= len; + } + + obj.ReportCollisionVsWorld(dx * pen, dy * pen, dx, dy, t); + + return Circle.COL_OTHER; + } + } + + return Circle.COL_NONE; + + } + + public ProjCircle_45Deg(x, y, oH, oV, obj: Circle, t: TileMapCell) { + + //if we're colliding diagonally: + // -if obj is in the diagonal pointed to by the slope normal: we can't collide, do nothing + // -else, collide vs. the appropriate vertex + //if obj is in this tile: perform collision as for aabb-ve-45deg + //if obj is horiz OR very neighb in direction of slope: collide only vs. slope + //if obj is horiz or vert neigh against direction of slope: collide vs. face + + var signx = t.signx; + var signy = t.signy; + var lenP; + + if (oH == 0) + { + if (oV == 0) + { + //colliding with current tile + + var sx = t.sx; + var sy = t.sy; + + var ox = (obj.pos.x - (sx * obj.radius)) - t.pos.x;//this gives is the coordinates of the innermost + var oy = (obj.pos.y - (sy * obj.radius)) - t.pos.y;//point on the circle, relative to the tile center + + //if the dotprod of (ox,oy) and (sx,sy) is negative, the innermost point is in the slope + //and we need toproject it out by the magnitude of the projection of (ox,oy) onto (sx,sy) + var dp = (ox * sx) + (oy * sy); + if (dp < 0) + { + //collision; project delta onto slope and use this as the slope penetration vector + sx *= -dp;//(sx,sy) is now the penetration vector + sy *= -dp; + + //find the smallest axial projection vector + if (x < y) + { + //penetration in x is smaller + lenP = x; + y = 0; + + //get sign for projection along x-axis + if ((obj.pos.x - t.pos.x) < 0) + { + x *= -1; + } + } + else + { + //penetration in y is smaller + lenP = y; + x = 0; + + //get sign for projection along y-axis + if ((obj.pos.y - t.pos.y) < 0) + { + y *= -1; + } + } + + var lenN = Math.sqrt(sx * sx + sy * sy); + + if (lenP < lenN) + { + obj.ReportCollisionVsWorld(x, y, x / lenP, y / lenP, t); + + return Circle.COL_AXIS; + } + else + { + obj.ReportCollisionVsWorld(sx, sy, t.sx, t.sy, t); + + return Circle.COL_OTHER; + } + } + + } + else + { + //colliding vertically + if ((signy * oV) < 0) + { + //colliding with face/edge + obj.ReportCollisionVsWorld(0, y * oV, 0, oV, t); + + return Circle.COL_AXIS; + } + else + { + //we could only be colliding vs the slope OR a vertex + //look at the vector form the closest vert to the circle to decide + + var sx = t.sx; + var sy = t.sy; + + var ox = obj.pos.x - (t.pos.x - (signx * t.xw));//this gives is the coordinates of the innermost + var oy = obj.pos.y - (t.pos.y + (oV * t.yw));//point on the circle, relative to the closest tile vert + + //if the component of (ox,oy) parallel to the normal's righthand normal + //has the same sign as the slope of the slope (the sign of the slope's slope is signx*signy) + //then we project by the vertex, otherwise by the normal. + //note that this is simply a VERY tricky/weird method of determining + //if the circle is in side the slope/face's voronoi region, or that of the vertex. + var perp = (ox * -sy) + (oy * sx); + if (0 < (perp * signx * signy)) + { + //collide vs. vertex + var len = Math.sqrt(ox * ox + oy * oy); + var pen = obj.radius - len; + if (0 < pen) + { + //note: if len=0, then perp=0 and we'll never reach here, so don't worry about div-by-0 + ox /= len; + oy /= len; + + obj.ReportCollisionVsWorld(ox * pen, oy * pen, ox, oy, t); + + return Circle.COL_OTHER; + } + } + else + { + //collide vs. slope + + //if the component of (ox,oy) parallel to the normal is less than the circle radius, we're + //penetrating the slope. note that this method of penetration calculation doesn't hold + //in general (i.e it won't work if the circle is in the slope), but works in this case + //because we know the circle is in a neighboring cell + var dp = (ox * sx) + (oy * sy); + var pen = obj.radius - Math.abs(dp);//note: we don't need the abs because we know the dp will be positive, but just in case.. + if (0 < pen) + { + //collision; circle out along normal by penetration amount + obj.ReportCollisionVsWorld(sx * pen, sy * pen, sx, sy, t); + + return Circle.COL_OTHER; + } + } + } + } + } + else if (oV == 0) + { + //colliding horizontally + if ((signx * oH) < 0) + { + //colliding with face/edge + obj.ReportCollisionVsWorld(x * oH, 0, oH, 0, t); + + return Circle.COL_AXIS; + } + else + { + //we could only be colliding vs the slope OR a vertex + //look at the vector form the closest vert to the circle to decide + + var sx = t.sx; + var sy = t.sy; + + var ox = obj.pos.x - (t.pos.x + (oH * t.xw));//this gives is the coordinates of the innermost + var oy = obj.pos.y - (t.pos.y - (signy * t.yw));//point on the circle, relative to the closest tile vert + + //if the component of (ox,oy) parallel to the normal's righthand normal + //has the same sign as the slope of the slope (the sign of the slope's slope is signx*signy) + //then we project by the normal, otherwise by the vertex. + //(NOTE: this is the opposite logic of the vertical case; + // for vertical, if the perp prod and the slope's slope agree, it's outside. + // for horizontal, if the perp prod and the slope's slope agree, circle is inside. + // ..but this is only a property of flahs' coord system (i.e the rules might swap + // in righthanded systems)) + //note that this is simply a VERY tricky/weird method of determining + //if the circle is in side the slope/face's voronio region, or that of the vertex. + var perp = (ox * -sy) + (oy * sx); + if ((perp * signx * signy) < 0) + { + //collide vs. vertex + var len = Math.sqrt(ox * ox + oy * oy); + var pen = obj.radius - len; + if (0 < pen) + { + //note: if len=0, then perp=0 and we'll never reach here, so don't worry about div-by-0 + ox /= len; + oy /= len; + + obj.ReportCollisionVsWorld(ox * pen, oy * pen, ox, oy, t); + + return Circle.COL_OTHER; + } + } + else + { + //collide vs. slope + + //if the component of (ox,oy) parallel to the normal is less than the circle radius, we're + //penetrating the slope. note that this method of penetration calculation doesn't hold + //in general (i.e it won't work if the circle is in the slope), but works in this case + //because we know the circle is in a neighboring cell + var dp = (ox * sx) + (oy * sy); + var pen = obj.radius - Math.abs(dp);//note: we don't need the abs because we know the dp will be positive, but just in case.. + if (0 < pen) + { + //collision; circle out along normal by penetration amount + obj.ReportCollisionVsWorld(sx * pen, sy * pen, sx, sy, t); + + return Circle.COL_OTHER; + } + } + } + } + else + { + //colliding diagonally + if (0 < ((signx * oH) + (signy * oV))) + { + //the dotprod of slope normal and cell offset is strictly positive, + //therefore obj is in the diagonal neighb pointed at by the normal, and + //it cannot possibly reach/touch/penetrate the slope + return Circle.COL_NONE; + } + else + { + //collide vs. vertex + //get diag vertex position + var vx = t.pos.x + (oH * t.xw); + var vy = t.pos.y + (oV * t.yw); + + var dx = obj.pos.x - vx;//calc vert->circle vector + var dy = obj.pos.y - vy; + + var len = Math.sqrt(dx * dx + dy * dy); + var pen = obj.radius - len; + if (0 < pen) + { + //vertex is in the circle; project outward + if (len == 0) + { + //project out by 45deg + dx = oH / Math.SQRT2; + dy = oV / Math.SQRT2; + } + else + { + dx /= len; + dy /= len; + } + + obj.ReportCollisionVsWorld(dx * pen, dy * pen, dx, dy, t); + return Circle.COL_OTHER; + } + + } + + } + + return Circle.COL_NONE; + } + + public ProjCircle_Concave(x, y, oH, oV, obj: Circle, t: TileMapCell) { + + //if we're colliding diagonally: + // -if obj is in the diagonal pointed to by the slope normal: we can't collide, do nothing + // -else, collide vs. the appropriate vertex + //if obj is in this tile: perform collision as for aabb + //if obj is horiz OR very neighb in direction of slope: collide vs vert + //if obj is horiz or vert neigh against direction of slope: collide vs. face + + var signx = t.signx; + var signy = t.signy; + var lenP; + + if (oH == 0) + { + if (oV == 0) + { + //colliding with current tile + + var ox = (t.pos.x + (signx * t.xw)) - obj.pos.x;//(ox,oy) is the vector from the circle to + var oy = (t.pos.y + (signy * t.yw)) - obj.pos.y;//tile-circle's center + + var twid = t.xw * 2; + var trad = Math.sqrt(twid * twid + 0);//this gives us the radius of a circle centered on the tile's corner and extending to the opposite edge of the tile; + //note that this should be precomputed at compile-time since it's constant + + var len = Math.sqrt(ox * ox + oy * oy); + var pen = (len + obj.radius) - trad; + + if (0 < pen) + { + //find the smallest axial projection vector + if (x < y) + { + //penetration in x is smaller + lenP = x; + y = 0; + + //get sign for projection along x-axis + if ((obj.pos.x - t.pos.x) < 0) + { + x *= -1; + } + } + else + { + //penetration in y is smaller + lenP = y; + x = 0; + + //get sign for projection along y-axis + if ((obj.pos.y - t.pos.y) < 0) + { + y *= -1; + } + } + + + if (lenP < pen) + { + obj.ReportCollisionVsWorld(x, y, x / lenP, y / lenP, t); + + return Circle.COL_AXIS; + } + else + { + //we can assume that len >0, because if we're here then + //(len + obj.radius) > trad, and since obj.radius <= trad + //len MUST be > 0 + ox /= len; + oy /= len; + + obj.ReportCollisionVsWorld(ox * pen, oy * pen, ox, oy, t); + + return Circle.COL_OTHER; + } + } + else + { + return Circle.COL_NONE; + } + + } + else + { + //colliding vertically + if ((signy * oV) < 0) + { + //colliding with face/edge + obj.ReportCollisionVsWorld(0, y * oV, 0, oV, t); + + return Circle.COL_AXIS; + } + else + { + //we could only be colliding vs the vertical tip + + //get diag vertex position + var vx = t.pos.x - (signx * t.xw); + var vy = t.pos.y + (oV * t.yw); + + var dx = obj.pos.x - vx;//calc vert->circle vector + var dy = obj.pos.y - vy; + + var len = Math.sqrt(dx * dx + dy * dy); + var pen = obj.radius - len; + if (0 < pen) + { + //vertex is in the circle; project outward + if (len == 0) + { + //project out vertically + dx = 0; + dy = oV; + } + else + { + dx /= len; + dy /= len; + } + + obj.ReportCollisionVsWorld(dx * pen, dy * pen, dx, dy, t); + + return Circle.COL_OTHER; + } + } + } + } + else if (oV == 0) + { + //colliding horizontally + if ((signx * oH) < 0) + { + //colliding with face/edge + obj.ReportCollisionVsWorld(x * oH, 0, oH, 0, t); + + return Circle.COL_AXIS; + } + else + { + //we could only be colliding vs the horizontal tip + + //get diag vertex position + var vx = t.pos.x + (oH * t.xw); + var vy = t.pos.y - (signy * t.yw); + + var dx = obj.pos.x - vx;//calc vert->circle vector + var dy = obj.pos.y - vy; + + var len = Math.sqrt(dx * dx + dy * dy); + var pen = obj.radius - len; + if (0 < pen) + { + //vertex is in the circle; project outward + if (len == 0) + { + //project out horizontally + dx = oH; + dy = 0; + } + else + { + dx /= len; + dy /= len; + } + + obj.ReportCollisionVsWorld(dx * pen, dy * pen, dx, dy, t); + + return Circle.COL_OTHER; + } + } + } + else + { + //colliding diagonally + if (0 < ((signx * oH) + (signy * oV))) + { + //the dotprod of slope normal and cell offset is strictly positive, + //therefore obj is in the diagonal neighb pointed at by the normal, and + //it cannot possibly reach/touch/penetrate the slope + return Circle.COL_NONE; + } + else + { + //collide vs. vertex + //get diag vertex position + var vx = t.pos.x + (oH * t.xw); + var vy = t.pos.y + (oV * t.yw); + + var dx = obj.pos.x - vx;//calc vert->circle vector + var dy = obj.pos.y - vy; + + var len = Math.sqrt(dx * dx + dy * dy); + var pen = obj.radius - len; + if (0 < pen) + { + //vertex is in the circle; project outward + if (len == 0) + { + //project out by 45deg + dx = oH / Math.SQRT2; + dy = oV / Math.SQRT2; + } + else + { + dx /= len; + dy /= len; + } + + obj.ReportCollisionVsWorld(dx * pen, dy * pen, dx, dy, t); + + return Circle.COL_OTHER; + } + + } + + } + + return Circle.COL_NONE; + + } + + public ProjCircle_Convex(x, y, oH, oV, obj: Circle, t: TileMapCell) { + //if the object is horiz AND/OR vertical neighbor in the normal (signx,signy) + //direction, collide vs. tile-circle only. + //if we're colliding diagonally: + // -else, collide vs. the appropriate vertex + //if obj is in this tile: perform collision as for aabb + //if obj is horiz or vert neigh against direction of slope: collide vs. face + + var signx = t.signx; + var signy = t.signy; + var lenP; + + if (oH == 0) + { + if (oV == 0) + { + //colliding with current tile + + + var ox = obj.pos.x - (t.pos.x - (signx * t.xw));//(ox,oy) is the vector from the tile-circle to + var oy = obj.pos.y - (t.pos.y - (signy * t.yw));//the circle's center + + var twid = t.xw * 2; + var trad = Math.sqrt(twid * twid + 0);//this gives us the radius of a circle centered on the tile's corner and extending to the opposite edge of the tile; + //note that this should be precomputed at compile-time since it's constant + + var len = Math.sqrt(ox * ox + oy * oy); + var pen = (trad + obj.radius) - len; + + if (0 < pen) + { + //find the smallest axial projection vector + if (x < y) + { + //penetration in x is smaller + lenP = x; + y = 0; + + //get sign for projection along x-axis + if ((obj.pos.x - t.pos.x) < 0) + { + x *= -1; + } + } + else + { + //penetration in y is smaller + lenP = y; + x = 0; + + //get sign for projection along y-axis + if ((obj.pos.y - t.pos.y) < 0) + { + y *= -1; + } + } + + + if (lenP < pen) + { + obj.ReportCollisionVsWorld(x, y, x / lenP, y / lenP, t); + + return Circle.COL_AXIS; + } + else + { + //note: len should NEVER be == 0, because if it is, + //projeciton by an axis shoudl always be shorter, and we should + //never arrive here + ox /= len; + oy /= len; + + obj.ReportCollisionVsWorld(ox * pen, oy * pen, ox, oy, t); + + return Circle.COL_OTHER; + + } + } + } + else + { + //colliding vertically + if ((signy * oV) < 0) + { + //colliding with face/edge + obj.ReportCollisionVsWorld(0, y * oV, 0, oV, t); + + return Circle.COL_AXIS; + } + else + { + //obj in neighboring cell pointed at by tile normal; + //we could only be colliding vs the tile-circle surface + + var ox = obj.pos.x - (t.pos.x - (signx * t.xw));//(ox,oy) is the vector from the tile-circle to + var oy = obj.pos.y - (t.pos.y - (signy * t.yw));//the circle's center + + var twid = t.xw * 2; + var trad = Math.sqrt(twid * twid + 0);//this gives us the radius of a circle centered on the tile's corner and extending to the opposite edge of the tile; + //note that this should be precomputed at compile-time since it's constant + + var len = Math.sqrt(ox * ox + oy * oy); + var pen = (trad + obj.radius) - len; + + if (0 < pen) + { + + //note: len should NEVER be == 0, because if it is, + //obj is not in a neighboring cell! + ox /= len; + oy /= len; + + obj.ReportCollisionVsWorld(ox * pen, oy * pen, ox, oy, t); + + return Circle.COL_OTHER; + } + } + } + } + else if (oV == 0) + { + //colliding horizontally + if ((signx * oH) < 0) + { + //colliding with face/edge + obj.ReportCollisionVsWorld(x * oH, 0, oH, 0, t); + + return Circle.COL_AXIS; + } + else + { + //obj in neighboring cell pointed at by tile normal; + //we could only be colliding vs the tile-circle surface + + var ox = obj.pos.x - (t.pos.x - (signx * t.xw));//(ox,oy) is the vector from the tile-circle to + var oy = obj.pos.y - (t.pos.y - (signy * t.yw));//the circle's center + + var twid = t.xw * 2; + var trad = Math.sqrt(twid * twid + 0);//this gives us the radius of a circle centered on the tile's corner and extending to the opposite edge of the tile; + //note that this should be precomputed at compile-time since it's constant + + var len = Math.sqrt(ox * ox + oy * oy); + var pen = (trad + obj.radius) - len; + + if (0 < pen) + { + + //note: len should NEVER be == 0, because if it is, + //obj is not in a neighboring cell! + ox /= len; + oy /= len; + + obj.ReportCollisionVsWorld(ox * pen, oy * pen, ox, oy, t); + + return Circle.COL_OTHER; + } + } + } + else + { + //colliding diagonally + if (0 < ((signx * oH) + (signy * oV))) + { + //obj in diag neighb cell pointed at by tile normal; + //we could only be colliding vs the tile-circle surface + + var ox = obj.pos.x - (t.pos.x - (signx * t.xw));//(ox,oy) is the vector from the tile-circle to + var oy = obj.pos.y - (t.pos.y - (signy * t.yw));//the circle's center + + var twid = t.xw * 2; + var trad = Math.sqrt(twid * twid + 0);//this gives us the radius of a circle centered on the tile's corner and extending to the opposite edge of the tile; + //note that this should be precomputed at compile-time since it's constant + + var len = Math.sqrt(ox * ox + oy * oy); + var pen = (trad + obj.radius) - len; + + if (0 < pen) + { + + //note: len should NEVER be == 0, because if it is, + //obj is not in a neighboring cell! + ox /= len; + oy /= len; + + obj.ReportCollisionVsWorld(ox * pen, oy * pen, ox, oy, t); + + return Circle.COL_OTHER; + } + } + else + { + //collide vs. vertex + //get diag vertex position + var vx = t.pos.x + (oH * t.xw); + var vy = t.pos.y + (oV * t.yw); + + var dx = obj.pos.x - vx;//calc vert->circle vector + var dy = obj.pos.y - vy; + + var len = Math.sqrt(dx * dx + dy * dy); + var pen = obj.radius - len; + if (0 < pen) + { + //vertex is in the circle; project outward + if (len == 0) + { + //project out by 45deg + dx = oH / Math.SQRT2; + dy = oV / Math.SQRT2; + } + else + { + dx /= len; + dy /= len; + } + + obj.ReportCollisionVsWorld(dx * pen, dy * pen, dx, dy, t); + + return Circle.COL_OTHER; + } + + } + + } + + return Circle.COL_NONE; + + } + +} + + + +(function () { + + var myGame = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + + function init() { + + myGame.loader.addImageFile('atari1', 'assets/sprites/atari130xe.png'); + myGame.loader.load(); + + } + + physics: NPhysics; + c: Circle; + t: TileMapCell; + + function create() { + + this.physics = new NPhysics(); + this.c = new Circle(200, 100, 25); + // pos is center, not upper-left + this.t = new TileMapCell(200, 500, 100, 100); + //this.t.SetState(TileMapCell.TID_FULL); + //this.t.SetState(TileMapCell.TID_45DEGpn); + //this.t.SetState(TileMapCell.TID_CONCAVEpn); + this.t.SetState(TileMapCell.TID_CONVEXpn); + + } + + function update() { + + var fx = 0; + var fy = 0; + + if (myGame.input.keyboard.isDown(Phaser.Keyboard.LEFT)) + { + fx -= 0.2; + } + else if (myGame.input.keyboard.isDown(Phaser.Keyboard.RIGHT)) + { + fx += 0.2; + } + + if (myGame.input.keyboard.isDown(Phaser.Keyboard.UP)) + { + fy -= 0.2 + 0.2; + } + else if (myGame.input.keyboard.isDown(Phaser.Keyboard.DOWN)) + { + fy += 0.2; + } + + var p = this.c.pos; + var o = this.c.oldpos; + var vx = p.x - o.x; + var vy = p.y - o.y; + var newx = Math.min(20, Math.max(-20, vx+fx)); + var newy = Math.min(20, Math.max(-20, vy+fy)); + p.x = o.x + newx; + p.y = o.y + newy; + + this.c.IntegrateVerlet(); + this.c.CollideCircleVsTile(this.t); + this.c.CollideCircleVsWorldBounds(); + + } + + function render() { + + this.c.render(myGame.stage.context); + this.t.render(myGame.stage.context); + + } + +})(); diff --git a/Tests/scrollzones/blasteroids.js b/Tests/scrollzones/blasteroids.js index f484ec2d..24f88c11 100644 --- a/Tests/scrollzones/blasteroids.js +++ b/Tests/scrollzones/blasteroids.js @@ -23,6 +23,7 @@ emitter.setRotation(0, 0); // Looks like a smoke trail! //emitter.globalCompositeOperation = 'xor'; + // Looks way cool :) emitter.globalCompositeOperation = 'lighter'; bullets = myGame.createGroup(50); // Create our bullet pool diff --git a/Tests/tilemap/collide with tile.ts b/Tests/tilemap/collide with tile.ts index 396ed9e3..0653c725 100644 --- a/Tests/tilemap/collide with tile.ts +++ b/Tests/tilemap/collide with tile.ts @@ -16,7 +16,8 @@ myGame.loader.load(); } - + var CACTUS = 31; + var SIGN_POST = 46; var map: Phaser.Tilemap; var car: Phaser.Sprite; var tile: Phaser.Tile; @@ -28,10 +29,10 @@ // When the car collides with the cactus tile we'll flash the screen red briefly, // but it won't stop the car (the separateX/Y values are set to false) - map.setCollisionByIndex([31], Phaser.Collision.ANY, true, false, false); + map.setCollisionByIndex([CACTUS], Phaser.Collision.ANY, true, false, false); // When the car collides with the sign post tile we'll stop the car moving (separation is set to true) - map.setCollisionByIndex([46], Phaser.Collision.ANY, true, true, true); + map.setCollisionByIndex([SIGN_POST], Phaser.Collision.ANY, true, true, true); // This is the callback that will be called every time map.collide() returns true map.collisionCallback = collide; @@ -79,12 +80,12 @@ // collisionData is an array containing all of the tiles the object overlapped with (can be more than 1) for (var i = 0; i < collisionData.length; i++) { - if (collisionData[i].tile.index == 31) + if (collisionData[i].tile.index == CACTUS) { console.log('you hit a cactus!'); flash.start(0xff0000, 1); } - else if (collisionData[i].tile.index == 31) + else if (collisionData[i].tile.index == SIGN_POST) { console.log('you hit a sign post!'); } diff --git a/Tests/tilemap/collision.js b/Tests/tilemap/collision.js index 86b1c3d1..31d7de69 100644 --- a/Tests/tilemap/collision.js +++ b/Tests/tilemap/collision.js @@ -11,7 +11,7 @@ myGame.loader.load(); } var map; - var car; + var ufo; var tile; var emitter; var test; @@ -19,6 +19,7 @@ map = myGame.createTilemap('tiles', 'platform', Phaser.Tilemap.FORMAT_TILED_JSON); map.setCollisionRange(21, 53); map.setCollisionRange(105, 109); + myGame.camera.opaque = true; myGame.camera.backgroundColor = 'rgb(47,154,204)'; myGame.input.keyboard.addKeyCapture([ Phaser.Keyboard.LEFT, @@ -26,36 +27,37 @@ Phaser.Keyboard.UP, Phaser.Keyboard.DOWN ]); - emitter = myGame.createEmitter(32, 80); - emitter.width = 700; - emitter.makeParticles('chunk', 100, false, 1); - emitter.gravity = 200; - emitter.bounce = 0.8; - emitter.start(false, 10, 0.05); - car = myGame.createSprite(250, 64, 'ufo'); - car.renderRotation = false; + //emitter = myGame.createEmitter(32, 80); + //emitter.width = 700; + //emitter.makeParticles('chunk', 100, false, 1); + //emitter.gravity = 200; + //emitter.bounce = 0.8; + //emitter.start(false, 10, 0.05); + ufo = myGame.createSprite(250, 64, 'ufo'); + ufo.renderDebug = true; + ufo.renderRotation = false; test = myGame.createSprite(200, 64, 'ufo'); test.elasticity = 1; test.velocity.x = 50; test.velocity.y = 100; - car.setBounds(0, 0, map.widthInPixels - 32, map.heightInPixels - 32); + ufo.setBounds(0, 0, map.widthInPixels - 32, map.heightInPixels - 32); } function update() { // Collide everything with the map map.collide(); // And collide everything in the game :) myGame.collide(); - car.velocity.x = 0; - car.velocity.y = 0; + ufo.velocity.x = 0; + ufo.velocity.y = 0; if(myGame.input.keyboard.isDown(Phaser.Keyboard.LEFT)) { - car.velocity.x = -200; + ufo.velocity.x = -200; } else if(myGame.input.keyboard.isDown(Phaser.Keyboard.RIGHT)) { - car.velocity.x = 200; + ufo.velocity.x = 200; } if(myGame.input.keyboard.isDown(Phaser.Keyboard.UP)) { - car.velocity.y = -200; + ufo.velocity.y = -200; } else if(myGame.input.keyboard.isDown(Phaser.Keyboard.DOWN)) { - car.velocity.y = 200; + ufo.velocity.y = 200; } } })(); diff --git a/Tests/tilemap/collision.ts b/Tests/tilemap/collision.ts index a3d37616..48d3c91b 100644 --- a/Tests/tilemap/collision.ts +++ b/Tests/tilemap/collision.ts @@ -18,7 +18,7 @@ } var map: Phaser.Tilemap; - var car: Phaser.Sprite; + var ufo: Phaser.Sprite; var tile: Phaser.Tile; var emitter: Phaser.Emitter; var test: Phaser.Sprite; @@ -29,26 +29,28 @@ map.setCollisionRange(21,53); map.setCollisionRange(105,109); + myGame.camera.opaque = true; myGame.camera.backgroundColor = 'rgb(47,154,204)'; myGame.input.keyboard.addKeyCapture([Phaser.Keyboard.LEFT, Phaser.Keyboard.RIGHT, Phaser.Keyboard.UP, Phaser.Keyboard.DOWN]); - emitter = myGame.createEmitter(32, 80); - emitter.width = 700; - emitter.makeParticles('chunk', 100, false, 1); - emitter.gravity = 200; - emitter.bounce = 0.8; - emitter.start(false, 10, 0.05); + //emitter = myGame.createEmitter(32, 80); + //emitter.width = 700; + //emitter.makeParticles('chunk', 100, false, 1); + //emitter.gravity = 200; + //emitter.bounce = 0.8; + //emitter.start(false, 10, 0.05); - car = myGame.createSprite(250, 64, 'ufo'); - car.renderRotation = false; + ufo = myGame.createSprite(250, 64, 'ufo'); + ufo.renderDebug = true; + ufo.renderRotation = false; test = myGame.createSprite(200, 64, 'ufo'); test.elasticity = 1; test.velocity.x = 50; test.velocity.y = 100; - car.setBounds(0, 0, map.widthInPixels - 32, map.heightInPixels - 32); + ufo.setBounds(0, 0, map.widthInPixels - 32, map.heightInPixels - 32); } @@ -60,25 +62,25 @@ // And collide everything in the game :) myGame.collide(); - car.velocity.x = 0; - car.velocity.y = 0; + ufo.velocity.x = 0; + ufo.velocity.y = 0; if (myGame.input.keyboard.isDown(Phaser.Keyboard.LEFT)) { - car.velocity.x = -200; + ufo.velocity.x = -200; } else if (myGame.input.keyboard.isDown(Phaser.Keyboard.RIGHT)) { - car.velocity.x = 200; + ufo.velocity.x = 200; } if (myGame.input.keyboard.isDown(Phaser.Keyboard.UP)) { - car.velocity.y = -200; + ufo.velocity.y = -200; } else if (myGame.input.keyboard.isDown(Phaser.Keyboard.DOWN)) { - car.velocity.y = 200; + ufo.velocity.y = 200; } } diff --git a/build/phaser.d.ts b/build/phaser.d.ts index 6fcb1ad4..d139098b 100644 --- a/build/phaser.d.ts +++ b/build/phaser.d.ts @@ -460,6 +460,8 @@ module Phaser { * Destroy all objects and references belonging to this CollisionMask */ public destroy(): void; + public intersectsRaw(left: number, right: number, top: number, bottom: number): bool; + public intersectsVector(vector: Vector2): bool; /** * Gives a basic boolean response to a geometric collision. * If you need the details of the collision use the Collision functions instead and inspect the IntersectResult object. @@ -916,6 +918,7 @@ module Phaser { * Clean up memory. */ public destroy(): void; + public setPosition(x: number, y: number): void; public x : number; public y : number; public rotation : number; @@ -1235,9 +1238,10 @@ module Phaser { /** * Load graphic for this sprite. (graphic can be SpriteSheet or Texture) * @param key {string} Key of the graphic you want to load for this sprite. + * @param clearAnimations {boolean} If this Sprite has a set of animation data already loaded you can choose to keep or clear it with this boolean * @return {Sprite} Sprite instance itself. */ - public loadGraphic(key: string): Sprite; + public loadGraphic(key: string, clearAnimations?: bool): Sprite; /** * Load a DynamicTexture as its texture. * @param texture {DynamicTexture} The texture object to be used by this sprite. @@ -1696,6 +1700,10 @@ module Phaser { public frameTotal : number; public frame : number; public frameName : string; + /** + * Removes all related references + */ + public destroy(): void; } } /** @@ -1840,9 +1848,6 @@ module Phaser { * * Your game only has one CameraManager instance and it's responsible for looking after, creating and destroying * all of the cameras in the world. -* -* TODO: If the Camera is larger than the Stage size then the rotation offset isn't correct -* TODO: Texture Repeat doesn't scroll, because it's part of the camera not the world, need to think about this more */ module Phaser { class CameraManager { @@ -2573,6 +2578,33 @@ module Phaser { } } /** +* Phaser - Polygon +* +*/ +module Phaser { + class Polygon { + /** + * A *convex* clockwise polygon + * @class Polygon + * @constructor + * @param {Vector2} pos A vector representing the origin of the polygon (all other points are relative to this one) + * @param {Array.} points An Array of vectors representing the points in the polygon, in clockwise order. + **/ + constructor(pos?: Vector2, points?: Vector2[], parent?: any); + public parent: any; + public pos: Vector2; + public points: Vector2[]; + public edges: Vector2[]; + public normals: Vector2[]; + /** + * Recalculate the edges and normals of the polygon. This + * MUST be called if the points array is modified at all and + * the edges or normals are to be accessed. + */ + public recalc(): void; + } +} +/** * Phaser - Quad * * A Quad object is an area defined by its position, as indicated by its top-left corner (x,y) and width and height. @@ -2621,6 +2653,17 @@ module Phaser { **/ public intersects(quad, tolerance?: number): bool; /** + * Determines whether the object specified intersects (overlaps) with the given values. + * @method intersectsProps + * @param {Number} left + * @param {Number} right + * @param {Number} top + * @param {Number} bottomt + * @param {Number} tolerance A tolerance value to allow for an intersection test with padding, default to 0 + * @return {Boolean} A value of true if the specified object intersects with this Quad; otherwise false. + **/ + public intersectsRaw(left: number, right: number, top: number, bottom: number, tolerance?: number): bool; + /** * Determines whether the specified coordinates are contained within the region defined by this Quad object. * @method contains * @param {Number} x The x coordinate of the point to test. @@ -2643,6 +2686,12 @@ module Phaser { **/ public copyTo(target): any; /** + * Creates and returns a Polygon that is the same as this Quad. + * @method toPolygon + * @return {Polygon} A new Polygon that represents this quad. + **/ + public toPolygon(): Polygon; + /** * Returns a string representation of this object. * @method toString * @return {string} a string representation of the object. @@ -2669,6 +2718,7 @@ module Phaser { constructor(x?: number, y?: number, diameter?: number); private _diameter; private _radius; + private _pos; /** * The x coordinate of the center of the circle * @property x @@ -2682,6 +2732,12 @@ module Phaser { **/ public y: number; /** + * The position of this Circle object represented by a Vector2 + * @property pos + * @type Vector2 + **/ + public pos : Vector2; + /** * The diameter of the circle. The largest distance between any two points on the circle. The same as the radius * 2. * @method diameter * @return {Number} @@ -3100,6 +3156,274 @@ module Phaser { } } /** +* Phaser - Response +* +*/ +module Phaser { + class Response { + /** + * An object representing the result of an intersection. Contain information about: + * - The two objects participating in the intersection + * - The vector representing the minimum change necessary to extract the first object + * from the second one. + * - Whether the first object is entirely inside the second, or vice versa. + * + * @constructor + */ + constructor(); + /** + * The first object in the collision + */ + public a; + /** + * The second object in the collision + */ + public b; + /** + * The shortest colliding axis (unit-vector) + */ + public overlapN: Vector2; + /** + * The overlap vector (i.e. overlapN.scale(overlap, overlap)). + * If this vector is subtracted from the position of `a`, `a` and `b` will no longer be colliding. + */ + public overlapV: Vector2; + /** + * Whether the first object is completely inside the second. + */ + public aInB: bool; + /** + * Whether the second object is completely inside the first. + */ + public bInA: bool; + /** + * Magnitude of the overlap on the shortest colliding axis + */ + public overlap: number; + /** + * Set some values of the response back to their defaults. Call this between tests if + * you are going to reuse a single Response object for multiple intersection tests (recommented) + * + * @return {Response} This for chaining + */ + public clear(): Response; + } +} +/** +* Phaser - Vector2 +* +* A two dimensional vector. +* Contains methods and ideas from verlet-js by Sub Protocol, SAT.js by Jim Riecken and N by Metanet Software. +*/ +module Phaser { + class Vector2 { + /** + * Creates a new Vector2 object. + * @class Vector2 + * @constructor + * @param {Number} x The x position of the vector + * @param {Number} y The y position of the vector + * @return {Vector2} This object + **/ + constructor(x?: number, y?: number); + public x: number; + public y: number; + public setTo(x: number, y: number): Vector2; + /** + * Add this vector to the given one and return the result. + * + * @param {Vector2} v The other Vector. + * @param {Vector2} The output Vector. + * @return {Vector2} The new Vector + */ + public add(v: Vector2, output?: Vector2): Vector2; + /** + * Subtract this vector to the given one and return the result. + * + * @param {Vector2} v The other Vector. + * @param {Vector2} The output Vector. + * @return {Vector2} The new Vector + */ + public sub(v: Vector2, output?: Vector2): Vector2; + /** + * Multiply this vector with the given one and return the result. + * + * @param {Vector2} v The other Vector. + * @param {Vector2} The output Vector. + * @return {Vector2} The new Vector + */ + public mul(v: Vector2, output?: Vector2): Vector2; + /** + * Divide this vector by the given one and return the result. + * + * @param {Vector2} v The other Vector. + * @param {Vector2} The output Vector. + * @return {Vector2} The new Vector + */ + public div(v: Vector2, output?: Vector2): Vector2; + /** + * Scale this vector by the given values and return the result. + * + * @param {number} x The scaling factor in the x direction. + * @param {?number=} y The scaling factor in the y direction. If this + * is not specified, the x scaling factor will be used. + * @return {Vector} The new Vector + */ + public scale(x: number, y?: number, output?: Vector2): Vector2; + /** + * Rotate this vector by 90 degrees + * + * @return {Vector} This for chaining. + */ + public perp(output?: Vector2): Vector2; + public mutableSet(v: Vector2): Vector2; + /** + * Add another vector to this one. + * + * @param {Vector} other The other Vector. + * @return {Vector} This for chaining. + */ + public mutableAdd(v: Vector2): Vector2; + /** + * Subtract another vector from this one. + * + * @param {Vector} other The other Vector. + * @return {Vector} This for chaining. + */ + public mutableSub(v: Vector2): Vector2; + /** + * Multiply another vector with this one. + * + * @param {Vector} other The other Vector. + * @return {Vector} This for chaining. + */ + public mutableMul(v: Vector2): Vector2; + /** + * Divide this vector by another one. + * + * @param {Vector} other The other Vector. + * @return {Vector} This for chaining. + */ + public mutableDiv(v: Vector2): Vector2; + /** + * Scale this vector. + * + * @param {number} x The scaling factor in the x direction. + * @param {?number=} y The scaling factor in the y direction. If this + * is not specified, the x scaling factor will be used. + * @return {Vector} This for chaining. + */ + public mutableScale(x: number, y?: number): Vector2; + /** + * Reverse this vector. + * + * @return {Vector} This for chaining. + */ + public reverse(): Vector2; + public edge(v: Vector2, output?: Vector2): Vector2; + public equals(v: Vector2): bool; + public epsilonEquals(v: Vector2, epsilon: number): bool; + /** + * Get the length of this vector. + * + * @return {number} The length of this vector. + */ + public length(): number; + /** + * Get the length^2 of this vector. + * + * @return {number} The length^2 of this vector. + */ + public length2(): number; + /** + * Get the distance between this vector and the given vector. + * + * @return {Vector2} v The vector to check + */ + public distance(v: Vector2): number; + /** + * Get the distance^2 between this vector and the given vector. + * + * @return {Vector2} v The vector to check + */ + public distance2(v: Vector2): number; + /** + * Project this vector on to another vector. + * + * @param {Vector} other The vector to project onto. + * @return {Vector} This for chaining. + */ + public project(other: Vector2): Vector2; + /** + * Project this vector onto a vector of unit length. + * + * @param {Vector} other The unit vector to project onto. + * @return {Vector} This for chaining. + */ + public projectN(other: Vector2): Vector2; + /** + * Reflect this vector on an arbitrary axis. + * + * @param {Vector} axis The vector representing the axis. + * @return {Vector} This for chaining. + */ + public reflect(axis): Vector2; + /** + * Reflect this vector on an arbitrary axis (represented by a unit vector) + * + * @param {Vector} axis The unit vector representing the axis. + * @return {Vector} This for chaining. + */ + public reflectN(axis): Vector2; + public getProjectionMagnitude(v: Vector2): number; + public direction(output?: Vector2): Vector2; + public normalRightHand(output?: Vector2): Vector2; + /** + * Normalize (make unit length) this vector. + * + * @return {Vector} This for chaining. + */ + public normalize(output?: Vector2): Vector2; + public getMagnitude(): number; + /** + * Get the dot product of this vector against another. + * + * @param {Vector} other The vector to dot this one against. + * @return {number} The dot product. + */ + public dot(v: Vector2): number; + /** + * Get the cross product of this vector against another. + * + * @param {Vector} other The vector to cross this one against. + * @return {number} The cross product. + */ + public cross(v: Vector2): number; + /** + * Get the angle between this vector and the given vector. + * + * @return {Vector2} v The vector to check + */ + public angle(v: Vector2): number; + public angle2(vLeft: Vector2, vRight: Vector2): number; + /** + * Rotate this vector around the origin to the given angle (theta) and return the result in a new Vector + * + * @return {Vector2} v The vector to check + */ + public rotate(origin, theta, output?: Vector2): Vector2; + public clone(output?: Vector2): Vector2; + public copyFrom(v: Vector2): Vector2; + public copyTo(v: Vector2): Vector2; + /** + * Returns a string representation of this object. + * @method toString + * @return {string} a string representation of the object. + **/ + public toString(): string; + } +} +/** * Phaser - LinkedList * * A miniature linked list class. Useful for optimizing time-critical or highly repetitive tasks! @@ -3576,6 +3900,20 @@ module Phaser { static separateTileY(object: GameObject, x: number, y: number, width: number, height: number, mass: number, collideUp: bool, collideDown: bool, separate: bool): bool; /** * Separates the two objects on their x axis + * @param object The GameObject to separate + * @param tile The Tile to separate + * @returns {boolean} Whether the objects in fact touched and were separated along the X axis. + */ + static NEWseparateTileX(object: GameObject, x: number, y: number, width: number, height: number, mass: number, collideLeft: bool, collideRight: bool, separate: bool): bool; + /** + * Separates the two objects on their y axis + * @param object The first GameObject to separate + * @param tile The second GameObject to separate + * @returns {boolean} Whether the objects in fact touched and were separated along the Y axis. + */ + static NEWseparateTileY(object: GameObject, x: number, y: number, width: number, height: number, mass: number, collideUp: bool, collideDown: bool, separate: bool): bool; + /** + * Separates the two objects on their x axis * @param object1 The first GameObject to separate * @param object2 The second GameObject to separate * @returns {boolean} Whether the objects in fact touched and were separated along the X axis. @@ -3589,20 +3927,6 @@ module Phaser { */ static separateY(object1, object2): bool; /** - * Separates the two objects on their x axis - * @param object1 The first GameObject to separate - * @param object2 The second GameObject to separate - * @returns {boolean} Whether the objects in fact touched and were separated along the X axis. - */ - static OLDseparateX(object1, object2): bool; - /** - * Separates the two objects on their y axis - * @param object1 The first GameObject to separate - * @param object2 The second GameObject to separate - * @returns {boolean} Whether the objects in fact touched and were separated along the Y axis. - */ - static OLDseparateY(object1, object2): bool; - /** * Returns the distance between the two given coordinates. * @param x1 The X value of the first coordinate * @param y1 The Y value of the first coordinate @@ -3620,6 +3944,108 @@ module Phaser { * @returns {number} The distance between the two coordinates */ static distanceSquared(x1: number, y1: number, x2: number, y2: number): number; + /** + * Flattens the specified array of points onto a unit vector axis, + * resulting in a one dimensional range of the minimum and + * maximum value on that axis. + * + * @param {Array.} points The points to flatten. + * @param {Vector} normal The unit vector axis to flatten on. + * @param {Array.} result An array. After calling this function, + * result[0] will be the minimum value, + * result[1] will be the maximum value. + */ + static flattenPointsOn(points, normal, result): void; + /** + * Pool of Vectors used in calculations. + * + * @type {Array.} + */ + static T_VECTORS: Vector2[]; + /** + * Pool of Arrays used in calculations. + * + * @type {Array.>} + */ + static T_ARRAYS; + /** + * Check whether two convex clockwise polygons are separated by the specified + * axis (must be a unit vector). + * + * @param {Vector} aPos The position of the first polygon. + * @param {Vector} bPos The position of the second polygon. + * @param {Array.} aPoints The points in the first polygon. + * @param {Array.} bPoints The points in the second polygon. + * @param {Vector} axis The axis (unit sized) to test against. The points of both polygons + * will be projected onto this axis. + * @param {Response=} response A Response object (optional) which will be populated + * if the axis is not a separating axis. + * @return {boolean} true if it is a separating axis, false otherwise. If false, + * and a response is passed in, information about how much overlap and + * the direction of the overlap will be populated. + */ + static isSeparatingAxis(aPos, bPos, aPoints, bPoints, axis, response?: Response): bool; + static LEFT_VORNOI_REGION: number; + static MIDDLE_VORNOI_REGION: number; + static RIGHT_VORNOI_REGION: number; + /** + * Calculates which Vornoi region a point is on a line segment. + * It is assumed that both the line and the point are relative to (0, 0) + * + * | (0) | + * (-1) [0]--------------[1] (1) + * | (0) | + * + * @param {Vector} line The line segment. + * @param {Vector} point The point. + * @return {number} LEFT_VORNOI_REGION (-1) if it is the left region, + * MIDDLE_VORNOI_REGION (0) if it is the middle region, + * RIGHT_VORNOI_REGION (1) if it is the right region. + */ + static vornoiRegion(line: Vector2, point: Vector2): number; + /** + * Check if two circles intersect. + * + * @param {Circle} a The first circle. + * @param {Circle} b The second circle. + * @param {Response=} response Response object (optional) that will be populated if + * the circles intersect. + * @return {boolean} true if the circles intersect, false if they don't. + */ + static testCircleCircle(a: Circle, b: Circle, response?: Response): bool; + /** + * Check if a polygon and a circle intersect. + * + * @param {Polygon} polygon The polygon. + * @param {Circle} circle The circle. + * @param {Response=} response Response object (optional) that will be populated if + * they interset. + * @return {boolean} true if they intersect, false if they don't. + */ + static testPolygonCircle(polygon: Polygon, circle: Circle, response?: Response): bool; + /** + * Check if a circle and a polygon intersect. + * + * NOTE: This runs slightly slower than polygonCircle as it just + * runs polygonCircle and reverses everything at the end. + * + * @param {Circle} circle The circle. + * @param {Polygon} polygon The polygon. + * @param {Response=} response Response object (optional) that will be populated if + * they interset. + * @return {boolean} true if they intersect, false if they don't. + */ + static testCirclePolygon(circle: Circle, polygon: Polygon, response?: Response): bool; + /** + * Checks whether two convex, clockwise polygons intersect. + * + * @param {Polygon} a The first polygon. + * @param {Polygon} b The second polygon. + * @param {Response=} response Response object (optional) that will be populated if + * they interset. + * @return {boolean} true if they intersect, false if they don't. + */ + static testPolygonPolygon(a: Polygon, b: Polygon, response?: Response): bool; } } /** @@ -4269,14 +4695,173 @@ module Phaser { **/ static distanceBetween(x1: number, y1: number, x2: number, y2: number): number; /** - * Rotates a point around the x/y coordinates given to the desired angle + * Rotates the point around the x/y coordinates given to the desired angle and distance + * @param point {Object} Any object with exposed x and y properties * @param x {number} The x coordinate of the anchor point * @param y {number} The y coordinate of the anchor point - * @param angle {number} The angle of the rotation in radians - * @param point {Point} The point object to perform the rotation on + * @param {Number} angle The angle in radians (unless asDegrees is true) to return the point from. + * @param {Boolean} asDegrees Is the given angle in radians (false) or degrees (true)? + * @param {Number} distance An optional distance constraint between the point and the anchor * @return The modified point object */ - public rotatePoint(x: number, y: number, angle: number, point); + public rotatePoint(point, x1: number, y1: number, angle: number, asDegrees?: bool, distance?: number); + } +} +/** +* Phaser - GameObjectFactory +* +* A quick way to create new world objects and add existing objects to the current world. +*/ +module Phaser { + class GameObjectFactory { + /** + * GameObjectFactory constructor + * @param game {Game} A reference to the current Game. + */ + constructor(game: Game); + /** + * Local private reference to Game + */ + private _game; + /** + * Local private reference to World + */ + private _world; + /** + * Create a new camera with specific position and size. + * + * @param x {number} X position of the new camera. + * @param y {number} Y position of the new camera. + * @param width {number} Width of the new camera. + * @param height {number} Height of the new camera. + * @returns {Camera} The newly created camera object. + */ + public camera(x: number, y: number, width: number, height: number): Camera; + /** + * Create a new GeomSprite with specific position. + * + * @param x {number} X position of the new geom sprite. + * @param y {number} Y position of the new geom sprite. + * @returns {GeomSprite} The newly created geom sprite object. + */ + public geomSprite(x: number, y: number): GeomSprite; + /** + * Create a new Sprite with specific position and sprite sheet key. + * + * @param x {number} X position of the new sprite. + * @param y {number} Y position of the new sprite. + * @param key {string} Optional, key for the sprite sheet you want it to use. + * @returns {Sprite} The newly created sprite object. + */ + public sprite(x: number, y: number, key?: string): Sprite; + /** + * Create a new DynamicTexture with specific size. + * + * @param width {number} Width of the texture. + * @param height {number} Height of the texture. + * @returns {DynamicTexture} The newly created dynamic texture object. + */ + public dynamicTexture(width: number, height: number): DynamicTexture; + /** + * Create a new object container. + * + * @param maxSize {number} Optional, capacity of this group. + * @returns {Group} The newly created group. + */ + public group(maxSize?: number): Group; + /** + * Create a new Particle. + * + * @return {Particle} The newly created particle object. + */ + public particle(): Particle; + /** + * Create a new Emitter. + * + * @param x {number} Optional, x position of the emitter. + * @param y {number} Optional, y position of the emitter. + * @param size {number} Optional, size of this emitter. + * @return {Emitter} The newly created emitter object. + */ + public emitter(x?: number, y?: number, size?: number): Emitter; + /** + * Create a new ScrollZone object with image key, position and size. + * + * @param key {string} Key to a image you wish this object to use. + * @param x {number} X position of this object. + * @param y {number} Y position of this object. + * @param width number} Width of this object. + * @param height {number} Height of this object. + * @returns {ScrollZone} The newly created scroll zone object. + */ + public scrollZone(key: string, x?: number, y?: number, width?: number, height?: number): ScrollZone; + /** + * Create a new Tilemap. + * + * @param key {string} Key for tileset image. + * @param mapData {string} Data of this tilemap. + * @param format {number} Format of map data. (Tilemap.FORMAT_CSV or Tilemap.FORMAT_TILED_JSON) + * @param [resizeWorld] {boolean} resize the world to make same as tilemap? + * @param [tileWidth] {number} width of each tile. + * @param [tileHeight] {number} height of each tile. + * @return {Tilemap} The newly created tilemap object. + */ + public tilemap(key: string, mapData: string, format: number, resizeWorld?: bool, tileWidth?: number, tileHeight?: number): Tilemap; + /** + * Create a tween object for a specific object. + * + * @param obj Object you wish the tween will affect. + * @return {Phaser.Tween} The newly created tween object. + */ + public tween(obj): Tween; + /** + * Add an existing Sprite to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param sprite The Sprite to add to the Game World + * @return {Phaser.Sprite} The Sprite object + */ + public existingSprite(sprite: Sprite): Sprite; + /** + * Add an existing GeomSprite to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param sprite The GeomSprite to add to the Game World + * @return {Phaser.GeomSprite} The GeomSprite object + */ + public existingGeomSprite(sprite: GeomSprite): GeomSprite; + /** + * Add an existing Emitter to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param emitter The Emitter to add to the Game World + * @return {Phaser.Emitter} The Emitter object + */ + public existingEmitter(emitter: Emitter): Emitter; + /** + * Add an existing ScrollZone to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param scrollZone The ScrollZone to add to the Game World + * @return {Phaser.ScrollZone} The ScrollZone object + */ + public existingScrollZone(scrollZone: ScrollZone): ScrollZone; + /** + * Add an existing Tilemap to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param tilemap The Tilemap to add to the Game World + * @return {Phaser.Tilemap} The Tilemap object + */ + public existingTilemap(tilemap: Tilemap): Tilemap; + /** + * Add an existing Tween to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param tween The Tween to add to the Game World + * @return {Phaser.Tween} The Tween object + */ + public existingTween(tween: Tween): Tween; } } /** @@ -4336,6 +4921,27 @@ module Phaser { */ public alpha: number; /** + * An Array of Cameras to which this Group, or any of its children, won't render + * @type {Array} + */ + public cameraBlacklist: number[]; + /** + * If you do not wish this object to be visible to a specific camera, pass the camera here. + * + * @param camera {Camera} The specific camera. + */ + public hideFromCamera(camera: Camera): void; + /** + * Make this object only visible to a specific camera. + * + * @param camera {Camera} The camera you wish it to be visible. + */ + public showToCamera(camera: Camera): void; + /** + * This clears the camera black list, making the GameObject visible to all cameras. + */ + public clearCameraList(): void; + /** * Override this function to handle any deleting or "shutdown" type operations you might need, * such as removing traditional Flash children like Basic objects. */ @@ -4581,6 +5187,11 @@ module Phaser { */ public progress: number; /** + * The crossOrigin value applied to loaded images + * @type {string} + */ + public crossOrigin: string; + /** * Reset loader, this will remove all loaded assets. */ public reset(): void; @@ -4962,7 +5573,7 @@ module Phaser { /** * Phaser * -* v0.9.5 - April 28th 2013 +* v0.9.6 - May 21st 2013 * * A small and feature-packed 2D canvas game framework born from the firey pits of Flixel and Kiwi. * @@ -5307,6 +5918,8 @@ module Phaser { * This method is called when the canvas elements visibility is changed. */ private visibilityChange(event); + public pauseGame(): void; + public resumeGame(): void; /** * Get the DOM offset values of the given element */ @@ -5771,55 +6384,6 @@ module Phaser { } } /** -* Phaser - Vector2 -* -* A simple 2-dimensional vector class. Based on the one included with verlet-js by Sub Protocol released under MIT -*/ -module Phaser { - class Vector2 { - /** - * Creates a new Vector2 object. - * @class Vector2 - * @constructor - * @param {Number} x The x coordinate of vector2 - * @param {Number} y The y coordinate of vector2 - * @return {Vector2} This object - **/ - constructor(x?: number, y?: number); - public x: number; - public y: number; - public setTo(x: number, y: number): Vector2; - public add(v: Vector2, output?: Vector2): Vector2; - public sub(v: Vector2, output?: Vector2): Vector2; - public mul(v: Vector2, output?: Vector2): Vector2; - public div(v: Vector2, output?: Vector2): Vector2; - public scale(coef: number, output?: Vector2): Vector2; - public mutableSet(v: Vector2): Vector2; - public mutableAdd(v: Vector2): Vector2; - public mutableSub(v: Vector2): Vector2; - public mutableMul(v: Vector2): Vector2; - public mutableDiv(v: Vector2): Vector2; - public mutableScale(coef: number): Vector2; - public equals(v: Vector2): bool; - public epsilonEquals(v: Vector2, epsilon: number): bool; - public length(): number; - public length2(): number; - public dist(v: Vector2): number; - public dist2(v: Vector2): number; - public normal(output?: Vector2): Vector2; - public dot(v: Vector2): number; - public angle(v: Vector2): number; - public angle2(vLeft: Vector2, vRight: Vector2): number; - public rotate(origin, theta, output?: Vector2): Vector2; - /** - * Returns a string representation of this object. - * @method toString - * @return {string} a string representation of the object. - **/ - public toString(): string; - } -} -/** * Phaser - Verlet - Particle * * @@ -5879,6 +6443,26 @@ module Phaser.Verlet { **/ constructor(game: Game); private _game; + /** + * Texture of the particles to be rendered. + */ + private _texture; + /** + * Rendering bounds for the texture + * @type {Quad} + */ + private frameBounds; + private _sx; + private _sy; + private _sw; + private _sh; + private _dx; + private _dy; + private _dw; + private _dh; + private _hw; + private _hh; + public sprites: Sprite[]; public particles: Particle[]; public constraints; public drawParticles; @@ -5886,6 +6470,16 @@ module Phaser.Verlet { public createDistanceConstraint(a: Particle, b: Particle, stiffness: number, distance?: number): DistanceConstraint; public createAngleConstraint(a: Particle, b: Particle, c: Particle, stiffness: number): AngleConstraint; public createPinConstraint(a: Particle, pos: Vector2): PinConstraint; + /** + * Load a graphic for this Composite. The graphic cannot be a SpriteSheet yet. + * @param key {string} Key of the graphic you want to load for this sprite. + * @return {Composite} This object + */ + public loadGraphic(key: string): Composite; + public hideConstraints: bool; + public constraintLineColor: string; + private renderConstraints(context); + private render(context); public pin(index, pos?): PinConstraint; } } @@ -5955,6 +6549,7 @@ module Phaser.Verlet { **/ constructor(game: Game, width: number, height: number); private _game; + private _v; public composites: any[]; public width: number; public height: number; @@ -5966,28 +6561,26 @@ module Phaser.Verlet { public draggedEntity; public highlightColor: string; /** - * This class is actually a wrapper of canvas. + * A reference to the canvas this renders to * @type {HTMLCanvasElement} */ public canvas: HTMLCanvasElement; /** - * Canvas context of this object. + * A reference to the context this renders to * @type {CanvasRenderingContext2D} */ public context: CanvasRenderingContext2D; /** * Computes time of intersection of a particle with a wall * - * @param {Vec2} line wall's root position - * @param {Vec2} p particle's position - * @param {Vec2} dir walls's direction - * @param {Vec2} v particle's velocity + * @param {Vec2} line walls root position + * @param {Vec2} p particle position + * @param {Vec2} dir walls direction + * @param {Vec2} v particles velocity */ public intersectionTime(wall, p, dir, v): number; public intersectionPoint(wall, p, dir, v): Vector2; - private v; public bounds(particle: Particle): void; - public OLDbounds(particle: Particle): void; public createPoint(pos: Vector2): Composite; public createLineSegments(vertices, stiffness): Composite; public createCloth(origin, width, height, segments, pinMod, stiffness): Composite; @@ -5996,6 +6589,7 @@ module Phaser.Verlet { private mouseDownHandler(); private mouseUpHandler(); public nearestEntity(); + public hideNearestEntityCircle: bool; public render(): void; } } @@ -6055,13 +6649,14 @@ module Phaser { public destroy(): void; /** * Update size of this world with specific width and height. - * You can choose update camera bounds automatically or not. + * You can choose update camera bounds and verlet manager automatically or not. * * @param width {number} New width of the world. * @param height {number} New height of the world. * @param [updateCameraBounds] {boolean} update camera bounds automatically or not. Default to true. + * @param [updateVerletBounds] {boolean} update verlet bounds automatically or not. Default to true. */ - public setSize(width: number, height: number, updateCameraBounds?: bool): void; + public setSize(width: number, height: number, updateCameraBounds?: bool, updateVerletBounds?: bool): void; public width : number; public height : number; public centerX : number; @@ -6672,17 +7267,17 @@ module Phaser { */ public active: bool; /** - * A Point object representing the x/y screen coordinates of the Pointer. - * @property pointA - * @type {Point} + * A Vector object containing the initial position when the Pointer was engaged with the screen. + * @property positionDown + * @type {Vector2} **/ - public pointA: Point; + public positionDown: Vector2; /** - * A Point object representing the x/y screen coordinates of the Pointer. - * @property pointB - * @type {Point} + * A Vector object containing the current position of the Pointer on the screen. + * @property position + * @type {Vector2} **/ - public pointB: Point; + public position: Vector2; /** * A Circle object centered on the x/y screen coordinates of the Pointer. * Default size of 44px (Apple's recommended "finger tip" size) @@ -7026,11 +7621,11 @@ module Phaser { */ public gestures: Gestures; /** - * A Point object representing the x/y screen coordinates of the Pointer. - * @property point - * @type {Point} + * A vector object representing the current position of the Pointer. + * @property vector + * @type {Vector2} **/ - public point: Point; + public position: Vector2; /** * A Circle object centered on the x/y screen coordinates of the Input. * Default size of 44px (Apples recommended "finger tip" size) but can be changed to anything @@ -7221,7 +7816,22 @@ module Phaser { * @type {Number} **/ public y : number; + /** + * Add a new Pointer object to the Input Manager. By default Input creates 5 pointer objects for you. If you need more + * use this to create a new one, up to a maximum of 10. + * @method addPointer + * @return {Pointer} A reference to the new Pointer object + **/ + public addPointer(): Pointer; + /** + * Starts the Input Manager running + * @method start + **/ public start(): void; + /** + * Updates the Input Manager. Called by the core Game loop. + * @method update + **/ public update(): void; /** * Reset all of the Pointers and Input states @@ -7290,6 +7900,20 @@ module Phaser { * @param {String} [color] */ public renderDebugInfo(x: number, y: number, color?: string): void; + /** + * Get the distance between two Pointer objects + * @method getDistance + * @param {Pointer} pointer1 + * @param {Pointer} pointer2 + **/ + public getDistance(pointer1: Pointer, pointer2: Pointer): number; + /** + * Get the angle between two Pointer objects + * @method getAngle + * @param {Pointer} pointer1 + * @param {Pointer} pointer2 + **/ + public getAngle(pointer1: Pointer, pointer2: Pointer): number; } } /** @@ -7812,6 +8436,11 @@ module Phaser { */ static RECTANGLE: number; /** + * Polygon. + * @type {number} + */ + static POLYGON: number; + /** * Circle shape container. A Circle instance. * @type {Circle} */ @@ -7832,6 +8461,11 @@ module Phaser { */ public rect: Rectangle; /** + * Polygon shape container. A Polygon instance. + * @type {Polygon} + */ + public polygon: Polygon; + /** * Render outline of this sprite or not. (default is true) * @type {boolean} */ @@ -7906,6 +8540,13 @@ module Phaser { */ public createRectangle(width: number, height: number): GeomSprite; /** + * Create a polygon object + * @param width {Number} Width of the rectangle + * @param height {Number} Height of the rectangle + * @return {GeomSprite} GeomSprite instance. + */ + public createPolygon(points?: Vector2[]): GeomSprite; + /** * Destroy all geom shapes of this sprite. */ public refresh(): void; @@ -8809,6 +9450,11 @@ module Phaser { */ public onDestroyCallback; /** + * Reference to the GameObject Factory. + * @type {GameObjectFactory} + */ + public add: GameObjectFactory; + /** * Reference to the assets cache. * @type {Cache} */ @@ -8874,7 +9520,7 @@ module Phaser { */ public rnd: RandomDataGenerator; /** - * Device detector. + * Contains device information and capabilities. * @type {Device} */ public device: Device; @@ -8938,93 +9584,6 @@ module Phaser { public paused : bool; public framerate : number; /** - * Create a new camera with specific position and size. - * - * @param x {number} X position of the new camera. - * @param y {number} Y position of the new camera. - * @param width {number} Width of the new camera. - * @param height {number} Height of the new camera. - * @returns {Camera} The newly created camera object. - */ - public createCamera(x: number, y: number, width: number, height: number): Camera; - /** - * Create a new GeomSprite with specific position. - * - * @param x {number} X position of the new geom sprite. - * @param y {number} Y position of the new geom sprite. - * @returns {GeomSprite} The newly created geom sprite object. - */ - public createGeomSprite(x: number, y: number): GeomSprite; - /** - * Create a new Sprite with specific position and sprite sheet key. - * - * @param x {number} X position of the new sprite. - * @param y {number} Y position of the new sprite. - * @param key {string} Optional, key for the sprite sheet you want it to use. - * @returns {Sprite} The newly created sprite object. - */ - public createSprite(x: number, y: number, key?: string): Sprite; - /** - * Create a new DynamicTexture with specific size. - * - * @param width {number} Width of the texture. - * @param height {number} Height of the texture. - * @returns {DynamicTexture} The newly created dynamic texture object. - */ - public createDynamicTexture(width: number, height: number): DynamicTexture; - /** - * Create a new object container. - * - * @param maxSize {number} Optional, capacity of this group. - * @returns {Group} The newly created group. - */ - public createGroup(maxSize?: number): Group; - /** - * Create a new Particle. - * - * @return {Particle} The newly created particle object. - */ - public createParticle(): Particle; - /** - * Create a new Emitter. - * - * @param x {number} Optional, x position of the emitter. - * @param y {number} Optional, y position of the emitter. - * @param size {number} Optional, size of this emitter. - * @return {Emitter} The newly created emitter object. - */ - public createEmitter(x?: number, y?: number, size?: number): Emitter; - /** - * Create a new ScrollZone object with image key, position and size. - * - * @param key {string} Key to a image you wish this object to use. - * @param x {number} X position of this object. - * @param y {number} Y position of this object. - * @param width number} Width of this object. - * @param height {number} Height of this object. - * @returns {ScrollZone} The newly created scroll zone object. - */ - public createScrollZone(key: string, x?: number, y?: number, width?: number, height?: number): ScrollZone; - /** - * Create a new Tilemap. - * - * @param key {string} Key for tileset image. - * @param mapData {string} Data of this tilemap. - * @param format {number} Format of map data. (Tilemap.FORMAT_CSV or Tilemap.FORMAT_TILED_JSON) - * @param [resizeWorld] {boolean} resize the world to make same as tilemap? - * @param [tileWidth] {number} width of each tile. - * @param [tileHeight] {number} height of each tile. - * @return {Tilemap} The newly created tilemap object. - */ - public createTilemap(key: string, mapData: string, format: number, resizeWorld?: bool, tileWidth?: number, tileHeight?: number): Tilemap; - /** - * Create a tween object for a specific object. - * - * @param obj Object you wish the tween will affect. - * @return {Phaser.Tween} The newly created tween object. - */ - public createTween(obj): Tween; - /** * Checks for overlaps between two objects using the world QuadTree. Can be GameObject vs. GameObject, GameObject vs. Group or Group vs. Group. * Note: Does not take the objects scrollFactor into account. All overlaps are check in world space. * @param object1 The first GameObject or Group to check. If null the world.group is used. @@ -9148,6 +9707,11 @@ module Phaser { */ public collision: Collision; /** + * Reference to the GameObject Factory. + * @type {GameObjectFactory} + */ + public add: GameObjectFactory; + /** * Reference to the input manager * @type {Input} */ @@ -9219,93 +9783,6 @@ module Phaser { */ public destroy(): void; /** - * Create a new camera with specific position and size. - * - * @param x {number} X position of the new camera. - * @param y {number} Y position of the new camera. - * @param width {number} Width of the new camera. - * @param height {number} Height of the new camera. - * @returns {Camera} The newly created camera object. - */ - public createCamera(x: number, y: number, width: number, height: number): Camera; - /** - * Create a new GeomSprite with specific position. - * - * @param x {number} X position of the new geom sprite. - * @param y {number} Y position of the new geom sprite. - * @returns {GeomSprite} The newly created geom sprite object. - */ - public createGeomSprite(x: number, y: number): GeomSprite; - /** - * Create a new Sprite with specific position and sprite sheet key. - * - * @param x {number} X position of the new sprite. - * @param y {number} Y position of the new sprite. - * @param key {string} [optional] key for the sprite sheet you want it to use. - * @returns {Sprite} The newly created sprite object. - */ - public createSprite(x: number, y: number, key?: string): Sprite; - /** - * Create a new DynamicTexture with specific size. - * - * @param width {number} Width of the texture. - * @param height {number} Height of the texture. - * @returns {DynamicTexture} The newly created dynamic texture object. - */ - public createDynamicTexture(width: number, height: number): DynamicTexture; - /** - * Create a new object container. - * - * @param maxSize {number} [optional] capacity of this group. - * @returns {Group} The newly created group. - */ - public createGroup(maxSize?: number): Group; - /** - * Create a new Particle. - * - * @return {Particle} The newly created particle object. - */ - public createParticle(): Particle; - /** - * Create a new Emitter. - * - * @param x {number} [optional] x position of the emitter. - * @param y {number} [optional] y position of the emitter. - * @param size {number} [optional] size of this emitter. - * @return {Emitter} The newly created emitter object. - */ - public createEmitter(x?: number, y?: number, size?: number): Emitter; - /** - * Create a new ScrollZone object with image key, position and size. - * - * @param key {string} Key to a image you wish this object to use. - * @param x {number} X position of this object. - * @param y {number} Y position of this object. - * @param width {number} Width of this object. - * @param height {number} Height of this object. - * @returns {ScrollZone} The newly created scroll zone object. - */ - public createScrollZone(key: string, x?: number, y?: number, width?: number, height?: number): ScrollZone; - /** - * Create a new Tilemap. - * - * @param key {string} Key for tileset image. - * @param mapData {string} Data of this tilemap. - * @param format {number} Format of map data. (Tilemap.FORMAT_CSV or Tilemap.FORMAT_TILED_JSON) - * @param resizeWorld {boolean} [optional] resize the world to make same as tilemap? - * @param tileWidth {number} [optional] width of each tile. - * @param tileHeight number} [optional] height of each tile. - * @return {Tilemap} The newly created tilemap object. - */ - public createTilemap(key: string, mapData: string, format: number, resizeWorld?: bool, tileWidth?: number, tileHeight?: number): Tilemap; - /** - * Create a tween object for a specific object. - * - * @param obj Object you wish the tween will affect. - * @return {Phaser.Tween} The newly created tween object. - */ - public createTween(obj): Tween; - /** * Call this method to see if one object collids another. * @return {boolean} Whether the given objects or groups collids. */ diff --git a/build/phaser.js b/build/phaser.js index 1d5e6f54..e4136383 100644 --- a/build/phaser.js +++ b/build/phaser.js @@ -504,8 +504,6 @@ var Phaser; this.last.y = this.y; }; CollisionMask.prototype.update = function () { - //this.quad.x = this._parent.x + this.offset.x; - //this.quad.y = this._parent.y + this.offset.y; this._ref.x = this._parent.x + this.offset.x; this._ref.y = this._parent.y + this.offset.y; }; @@ -518,8 +516,7 @@ var Phaser; function (camera, cameraOffsetX, cameraOffsetY) { var _dx = cameraOffsetX + (this.x - camera.worldView.x); var _dy = cameraOffsetY + (this.y - camera.worldView.y); - //this._parent.context.fillStyle = this._parent.renderDebugColor; - this._parent.context.fillStyle = 'rgba(255,0,0,0.4)'; + this._parent.context.fillStyle = this._parent.renderDebugColor; if(this.type == CollisionMask.QUAD) { this._parent.context.fillRect(_dx, _dy, this.width, this.height); } else if(this.type == CollisionMask.CIRCLE) { @@ -543,6 +540,15 @@ var Phaser; this.line = null; this.offset = null; }; + CollisionMask.prototype.intersectsRaw = function (left, right, top, bottom) { + //if ((objBounds.x + objBounds.width > x) && (objBounds.x < x + width) && (objBounds.y + objBounds.height > y) && (objBounds.y < y + height)) + return true; + }; + CollisionMask.prototype.intersectsVector = function (vector) { + if(this.type == CollisionMask.QUAD) { + return this.quad.contains(vector.x, vector.y); + } + }; CollisionMask.prototype.intersects = /** * Gives a basic boolean response to a geometric collision. * If you need the details of the collision use the Collision functions instead and inspect the IntersectResult object. @@ -556,7 +562,6 @@ var Phaser; } // Circle vs. Circle if(this.type == CollisionMask.CIRCLE && source.type == CollisionMask.CIRCLE) { - console.log('c vs c'); return Phaser.Collision.circleToCircle(this.circle, source.circle).result; } // Circle vs. Rect @@ -889,7 +894,6 @@ var Phaser; this.alpha = 1; this.scale = new Phaser.MicroPoint(1, 1); this.last = new Phaser.MicroPoint(x, y); - //this.origin = new MicroPoint(this.frameBounds.halfWidth, this.frameBounds.halfHeight); this.align = GameObject.ALIGN_TOP_LEFT; this.mass = 1; this.elasticity = 0; @@ -1273,6 +1277,10 @@ var Phaser; */ function () { }; + GameObject.prototype.setPosition = function (x, y) { + this.x = x; + this.y = y; + }; Object.defineProperty(GameObject.prototype, "x", { get: function () { return this.frameBounds.x; @@ -1554,7 +1562,7 @@ var Phaser; this.fx.preUpdate(); if(this._target !== null) { if(this.deadzone == null) { - this.focusOnXY(this._target.x + this._target.origin.x, this._target.y + this._target.origin.y); + this.focusOnXY(this._target.x, this._target.y); } else { var edge; var targetX = this._target.x + ((this._target.x > 0) ? 0.0000001 : -0.0000001); @@ -1893,9 +1901,14 @@ var Phaser; Sprite.prototype.loadGraphic = /** * Load graphic for this sprite. (graphic can be SpriteSheet or Texture) * @param key {string} Key of the graphic you want to load for this sprite. + * @param clearAnimations {boolean} If this Sprite has a set of animation data already loaded you can choose to keep or clear it with this boolean * @return {Sprite} Sprite instance itself. */ - function (key) { + function (key, clearAnimations) { + if (typeof clearAnimations === "undefined") { clearAnimations = true; } + if(clearAnimations && this.animations.frameData !== null) { + this.animations.destroy(); + } if(this._game.cache.getImage(key) !== null) { if(this._game.cache.isSpriteSheet(key) == false) { this._texture = this._game.cache.getImage(key); @@ -1906,9 +1919,9 @@ var Phaser; } else { this._texture = this._game.cache.getImage(key); this.animations.loadFrameData(this._game.cache.getFrameData(key)); - //this.collisionMask.width = this._texture.width; - //this.collisionMask.height = this._texture.height; - } + this.collisionMask.width = this.animations.currentFrame.width; + this.collisionMask.height = this.animations.currentFrame.height; + } this._dynamicTexture = false; } return this; @@ -1947,14 +1960,20 @@ var Phaser; * @return {boolean} Return true if bounds of this sprite intersects the given rectangle, otherwise return false. */ function (camera) { - if(this.scrollFactor.x !== 1.0 || this.scrollFactor.y !== 1.0) { - this._dx = this.frameBounds.x - (camera.x * this.scrollFactor.x); - this._dy = this.frameBounds.y - (camera.y * this.scrollFactor.x); + // Object fixed in place regardless of the camera scrolling? Then it's always visible + if(this.scrollFactor.x == 0 && this.scrollFactor.y == 0) { + return true; + } + // Otherwise, if it's scrolling perfectly in sync with the camera (1 to 1) then it's a simple bounds check on world coordinates + if(this.scrollFactor.x == 1 && this.scrollFactor.y == 1) { + return camera.intersects(this.frameBounds, this.frameBounds.length); + } else { + // Else apply the offsets + this._dx = (this.frameBounds.x - camera.x) * this.scrollFactor.x; + this._dy = (this.frameBounds.y - camera.y) * this.scrollFactor.y; this._dw = this.frameBounds.width * this.scale.x; this._dh = this.frameBounds.height * this.scale.y; return (camera.right > this._dx) && (camera.x < this._dx + this._dw) && (camera.bottom > this._dy) && (camera.y < this._dy + this._dh); - } else { - return camera.intersects(this.frameBounds, this.frameBounds.length); } }; Sprite.prototype.postUpdate = /** @@ -2005,8 +2024,8 @@ var Phaser; this._sy = 0; this._sw = this.frameBounds.width; this._sh = this.frameBounds.height; - this._dx = cameraOffsetX + (this.frameBounds.topLeft.x - camera.worldView.x); - this._dy = cameraOffsetY + (this.frameBounds.topLeft.y - camera.worldView.y); + this._dx = (cameraOffsetX * this.scrollFactor.x) + this.frameBounds.topLeft.x - (camera.worldView.x * this.scrollFactor.x); + this._dy = (cameraOffsetY * this.scrollFactor.y) + this.frameBounds.topLeft.y - (camera.worldView.y * this.scrollFactor.y); this._dw = this.frameBounds.width * this.scale.x; this._dh = this.frameBounds.height * this.scale.y; if(this.align == Phaser.GameObject.ALIGN_TOP_CENTER) { @@ -2039,10 +2058,10 @@ var Phaser; } } // Apply camera difference - if(this.scrollFactor.x !== 1.0 || this.scrollFactor.y !== 1.0) { - this._dx -= (camera.worldView.x * this.scrollFactor.x); - this._dy -= (camera.worldView.y * this.scrollFactor.y); - } + if(this.scrollFactor.x !== 1 || this.scrollFactor.y !== 1) { + //this._dx -= (camera.worldView.x * this.scrollFactor.x); + //this._dy -= (camera.worldView.y * this.scrollFactor.y); + } // Rotation - needs to work from origin point really, but for now from center if(this.angle !== 0 || this.rotationOffset !== 0 || this.flipped == true) { this.context.save(); @@ -2711,7 +2730,11 @@ var Phaser; }); Object.defineProperty(AnimationManager.prototype, "frameTotal", { get: function () { - return this._frameData.total; + if(this._frameData) { + return this._frameData.total; + } else { + return -1; + } }, enumerable: true, configurable: true @@ -2746,6 +2769,17 @@ var Phaser; enumerable: true, configurable: true }); + AnimationManager.prototype.destroy = /** + * Removes all related references + */ + function () { + this._anims = { + }; + this._frameData = null; + this._frameIndex = 0; + this.currentAnim = null; + this.currentFrame = null; + }; return AnimationManager; })(); Phaser.AnimationManager = AnimationManager; @@ -2971,9 +3005,6 @@ var Phaser; * * Your game only has one CameraManager instance and it's responsible for looking after, creating and destroying * all of the cameras in the world. -* -* TODO: If the Camera is larger than the Stage size then the rotation offset isn't correct -* TODO: Texture Repeat doesn't scroll, because it's part of the camera not the world, need to think about this more */ var Phaser; (function (Phaser) { @@ -4086,6 +4117,54 @@ var Phaser; })(Phaser || (Phaser = {})); /// /** +* Phaser - Polygon +* +*/ +var Phaser; +(function (Phaser) { + var Polygon = (function () { + /** + * A *convex* clockwise polygon + * @class Polygon + * @constructor + * @param {Vector2} pos A vector representing the origin of the polygon (all other points are relative to this one) + * @param {Array.} points An Array of vectors representing the points in the polygon, in clockwise order. + **/ + function Polygon(pos, points, parent) { + if (typeof pos === "undefined") { pos = new Phaser.Vector2(); } + if (typeof points === "undefined") { points = []; } + if (typeof parent === "undefined") { parent = null; } + this.pos = pos; + this.points = points; + this.parent = parent; + this.recalc(); + } + Polygon.prototype.recalc = /** + * Recalculate the edges and normals of the polygon. This + * MUST be called if the points array is modified at all and + * the edges or normals are to be accessed. + */ + function () { + var points = this.points; + var len = points.length; + this.edges = []; + this.normals = []; + for(var i = 0; i < len; i++) { + var p1 = points[i]; + var p2 = i < len - 1 ? points[i + 1] : points[0]; + var e = new Phaser.Vector2().copyFrom(p2).sub(p1); + var n = new Phaser.Vector2().copyFrom(e).perp().normalize(); + this.edges.push(e); + this.normals.push(n); + } + }; + return Polygon; + })(); + Phaser.Polygon = Polygon; +})(Phaser || (Phaser = {})); +/// +/// +/** * Phaser - Quad * * A Quad object is an area defined by its position, as indicated by its top-left corner (x,y) and width and height. @@ -4181,6 +4260,20 @@ var Phaser; if (typeof tolerance === "undefined") { tolerance = 0; } return !(quad.left > this.right + tolerance || quad.right < this.left - tolerance || quad.top > this.bottom + tolerance || quad.bottom < this.top - tolerance); }; + Quad.prototype.intersectsRaw = /** + * Determines whether the object specified intersects (overlaps) with the given values. + * @method intersectsProps + * @param {Number} left + * @param {Number} right + * @param {Number} top + * @param {Number} bottomt + * @param {Number} tolerance A tolerance value to allow for an intersection test with padding, default to 0 + * @return {Boolean} A value of true if the specified object intersects with this Quad; otherwise false. + **/ + function (left, right, top, bottom, tolerance) { + if (typeof tolerance === "undefined") { tolerance = 0; } + return !(left > this.right + tolerance || right < this.left - tolerance || top > this.bottom + tolerance || bottom < this.top - tolerance); + }; Quad.prototype.contains = /** * Determines whether the specified coordinates are contained within the region defined by this Quad object. * @method contains @@ -4212,6 +4305,19 @@ var Phaser; function (target) { return target.copyFrom(this); }; + Quad.prototype.toPolygon = /** + * Creates and returns a Polygon that is the same as this Quad. + * @method toPolygon + * @return {Polygon} A new Polygon that represents this quad. + **/ + function () { + return new Phaser.Polygon(new Phaser.Vector2(this.x, this.y), [ + new Phaser.Vector2(), + new Phaser.Vector2(this.width, 0), + new Phaser.Vector2(this.width, this.height), + new Phaser.Vector2(0, this.height) + ]); + }; Quad.prototype.toString = /** * Returns a string representation of this object. * @method toString @@ -4260,8 +4366,21 @@ var Phaser; * @type Number **/ this.y = 0; + this._pos = new Phaser.Vector2(); this.setTo(x, y, diameter); } + Object.defineProperty(Circle.prototype, "pos", { + get: /** + * The position of this Circle object represented by a Vector2 + * @property pos + * @type Vector2 + **/ + function () { + return this._pos.setTo(this.x, this.y); + }, + enumerable: true, + configurable: true + }); Object.defineProperty(Circle.prototype, "diameter", { get: /** * The diameter of the circle. The largest distance between any two points on the circle. The same as the radius * 2. @@ -4952,6 +5071,414 @@ var Phaser; Phaser.IntersectResult = IntersectResult; })(Phaser || (Phaser = {})); /// +/// +/** +* Phaser - Response +* +*/ +var Phaser; +(function (Phaser) { + var Response = (function () { + /** + * An object representing the result of an intersection. Contain information about: + * - The two objects participating in the intersection + * - The vector representing the minimum change necessary to extract the first object + * from the second one. + * - Whether the first object is entirely inside the second, or vice versa. + * + * @constructor + */ + function Response() { + this.a = null; + this.b = null; + this.overlapN = new Phaser.Vector2(); + this.overlapV = new Phaser.Vector2(); + this.clear(); + } + Response.prototype.clear = /** + * Set some values of the response back to their defaults. Call this between tests if + * you are going to reuse a single Response object for multiple intersection tests (recommented) + * + * @return {Response} This for chaining + */ + function () { + this.aInB = true; + this.bInA = true; + this.overlap = Number.MAX_VALUE; + return this; + }; + return Response; + })(); + Phaser.Response = Response; +})(Phaser || (Phaser = {})); +/// +/** +* Phaser - Vector2 +* +* A two dimensional vector. +* Contains methods and ideas from verlet-js by Sub Protocol, SAT.js by Jim Riecken and N by Metanet Software. +*/ +var Phaser; +(function (Phaser) { + var Vector2 = (function () { + /** + * Creates a new Vector2 object. + * @class Vector2 + * @constructor + * @param {Number} x The x position of the vector + * @param {Number} y The y position of the vector + * @return {Vector2} This object + **/ + function Vector2(x, y) { + if (typeof x === "undefined") { x = 0; } + if (typeof y === "undefined") { y = 0; } + this.x = x; + this.y = y; + } + Vector2.prototype.setTo = function (x, y) { + this.x = x; + this.y = y; + return this; + }; + Vector2.prototype.add = /** + * Add this vector to the given one and return the result. + * + * @param {Vector2} v The other Vector. + * @param {Vector2} The output Vector. + * @return {Vector2} The new Vector + */ + function (v, output) { + if (typeof output === "undefined") { output = new Vector2(); } + return output.setTo(this.x + v.x, this.y + v.y); + }; + Vector2.prototype.sub = /** + * Subtract this vector to the given one and return the result. + * + * @param {Vector2} v The other Vector. + * @param {Vector2} The output Vector. + * @return {Vector2} The new Vector + */ + function (v, output) { + if (typeof output === "undefined") { output = new Vector2(); } + return output.setTo(this.x - v.x, this.y - v.y); + }; + Vector2.prototype.mul = /** + * Multiply this vector with the given one and return the result. + * + * @param {Vector2} v The other Vector. + * @param {Vector2} The output Vector. + * @return {Vector2} The new Vector + */ + function (v, output) { + if (typeof output === "undefined") { output = new Vector2(); } + return output.setTo(this.x * v.x, this.y * v.y); + }; + Vector2.prototype.div = /** + * Divide this vector by the given one and return the result. + * + * @param {Vector2} v The other Vector. + * @param {Vector2} The output Vector. + * @return {Vector2} The new Vector + */ + function (v, output) { + if (typeof output === "undefined") { output = new Vector2(); } + return output.setTo(this.x / v.x, this.y / v.y); + }; + Vector2.prototype.scale = /** + * Scale this vector by the given values and return the result. + * + * @param {number} x The scaling factor in the x direction. + * @param {?number=} y The scaling factor in the y direction. If this + * is not specified, the x scaling factor will be used. + * @return {Vector} The new Vector + */ + function (x, y, output) { + if (typeof y === "undefined") { y = null; } + if (typeof output === "undefined") { output = new Vector2(); } + if(y === null) { + y = x; + } + return output.setTo(this.x * x, this.y * y); + }; + Vector2.prototype.perp = /** + * Rotate this vector by 90 degrees + * + * @return {Vector} This for chaining. + */ + function (output) { + if (typeof output === "undefined") { output = this; } + var x = this.x; + return output.setTo(this.y, -x); + }; + Vector2.prototype.mutableSet = // Same as copyFrom, used by VerletManager + function (v) { + this.x = v.x; + this.y = v.y; + return this; + }; + Vector2.prototype.mutableAdd = /** + * Add another vector to this one. + * + * @param {Vector} other The other Vector. + * @return {Vector} This for chaining. + */ + function (v) { + this.x += v.x; + this.y += v.y; + return this; + }; + Vector2.prototype.mutableSub = /** + * Subtract another vector from this one. + * + * @param {Vector} other The other Vector. + * @return {Vector} This for chaining. + */ + function (v) { + this.x -= v.x; + this.y -= v.y; + return this; + }; + Vector2.prototype.mutableMul = /** + * Multiply another vector with this one. + * + * @param {Vector} other The other Vector. + * @return {Vector} This for chaining. + */ + function (v) { + this.x *= v.x; + this.y *= v.y; + return this; + }; + Vector2.prototype.mutableDiv = /** + * Divide this vector by another one. + * + * @param {Vector} other The other Vector. + * @return {Vector} This for chaining. + */ + function (v) { + this.x /= v.x; + this.y /= v.y; + return this; + }; + Vector2.prototype.mutableScale = /** + * Scale this vector. + * + * @param {number} x The scaling factor in the x direction. + * @param {?number=} y The scaling factor in the y direction. If this + * is not specified, the x scaling factor will be used. + * @return {Vector} This for chaining. + */ + function (x, y) { + this.x *= x; + this.y *= y || x; + return this; + }; + Vector2.prototype.reverse = /** + * Reverse this vector. + * + * @return {Vector} This for chaining. + */ + function () { + this.x = -this.x; + this.y = -this.y; + return this; + }; + Vector2.prototype.edge = function (v, output) { + if (typeof output === "undefined") { output = new Vector2(); } + return this.sub(v, output); + }; + Vector2.prototype.equals = function (v) { + return this.x == v.x && this.y == v.y; + }; + Vector2.prototype.epsilonEquals = function (v, epsilon) { + return Math.abs(this.x - v.x) <= epsilon && Math.abs(this.y - v.y) <= epsilon; + }; + Vector2.prototype.length = /** + * Get the length of this vector. + * + * @return {number} The length of this vector. + */ + function () { + return Math.sqrt((this.x * this.x) + (this.y * this.y)); + }; + Vector2.prototype.length2 = /** + * Get the length^2 of this vector. + * + * @return {number} The length^2 of this vector. + */ + function () { + return (this.x * this.x) + (this.y * this.y); + }; + Vector2.prototype.distance = /** + * Get the distance between this vector and the given vector. + * + * @return {Vector2} v The vector to check + */ + function (v) { + return Math.sqrt(this.distance2(v)); + }; + Vector2.prototype.distance2 = /** + * Get the distance^2 between this vector and the given vector. + * + * @return {Vector2} v The vector to check + */ + function (v) { + return ((v.x - this.x) * (v.x - this.x)) + ((v.y - this.y) * (v.y - this.y)); + }; + Vector2.prototype.project = /** + * Project this vector on to another vector. + * + * @param {Vector} other The vector to project onto. + * @return {Vector} This for chaining. + */ + function (other) { + var amt = this.dot(other) / other.length2(); + if(amt != 0) { + this.x = amt * other.x; + this.y = amt * other.y; + } + return this; + }; + Vector2.prototype.projectN = /** + * Project this vector onto a vector of unit length. + * + * @param {Vector} other The unit vector to project onto. + * @return {Vector} This for chaining. + */ + function (other) { + var amt = this.dot(other); + if(amt != 0) { + this.x = amt * other.x; + this.y = amt * other.y; + } + return this; + }; + Vector2.prototype.reflect = /** + * Reflect this vector on an arbitrary axis. + * + * @param {Vector} axis The vector representing the axis. + * @return {Vector} This for chaining. + */ + function (axis) { + var x = this.x; + var y = this.y; + this.project(axis).scale(2); + this.x -= x; + this.y -= y; + return this; + }; + Vector2.prototype.reflectN = /** + * Reflect this vector on an arbitrary axis (represented by a unit vector) + * + * @param {Vector} axis The unit vector representing the axis. + * @return {Vector} This for chaining. + */ + function (axis) { + var x = this.x; + var y = this.y; + this.projectN(axis).scale(2); + this.x -= x; + this.y -= y; + return this; + }; + Vector2.prototype.getProjectionMagnitude = function (v) { + var den = v.dot(v); + if(den == 0) { + return 0; + } else { + return Math.abs(this.dot(v) / den); + } + }; + Vector2.prototype.direction = function (output) { + if (typeof output === "undefined") { output = new Vector2(); } + output.copyFrom(this); + return this.normalize(output); + }; + Vector2.prototype.normalRightHand = function (output) { + if (typeof output === "undefined") { output = this; } + return output.setTo(this.y * -1, this.x); + }; + Vector2.prototype.normalize = /** + * Normalize (make unit length) this vector. + * + * @return {Vector} This for chaining. + */ + function (output) { + if (typeof output === "undefined") { output = this; } + var m = this.length(); + if(m != 0) { + output.setTo(this.x / m, this.y / m); + } + return output; + }; + Vector2.prototype.getMagnitude = function () { + return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2)); + }; + Vector2.prototype.dot = /** + * Get the dot product of this vector against another. + * + * @param {Vector} other The vector to dot this one against. + * @return {number} The dot product. + */ + function (v) { + return ((this.x * v.x) + (this.y * v.y)); + }; + Vector2.prototype.cross = /** + * Get the cross product of this vector against another. + * + * @param {Vector} other The vector to cross this one against. + * @return {number} The cross product. + */ + function (v) { + return ((this.x * v.y) - (this.y * v.x)); + }; + Vector2.prototype.angle = /** + * Get the angle between this vector and the given vector. + * + * @return {Vector2} v The vector to check + */ + function (v) { + return Math.atan2(this.x * v.y - this.y * v.x, this.x * v.x + this.y * v.y); + }; + Vector2.prototype.angle2 = function (vLeft, vRight) { + return vLeft.sub(this).angle(vRight.sub(this)); + }; + Vector2.prototype.rotate = /** + * Rotate this vector around the origin to the given angle (theta) and return the result in a new Vector + * + * @return {Vector2} v The vector to check + */ + function (origin, theta, output) { + if (typeof output === "undefined") { output = new Vector2(); } + var x = this.x - origin.x; + var y = this.y - origin.y; + return output.setTo(x * Math.cos(theta) - y * Math.sin(theta) + origin.x, x * Math.sin(theta) + y * Math.cos(theta) + origin.y); + }; + Vector2.prototype.clone = function (output) { + if (typeof output === "undefined") { output = new Vector2(); } + return output.setTo(this.x, this.y); + }; + Vector2.prototype.copyFrom = function (v) { + this.x = v.x; + this.y = v.y; + return this; + }; + Vector2.prototype.copyTo = function (v) { + return v.setTo(this.x, this.y); + }; + Vector2.prototype.toString = /** + * Returns a string representation of this object. + * @method toString + * @return {string} a string representation of the object. + **/ + function () { + return "[{Vector2 (x=" + this.x + " y=" + this.y + ")}]"; + }; + return Vector2; + })(); + Phaser.Vector2 = Vector2; +})(Phaser || (Phaser = {})); +/// /** * Phaser - LinkedList * @@ -5338,6 +5865,8 @@ var Phaser; /// /// /// +/// +/// /// /** * Phaser - Collision @@ -5353,6 +5882,14 @@ var Phaser; */ function Collision(game) { this._game = game; + Collision.T_VECTORS = []; + for(var i = 0; i < 10; i++) { + Collision.T_VECTORS.push(new Phaser.Vector2()); + } + Collision.T_ARRAYS = []; + for(var i = 0; i < 5; i++) { + Collision.T_ARRAYS.push([]); + } } Collision.LEFT = 0x0001; Collision.RIGHT = 0x0010; @@ -5703,20 +6240,7 @@ var Phaser; output.result = ((circle1.radius + circle2.radius) * (circle1.radius + circle2.radius)) >= Collision.distanceSquared(circle1.x, circle1.y, circle2.x, circle2.y); return output; }; - Collision.circleToRectangle = /* - public static circleToQuad(circle: Circle, quad: Quad): bool { - - // Check if the center of the circle is within the Quad - if (quad.contains(circle.x, circle.y)) - { - return true; - } - - // Failing that let's check each line of the quad against the circle - return false; - } - */ - /** + Collision.circleToRectangle = /** * Checks if the Circle object intersects with the Rectangle and returns the result in an IntersectResult object. * @param circle The Circle object to check * @param rect The Rectangle object to check @@ -5779,7 +6303,6 @@ var Phaser; * @returns {boolean} Returns true if the objects were separated, otherwise false. */ function separate(object1, object2) { - console.log('sep o'); object1.collisionMask.update(); object2.collisionMask.update(); var separatedX = Collision.separateX(object1, object2); @@ -5793,6 +6316,7 @@ var Phaser; * @returns {boolean} Whether the objects in fact touched and were separated */ function separateTile(object, x, y, width, height, mass, collideLeft, collideRight, collideUp, collideDown, separateX, separateY) { + object.collisionMask.update(); var separatedX = Collision.separateTileX(object, x, y, width, height, mass, collideLeft, collideRight, separateX); var separatedY = Collision.separateTileY(object, x, y, width, height, mass, collideUp, collideDown, separateY); return separatedX || separatedY; @@ -5811,9 +6335,11 @@ var Phaser; // First, get the object delta var overlap = 0; var objDelta = object.x - object.last.x; + //var objDelta: number = object.collisionMask.deltaX; if(objDelta != 0) { // Check if the X hulls actually overlap var objDeltaAbs = (objDelta > 0) ? objDelta : -objDelta; + //var objDeltaAbs: number = object.collisionMask.deltaXAbs; var objBounds = new Phaser.Quad(object.x - ((objDelta > 0) ? objDelta : 0), object.last.y, object.width + ((objDelta > 0) ? objDelta : -objDelta), object.height); if((objBounds.x + objBounds.width > x) && (objBounds.x < x + width) && (objBounds.y + objBounds.height > y) && (objBounds.y < y + height)) { var maxOverlap = objDeltaAbs + Collision.OVERLAP_BIAS; @@ -5838,6 +6364,7 @@ var Phaser; // Then adjust their positions and velocities accordingly (if there was any overlap) if(overlap != 0) { if(separate == true) { + //console.log(' object.x = object.x - overlap; object.velocity.x = -(object.velocity.x * object.elasticity); } @@ -5898,6 +6425,113 @@ var Phaser; return false; } }; + Collision.NEWseparateTileX = /** + * Separates the two objects on their x axis + * @param object The GameObject to separate + * @param tile The Tile to separate + * @returns {boolean} Whether the objects in fact touched and were separated along the X axis. + */ + function NEWseparateTileX(object, x, y, width, height, mass, collideLeft, collideRight, separate) { + // Can't separate two immovable objects (tiles are always immovable) + if(object.immovable) { + return false; + } + // First, get the object delta + var overlap = 0; + if(object.collisionMask.deltaX != 0) { + // Check if the X hulls actually overlap + //var objDeltaAbs: number = (objDelta > 0) ? objDelta : -objDelta; + //var objBounds: Quad = new Quad(object.x - ((objDelta > 0) ? objDelta : 0), object.last.y, object.width + ((objDelta > 0) ? objDelta : -objDelta), object.height); + //if ((objBounds.x + objBounds.width > x) && (objBounds.x < x + width) && (objBounds.y + objBounds.height > y) && (objBounds.y < y + height)) + if(object.collisionMask.intersectsRaw(x, x + width, y, y + height)) { + var maxOverlap = object.collisionMask.deltaXAbs + Collision.OVERLAP_BIAS; + // If they did overlap (and can), figure out by how much and flip the corresponding flags + if(object.collisionMask.deltaX > 0) { + //overlap = object.x + object.width - x; + overlap = object.collisionMask.right - x; + if((overlap > maxOverlap) || !(object.allowCollisions & Collision.RIGHT) || collideLeft == false) { + overlap = 0; + } else { + object.touching |= Collision.RIGHT; + } + } else if(object.collisionMask.deltaX < 0) { + //overlap = object.x - width - x; + overlap = object.collisionMask.x - width - x; + if((-overlap > maxOverlap) || !(object.allowCollisions & Collision.LEFT) || collideRight == false) { + overlap = 0; + } else { + object.touching |= Collision.LEFT; + } + } + } + } + // Then adjust their positions and velocities accordingly (if there was any overlap) + if(overlap != 0) { + if(separate == true) { + object.x = object.x - overlap; + object.velocity.x = -(object.velocity.x * object.elasticity); + } + Collision.TILE_OVERLAP = true; + return true; + } else { + return false; + } + }; + Collision.NEWseparateTileY = /** + * Separates the two objects on their y axis + * @param object The first GameObject to separate + * @param tile The second GameObject to separate + * @returns {boolean} Whether the objects in fact touched and were separated along the Y axis. + */ + function NEWseparateTileY(object, x, y, width, height, mass, collideUp, collideDown, separate) { + // Can't separate two immovable objects (tiles are always immovable) + if(object.immovable) { + return false; + } + // First, get the two object deltas + var overlap = 0; + //var objDelta: number = object.y - object.last.y; + if(object.collisionMask.deltaY != 0) { + // Check if the Y hulls actually overlap + //var objDeltaAbs: number = (objDelta > 0) ? objDelta : -objDelta; + //var objBounds: Quad = new Quad(object.x, object.y - ((objDelta > 0) ? objDelta : 0), object.width, object.height + objDeltaAbs); + //if ((objBounds.x + objBounds.width > x) && (objBounds.x < x + width) && (objBounds.y + objBounds.height > y) && (objBounds.y < y + height)) + if(object.collisionMask.intersectsRaw(x, x + width, y, y + height)) { + //var maxOverlap: number = objDeltaAbs + Collision.OVERLAP_BIAS; + var maxOverlap = object.collisionMask.deltaYAbs + Collision.OVERLAP_BIAS; + // If they did overlap (and can), figure out by how much and flip the corresponding flags + if(object.collisionMask.deltaY > 0) { + //overlap = object.y + object.height - y; + overlap = object.collisionMask.bottom - y; + if((overlap > maxOverlap) || !(object.allowCollisions & Collision.DOWN) || collideUp == false) { + overlap = 0; + } else { + object.touching |= Collision.DOWN; + } + } else if(object.collisionMask.deltaY < 0) { + //overlap = object.y - height - y; + overlap = object.collisionMask.y - height - y; + if((-overlap > maxOverlap) || !(object.allowCollisions & Collision.UP) || collideDown == false) { + overlap = 0; + } else { + object.touching |= Collision.UP; + } + } + } + } + // TODO - with super low velocities you get lots of stuttering, set some kind of base minimum here + // Then adjust their positions and velocities accordingly (if there was any overlap) + if(overlap != 0) { + if(separate == true) { + object.y = object.y - overlap; + object.velocity.y = -(object.velocity.y * object.elasticity); + } + Collision.TILE_OVERLAP = true; + return true; + } else { + return false; + } + }; Collision.separateX = /** * Separates the two objects on their x axis * @param object1 The first GameObject to separate @@ -6033,159 +6667,6 @@ var Phaser; return false; } }; - Collision.OLDseparateX = /** - * Separates the two objects on their x axis - * @param object1 The first GameObject to separate - * @param object2 The second GameObject to separate - * @returns {boolean} Whether the objects in fact touched and were separated along the X axis. - */ - function OLDseparateX(object1, object2) { - // Can't separate two immovable objects - if(object1.immovable && object2.immovable) { - return false; - } - // First, get the two object deltas - var overlap = 0; - var obj1Delta = object1.x - object1.last.x; - var obj2Delta = object2.x - object2.last.x; - if(obj1Delta != obj2Delta) { - // Check if the X hulls actually overlap - var obj1DeltaAbs = (obj1Delta > 0) ? obj1Delta : -obj1Delta; - var obj2DeltaAbs = (obj2Delta > 0) ? obj2Delta : -obj2Delta; - var obj1Bounds = new Phaser.Quad(object1.x - ((obj1Delta > 0) ? obj1Delta : 0), object1.last.y, object1.width + ((obj1Delta > 0) ? obj1Delta : -obj1Delta), object1.height); - var obj2Bounds = new Phaser.Quad(object2.x - ((obj2Delta > 0) ? obj2Delta : 0), object2.last.y, object2.width + ((obj2Delta > 0) ? obj2Delta : -obj2Delta), object2.height); - if((obj1Bounds.x + obj1Bounds.width > obj2Bounds.x) && (obj1Bounds.x < obj2Bounds.x + obj2Bounds.width) && (obj1Bounds.y + obj1Bounds.height > obj2Bounds.y) && (obj1Bounds.y < obj2Bounds.y + obj2Bounds.height)) { - var maxOverlap = obj1DeltaAbs + obj2DeltaAbs + Collision.OVERLAP_BIAS; - // If they did overlap (and can), figure out by how much and flip the corresponding flags - if(obj1Delta > obj2Delta) { - overlap = object1.x + object1.width - object2.x; - if((overlap > maxOverlap) || !(object1.allowCollisions & Collision.RIGHT) || !(object2.allowCollisions & Collision.LEFT)) { - overlap = 0; - } else { - object1.touching |= Collision.RIGHT; - object2.touching |= Collision.LEFT; - } - } else if(obj1Delta < obj2Delta) { - overlap = object1.x - object2.width - object2.x; - if((-overlap > maxOverlap) || !(object1.allowCollisions & Collision.LEFT) || !(object2.allowCollisions & Collision.RIGHT)) { - overlap = 0; - } else { - object1.touching |= Collision.LEFT; - object2.touching |= Collision.RIGHT; - } - } - } - } - // Then adjust their positions and velocities accordingly (if there was any overlap) - if(overlap != 0) { - var obj1Velocity = object1.velocity.x; - var obj2Velocity = object2.velocity.x; - if(!object1.immovable && !object2.immovable) { - overlap *= 0.5; - object1.x = object1.x - overlap; - object2.x += overlap; - var obj1NewVelocity = Math.sqrt((obj2Velocity * obj2Velocity * object2.mass) / object1.mass) * ((obj2Velocity > 0) ? 1 : -1); - var obj2NewVelocity = Math.sqrt((obj1Velocity * obj1Velocity * object1.mass) / object2.mass) * ((obj1Velocity > 0) ? 1 : -1); - var average = (obj1NewVelocity + obj2NewVelocity) * 0.5; - obj1NewVelocity -= average; - obj2NewVelocity -= average; - object1.velocity.x = average + obj1NewVelocity * object1.elasticity; - object2.velocity.x = average + obj2NewVelocity * object2.elasticity; - } else if(!object1.immovable) { - object1.x = object1.x - overlap; - object1.velocity.x = obj2Velocity - obj1Velocity * object1.elasticity; - } else if(!object2.immovable) { - object2.x += overlap; - object2.velocity.x = obj1Velocity - obj2Velocity * object2.elasticity; - } - return true; - } else { - return false; - } - }; - Collision.OLDseparateY = /** - * Separates the two objects on their y axis - * @param object1 The first GameObject to separate - * @param object2 The second GameObject to separate - * @returns {boolean} Whether the objects in fact touched and were separated along the Y axis. - */ - function OLDseparateY(object1, object2) { - // Can't separate two immovable objects - if(object1.immovable && object2.immovable) { - return false; - } - // First, get the two object deltas - var overlap = 0; - var obj1Delta = object1.y - object1.last.y; - var obj2Delta = object2.y - object2.last.y; - if(obj1Delta != obj2Delta) { - // Check if the Y hulls actually overlap - var obj1DeltaAbs = (obj1Delta > 0) ? obj1Delta : -obj1Delta; - var obj2DeltaAbs = (obj2Delta > 0) ? obj2Delta : -obj2Delta; - var obj1Bounds = new Phaser.Quad(object1.x, object1.y - ((obj1Delta > 0) ? obj1Delta : 0), object1.width, object1.height + obj1DeltaAbs); - var obj2Bounds = new Phaser.Quad(object2.x, object2.y - ((obj2Delta > 0) ? obj2Delta : 0), object2.width, object2.height + obj2DeltaAbs); - console.log(obj1Bounds.toString(), obj2Bounds.toString()); - if((obj1Bounds.x + obj1Bounds.width > obj2Bounds.x) && (obj1Bounds.x < obj2Bounds.x + obj2Bounds.width) && (obj1Bounds.y + obj1Bounds.height > obj2Bounds.y) && (obj1Bounds.y < obj2Bounds.y + obj2Bounds.height)) { - var maxOverlap = obj1DeltaAbs + obj2DeltaAbs + Collision.OVERLAP_BIAS; - console.log('max33', maxOverlap, obj1Delta, obj2Delta, obj1DeltaAbs, obj2DeltaAbs); - // If they did overlap (and can), figure out by how much and flip the corresponding flags - if(obj1Delta > obj2Delta) { - overlap = object1.y + object1.height - object2.y; - if((overlap > maxOverlap) || !(object1.allowCollisions & Collision.DOWN) || !(object2.allowCollisions & Collision.UP)) { - overlap = 0; - } else { - object1.touching |= Collision.DOWN; - object2.touching |= Collision.UP; - } - } else if(obj1Delta < obj2Delta) { - overlap = object1.y - object2.height - object2.y; - if((-overlap > maxOverlap) || !(object1.allowCollisions & Collision.UP) || !(object2.allowCollisions & Collision.DOWN)) { - overlap = 0; - } else { - object1.touching |= Collision.UP; - object2.touching |= Collision.DOWN; - } - } - } - } - // Then adjust their positions and velocities accordingly (if there was any overlap) - if(overlap != 0) { - console.log('y overlap', overlap); - var obj1Velocity = object1.velocity.y; - var obj2Velocity = object2.velocity.y; - if(!object1.immovable && !object2.immovable) { - overlap *= 0.5; - object1.y = object1.y - overlap; - object2.y += overlap; - var obj1NewVelocity = Math.sqrt((obj2Velocity * obj2Velocity * object2.mass) / object1.mass) * ((obj2Velocity > 0) ? 1 : -1); - var obj2NewVelocity = Math.sqrt((obj1Velocity * obj1Velocity * object1.mass) / object2.mass) * ((obj1Velocity > 0) ? 1 : -1); - var average = (obj1NewVelocity + obj2NewVelocity) * 0.5; - obj1NewVelocity -= average; - obj2NewVelocity -= average; - object1.velocity.y = average + obj1NewVelocity * object1.elasticity; - object2.velocity.y = average + obj2NewVelocity * object2.elasticity; - } else if(!object1.immovable) { - object1.y = object1.y - overlap; - object1.velocity.y = obj2Velocity - obj1Velocity * object1.elasticity; - // This is special case code that handles things like horizontal moving platforms you can ride - if(object2.active && object2.moves && (obj1Delta > obj2Delta)) { - object1.x += object2.x - object2.last.x; - } - console.log('y2', object1.y, object1.velocity.y); - } else if(!object2.immovable) { - object2.y += overlap; - object2.velocity.y = obj1Velocity - obj2Velocity * object2.elasticity; - // This is special case code that handles things like horizontal moving platforms you can ride - if(object1.active && object1.moves && (obj1Delta < obj2Delta)) { - object2.x += object1.x - object1.last.x; - } - console.log('y3', object2.y, object2.velocity.y); - } - return true; - } else { - return false; - } - }; Collision.distance = /** * Returns the distance between the two given coordinates. * @param x1 The X value of the first coordinate @@ -6208,6 +6689,369 @@ var Phaser; function distanceSquared(x1, y1, x2, y2) { return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); }; + Collision.flattenPointsOn = // SAT + /** + * Flattens the specified array of points onto a unit vector axis, + * resulting in a one dimensional range of the minimum and + * maximum value on that axis. + * + * @param {Array.} points The points to flatten. + * @param {Vector} normal The unit vector axis to flatten on. + * @param {Array.} result An array. After calling this function, + * result[0] will be the minimum value, + * result[1] will be the maximum value. + */ + function flattenPointsOn(points, normal, result) { + var min = Number.MAX_VALUE; + var max = -Number.MAX_VALUE; + var len = points.length; + for(var i = 0; i < len; i++) { + // Get the magnitude of the projection of the point onto the normal + var dot = points[i].dot(normal); + if(dot < min) { + min = dot; + } + if(dot > max) { + max = dot; + } + } + result[0] = min; + result[1] = max; + }; + Collision.isSeparatingAxis = /** + * Check whether two convex clockwise polygons are separated by the specified + * axis (must be a unit vector). + * + * @param {Vector} aPos The position of the first polygon. + * @param {Vector} bPos The position of the second polygon. + * @param {Array.} aPoints The points in the first polygon. + * @param {Array.} bPoints The points in the second polygon. + * @param {Vector} axis The axis (unit sized) to test against. The points of both polygons + * will be projected onto this axis. + * @param {Response=} response A Response object (optional) which will be populated + * if the axis is not a separating axis. + * @return {boolean} true if it is a separating axis, false otherwise. If false, + * and a response is passed in, information about how much overlap and + * the direction of the overlap will be populated. + */ + function isSeparatingAxis(aPos, bPos, aPoints, bPoints, axis, response) { + if (typeof response === "undefined") { response = null; } + var rangeA = Collision.T_ARRAYS.pop(); + var rangeB = Collision.T_ARRAYS.pop(); + // Get the magnitude of the offset between the two polygons + var offsetV = Collision.T_VECTORS.pop().copyFrom(bPos).sub(aPos); + var projectedOffset = offsetV.dot(axis); + // Project the polygons onto the axis. + Collision.flattenPointsOn(aPoints, axis, rangeA); + Collision.flattenPointsOn(bPoints, axis, rangeB); + // Move B's range to its position relative to A. + rangeB[0] += projectedOffset; + rangeB[1] += projectedOffset; + // Check if there is a gap. If there is, this is a separating axis and we can stop + if(rangeA[0] > rangeB[1] || rangeB[0] > rangeA[1]) { + Collision.T_VECTORS.push(offsetV); + Collision.T_ARRAYS.push(rangeA); + Collision.T_ARRAYS.push(rangeB); + return true; + } + // If we're calculating a response, calculate the overlap. + if(response) { + var overlap = 0; + // A starts further left than B + if(rangeA[0] < rangeB[0]) { + response.aInB = false; + // A ends before B does. We have to pull A out of B + if(rangeA[1] < rangeB[1]) { + overlap = rangeA[1] - rangeB[0]; + response.bInA = false; + // B is fully inside A. Pick the shortest way out. + } else { + var option1 = rangeA[1] - rangeB[0]; + var option2 = rangeB[1] - rangeA[0]; + overlap = option1 < option2 ? option1 : -option2; + } + // B starts further left than A + } else { + response.bInA = false; + // B ends before A ends. We have to push A out of B + if(rangeA[1] > rangeB[1]) { + overlap = rangeA[0] - rangeB[1]; + response.aInB = false; + // A is fully inside B. Pick the shortest way out. + } else { + var option1 = rangeA[1] - rangeB[0]; + var option2 = rangeB[1] - rangeA[0]; + overlap = option1 < option2 ? option1 : -option2; + } + } + // If this is the smallest amount of overlap we've seen so far, set it as the minimum overlap. + var absOverlap = Math.abs(overlap); + if(absOverlap < response.overlap) { + response.overlap = absOverlap; + response.overlapN.copyFrom(axis); + if(overlap < 0) { + response.overlapN.reverse(); + } + } + } + Collision.T_VECTORS.push(offsetV); + Collision.T_ARRAYS.push(rangeA); + Collision.T_ARRAYS.push(rangeB); + return false; + }; + Collision.LEFT_VORNOI_REGION = -1; + Collision.MIDDLE_VORNOI_REGION = 0; + Collision.RIGHT_VORNOI_REGION = 1; + Collision.vornoiRegion = /** + * Calculates which Vornoi region a point is on a line segment. + * It is assumed that both the line and the point are relative to (0, 0) + * + * | (0) | + * (-1) [0]--------------[1] (1) + * | (0) | + * + * @param {Vector} line The line segment. + * @param {Vector} point The point. + * @return {number} LEFT_VORNOI_REGION (-1) if it is the left region, + * MIDDLE_VORNOI_REGION (0) if it is the middle region, + * RIGHT_VORNOI_REGION (1) if it is the right region. + */ + function vornoiRegion(line, point) { + var len2 = line.length2(); + var dp = point.dot(line); + if(dp < 0) { + return Collision.LEFT_VORNOI_REGION; + } else if(dp > len2) { + return Collision.RIGHT_VORNOI_REGION; + } else { + return Collision.MIDDLE_VORNOI_REGION; + } + }; + Collision.testCircleCircle = /** + * Check if two circles intersect. + * + * @param {Circle} a The first circle. + * @param {Circle} b The second circle. + * @param {Response=} response Response object (optional) that will be populated if + * the circles intersect. + * @return {boolean} true if the circles intersect, false if they don't. + */ + function testCircleCircle(a, b, response) { + if (typeof response === "undefined") { response = null; } + var differenceV = Collision.T_VECTORS.pop().copyFrom(b.pos).sub(a.pos); + var totalRadius = a.radius + b.radius; + var totalRadiusSq = totalRadius * totalRadius; + var distanceSq = differenceV.length2(); + if(distanceSq > totalRadiusSq) { + // They do not intersect + Collision.T_VECTORS.push(differenceV); + return false; + } + // They intersect. If we're calculating a response, calculate the overlap. + if(response) { + var dist = Math.sqrt(distanceSq); + response.a = a; + response.b = b; + response.overlap = totalRadius - dist; + response.overlapN.copyFrom(differenceV.normalize()); + response.overlapV.copyFrom(differenceV).scale(response.overlap); + response.aInB = a.radius <= b.radius && dist <= b.radius - a.radius; + response.bInA = b.radius <= a.radius && dist <= a.radius - b.radius; + } + Collision.T_VECTORS.push(differenceV); + return true; + }; + Collision.testPolygonCircle = /** + * Check if a polygon and a circle intersect. + * + * @param {Polygon} polygon The polygon. + * @param {Circle} circle The circle. + * @param {Response=} response Response object (optional) that will be populated if + * they interset. + * @return {boolean} true if they intersect, false if they don't. + */ + function testPolygonCircle(polygon, circle, response) { + if (typeof response === "undefined") { response = null; } + var circlePos = Collision.T_VECTORS.pop().copyFrom(circle.pos).sub(polygon.pos); + var radius = circle.radius; + var radius2 = radius * radius; + var points = polygon.points; + var len = points.length; + var edge = Collision.T_VECTORS.pop(); + var point = Collision.T_VECTORS.pop(); + // For each edge in the polygon + for(var i = 0; i < len; i++) { + var next = i === len - 1 ? 0 : i + 1; + var prev = i === 0 ? len - 1 : i - 1; + var overlap = 0; + var overlapN = null; + // Get the edge + edge.copyFrom(polygon.edges[i]); + // Calculate the center of the cirble relative to the starting point of the edge + point.copyFrom(circlePos).sub(points[i]); + // If the distance between the center of the circle and the point + // is bigger than the radius, the polygon is definitely not fully in + // the circle. + if(response && point.length2() > radius2) { + response.aInB = false; + } + // Calculate which Vornoi region the center of the circle is in. + var region = Collision.vornoiRegion(edge, point); + if(region === Collision.LEFT_VORNOI_REGION) { + // Need to make sure we're in the RIGHT_VORNOI_REGION of the previous edge. + edge.copyFrom(polygon.edges[prev]); + // Calculate the center of the circle relative the starting point of the previous edge + var point2 = Collision.T_VECTORS.pop().copyFrom(circlePos).sub(points[prev]); + region = Collision.vornoiRegion(edge, point2); + if(region === Collision.RIGHT_VORNOI_REGION) { + // It's in the region we want. Check if the circle intersects the point. + var dist = point.length2(); + if(dist > radius) { + // No intersection + Collision.T_VECTORS.push(circlePos); + Collision.T_VECTORS.push(edge); + Collision.T_VECTORS.push(point); + Collision.T_VECTORS.push(point2); + return false; + } else if(response) { + // It intersects, calculate the overlap + response.bInA = false; + overlapN = point.normalize(); + overlap = radius - dist; + } + } + Collision.T_VECTORS.push(point2); + } else if(region === Collision.RIGHT_VORNOI_REGION) { + // Need to make sure we're in the left region on the next edge + edge.copyFrom(polygon.edges[next]); + // Calculate the center of the circle relative to the starting point of the next edge + point.copyFrom(circlePos).sub(points[next]); + region = Collision.vornoiRegion(edge, point); + if(region === Collision.LEFT_VORNOI_REGION) { + // It's in the region we want. Check if the circle intersects the point. + var dist = point.length2(); + if(dist > radius) { + // No intersection + Collision.T_VECTORS.push(circlePos); + Collision.T_VECTORS.push(edge); + Collision.T_VECTORS.push(point); + return false; + } else if(response) { + // It intersects, calculate the overlap + response.bInA = false; + overlapN = point.normalize(); + overlap = radius - dist; + } + } + // MIDDLE_VORNOI_REGION + } else { + // Need to check if the circle is intersecting the edge, + // Change the edge into its "edge normal". + var normal = edge.perp().normalize(); + // Find the perpendicular distance between the center of the + // circle and the edge. + var dist = point.dot(normal); + var distAbs = Math.abs(dist); + // If the circle is on the outside of the edge, there is no intersection + if(dist > 0 && distAbs > radius) { + Collision.T_VECTORS.push(circlePos); + Collision.T_VECTORS.push(normal); + Collision.T_VECTORS.push(point); + return false; + } else if(response) { + // It intersects, calculate the overlap. + overlapN = normal; + overlap = radius - dist; + // If the center of the circle is on the outside of the edge, or part of the + // circle is on the outside, the circle is not fully inside the polygon. + if(dist >= 0 || overlap < 2 * radius) { + response.bInA = false; + } + } + } + // If this is the smallest overlap we've seen, keep it. + // (overlapN may be null if the circle was in the wrong Vornoi region) + if(overlapN && response && Math.abs(overlap) < Math.abs(response.overlap)) { + response.overlap = overlap; + response.overlapN.copyFrom(overlapN); + } + } + // Calculate the final overlap vector - based on the smallest overlap. + if(response) { + response.a = polygon; + response.b = circle; + response.overlapV.copyFrom(response.overlapN).scale(response.overlap); + } + Collision.T_VECTORS.push(circlePos); + Collision.T_VECTORS.push(edge); + Collision.T_VECTORS.push(point); + return true; + }; + Collision.testCirclePolygon = /** + * Check if a circle and a polygon intersect. + * + * NOTE: This runs slightly slower than polygonCircle as it just + * runs polygonCircle and reverses everything at the end. + * + * @param {Circle} circle The circle. + * @param {Polygon} polygon The polygon. + * @param {Response=} response Response object (optional) that will be populated if + * they interset. + * @return {boolean} true if they intersect, false if they don't. + */ + function testCirclePolygon(circle, polygon, response) { + if (typeof response === "undefined") { response = null; } + var result = Collision.testPolygonCircle(polygon, circle, response); + if(result && response) { + // Swap A and B in the response. + var a = response.a; + var aInB = response.aInB; + response.overlapN.reverse(); + response.overlapV.reverse(); + response.a = response.b; + response.b = a; + response.aInB = response.bInA; + response.bInA = aInB; + } + return result; + }; + Collision.testPolygonPolygon = /** + * Checks whether two convex, clockwise polygons intersect. + * + * @param {Polygon} a The first polygon. + * @param {Polygon} b The second polygon. + * @param {Response=} response Response object (optional) that will be populated if + * they interset. + * @return {boolean} true if they intersect, false if they don't. + */ + function testPolygonPolygon(a, b, response) { + if (typeof response === "undefined") { response = null; } + var aPoints = a.points; + var aLen = aPoints.length; + var bPoints = b.points; + var bLen = bPoints.length; + // If any of the edge normals of A is a separating axis, no intersection. + for(var i = 0; i < aLen; i++) { + if(Collision.isSeparatingAxis(a.pos, b.pos, aPoints, bPoints, a.normals[i], response)) { + return false; + } + } + // If any of the edge normals of B is a separating axis, no intersection. + for(var i = 0; i < bLen; i++) { + if(Collision.isSeparatingAxis(a.pos, b.pos, aPoints, bPoints, b.normals[i], response)) { + return false; + } + } + // Since none of the edge normals of A or B are a separating axis, there is an intersection + // and we've already calculated the smallest overlap (in isSeparatingAxis). Calculate the + // final overlap vector. + if(response) { + response.a = a; + response.b = b; + response.overlapV.copyFrom(response.overlapN).scale(response.overlap); + } + return true; + }; return Collision; })(); Phaser.Collision = Collision; @@ -7345,26 +8189,233 @@ var Phaser; return Math.sqrt(dx * dx + dy * dy); }; GameMath.prototype.rotatePoint = /** - * Rotates a point around the x/y coordinates given to the desired angle + * Rotates the point around the x/y coordinates given to the desired angle and distance + * @param point {Object} Any object with exposed x and y properties * @param x {number} The x coordinate of the anchor point * @param y {number} The y coordinate of the anchor point - * @param angle {number} The angle of the rotation in radians - * @param point {Point} The point object to perform the rotation on + * @param {Number} angle The angle in radians (unless asDegrees is true) to return the point from. + * @param {Boolean} asDegrees Is the given angle in radians (false) or degrees (true)? + * @param {Number} distance An optional distance constraint between the point and the anchor * @return The modified point object */ - function (x, y, angle, point) { - var s = Math.sin(angle); - var c = Math.cos(angle); - point.x -= x; - point.y -= y; - var newX = point.x * c - point.y * s; - var newY = point.x * s - point.y * c; - return point.setTo(newX + x, newY + y); + function (point, x1, y1, angle, asDegrees, distance) { + if (typeof asDegrees === "undefined") { asDegrees = false; } + if (typeof distance === "undefined") { distance = null; } + if(asDegrees) { + angle = angle * GameMath.DEG_TO_RAD; + } + // Get distance from origin to the point + if(distance === null) { + distance = Math.sqrt(((x1 - point.x) * (x1 - point.x)) + ((y1 - point.y) * (y1 - point.y))); + } + point.x = x1 + distance * Math.cos(angle); + point.y = y1 + distance * Math.sin(angle); + return point; }; return GameMath; })(); Phaser.GameMath = GameMath; })(Phaser || (Phaser = {})); +/// +/** +* Phaser - GameObjectFactory +* +* A quick way to create new world objects and add existing objects to the current world. +*/ +var Phaser; +(function (Phaser) { + var GameObjectFactory = (function () { + /** + * GameObjectFactory constructor + * @param game {Game} A reference to the current Game. + */ + function GameObjectFactory(game) { + this._game = game; + this._world = this._game.world; + } + GameObjectFactory.prototype.camera = /** + * Create a new camera with specific position and size. + * + * @param x {number} X position of the new camera. + * @param y {number} Y position of the new camera. + * @param width {number} Width of the new camera. + * @param height {number} Height of the new camera. + * @returns {Camera} The newly created camera object. + */ + function (x, y, width, height) { + return this._world.createCamera(x, y, width, height); + }; + GameObjectFactory.prototype.geomSprite = /** + * Create a new GeomSprite with specific position. + * + * @param x {number} X position of the new geom sprite. + * @param y {number} Y position of the new geom sprite. + * @returns {GeomSprite} The newly created geom sprite object. + */ + function (x, y) { + return this._world.createGeomSprite(x, y); + }; + GameObjectFactory.prototype.sprite = /** + * Create a new Sprite with specific position and sprite sheet key. + * + * @param x {number} X position of the new sprite. + * @param y {number} Y position of the new sprite. + * @param key {string} Optional, key for the sprite sheet you want it to use. + * @returns {Sprite} The newly created sprite object. + */ + function (x, y, key) { + if (typeof key === "undefined") { key = ''; } + return this._world.createSprite(x, y, key); + }; + GameObjectFactory.prototype.dynamicTexture = /** + * Create a new DynamicTexture with specific size. + * + * @param width {number} Width of the texture. + * @param height {number} Height of the texture. + * @returns {DynamicTexture} The newly created dynamic texture object. + */ + function (width, height) { + return this._world.createDynamicTexture(width, height); + }; + GameObjectFactory.prototype.group = /** + * Create a new object container. + * + * @param maxSize {number} Optional, capacity of this group. + * @returns {Group} The newly created group. + */ + function (maxSize) { + if (typeof maxSize === "undefined") { maxSize = 0; } + return this._world.createGroup(maxSize); + }; + GameObjectFactory.prototype.particle = /** + * Create a new Particle. + * + * @return {Particle} The newly created particle object. + */ + function () { + return this._world.createParticle(); + }; + GameObjectFactory.prototype.emitter = /** + * Create a new Emitter. + * + * @param x {number} Optional, x position of the emitter. + * @param y {number} Optional, y position of the emitter. + * @param size {number} Optional, size of this emitter. + * @return {Emitter} The newly created emitter object. + */ + function (x, y, size) { + if (typeof x === "undefined") { x = 0; } + if (typeof y === "undefined") { y = 0; } + if (typeof size === "undefined") { size = 0; } + return this._world.createEmitter(x, y, size); + }; + GameObjectFactory.prototype.scrollZone = /** + * Create a new ScrollZone object with image key, position and size. + * + * @param key {string} Key to a image you wish this object to use. + * @param x {number} X position of this object. + * @param y {number} Y position of this object. + * @param width number} Width of this object. + * @param height {number} Height of this object. + * @returns {ScrollZone} The newly created scroll zone object. + */ + function (key, x, y, width, height) { + if (typeof x === "undefined") { x = 0; } + if (typeof y === "undefined") { y = 0; } + if (typeof width === "undefined") { width = 0; } + if (typeof height === "undefined") { height = 0; } + return this._world.createScrollZone(key, x, y, width, height); + }; + GameObjectFactory.prototype.tilemap = /** + * Create a new Tilemap. + * + * @param key {string} Key for tileset image. + * @param mapData {string} Data of this tilemap. + * @param format {number} Format of map data. (Tilemap.FORMAT_CSV or Tilemap.FORMAT_TILED_JSON) + * @param [resizeWorld] {boolean} resize the world to make same as tilemap? + * @param [tileWidth] {number} width of each tile. + * @param [tileHeight] {number} height of each tile. + * @return {Tilemap} The newly created tilemap object. + */ + function (key, mapData, format, resizeWorld, tileWidth, tileHeight) { + if (typeof resizeWorld === "undefined") { resizeWorld = true; } + if (typeof tileWidth === "undefined") { tileWidth = 0; } + if (typeof tileHeight === "undefined") { tileHeight = 0; } + return this._world.createTilemap(key, mapData, format, resizeWorld, tileWidth, tileHeight); + }; + GameObjectFactory.prototype.tween = /** + * Create a tween object for a specific object. + * + * @param obj Object you wish the tween will affect. + * @return {Phaser.Tween} The newly created tween object. + */ + function (obj) { + return this._game.tweens.create(obj); + }; + GameObjectFactory.prototype.existingSprite = /** + * Add an existing Sprite to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param sprite The Sprite to add to the Game World + * @return {Phaser.Sprite} The Sprite object + */ + function (sprite) { + return this._world.group.add(sprite); + }; + GameObjectFactory.prototype.existingGeomSprite = /** + * Add an existing GeomSprite to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param sprite The GeomSprite to add to the Game World + * @return {Phaser.GeomSprite} The GeomSprite object + */ + function (sprite) { + return this._world.group.add(sprite); + }; + GameObjectFactory.prototype.existingEmitter = /** + * Add an existing Emitter to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param emitter The Emitter to add to the Game World + * @return {Phaser.Emitter} The Emitter object + */ + function (emitter) { + return this._world.group.add(emitter); + }; + GameObjectFactory.prototype.existingScrollZone = /** + * Add an existing ScrollZone to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param scrollZone The ScrollZone to add to the Game World + * @return {Phaser.ScrollZone} The ScrollZone object + */ + function (scrollZone) { + return this._world.group.add(scrollZone); + }; + GameObjectFactory.prototype.existingTilemap = /** + * Add an existing Tilemap to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param tilemap The Tilemap to add to the Game World + * @return {Phaser.Tilemap} The Tilemap object + */ + function (tilemap) { + return this._world.group.add(tilemap); + }; + GameObjectFactory.prototype.existingTween = /** + * Add an existing Tween to the current world. + * Note: This doesn't check or update the objects reference to Game. If that is wrong, all kinds of things will break. + * + * @param tween The Tween to add to the Game World + * @return {Phaser.Tween} The Tween object + */ + function (tween) { + return this._game.tweens.add(tween); + }; + return GameObjectFactory; + })(); + Phaser.GameObjectFactory = GameObjectFactory; +})(Phaser || (Phaser = {})); /// /// /** @@ -7398,9 +8449,36 @@ var Phaser; this._maxSize = MaxSize; this._marker = 0; this._sortIndex = null; + this.cameraBlacklist = []; } Group.ASCENDING = -1; Group.DESCENDING = 1; + Group.prototype.hideFromCamera = /** + * If you do not wish this object to be visible to a specific camera, pass the camera here. + * + * @param camera {Camera} The specific camera. + */ + function (camera) { + if(this.cameraBlacklist.indexOf(camera.ID) == -1) { + this.cameraBlacklist.push(camera.ID); + } + }; + Group.prototype.showToCamera = /** + * Make this object only visible to a specific camera. + * + * @param camera {Camera} The camera you wish it to be visible. + */ + function (camera) { + if(this.cameraBlacklist.indexOf(camera.ID) !== -1) { + this.cameraBlacklist.slice(this.cameraBlacklist.indexOf(camera.ID), 1); + } + }; + Group.prototype.clearCameraList = /** + * This clears the camera black list, making the GameObject visible to all cameras. + */ + function () { + this.cameraBlacklist.length = 0; + }; Group.prototype.destroy = /** * Override this function to handle any deleting or "shutdown" type operations you might need, * such as removing traditional Flash children like Basic objects. @@ -7443,6 +8521,9 @@ var Phaser; */ function (camera, cameraOffsetX, cameraOffsetY, forceRender) { if (typeof forceRender === "undefined") { forceRender = false; } + if(this.cameraBlacklist.indexOf(camera.ID) !== -1) { + return; + } if(this.ignoreGlobalRender && forceRender == false) { return; } @@ -7945,6 +9026,11 @@ var Phaser; * @param callback {function} This will be called when assets completely loaded. */ function Loader(game, callback) { + /** + * The crossOrigin value applied to loaded images + * @type {string} + */ + this.crossOrigin = ''; this._game = game; this._gameCreateComplete = callback; this._keys = []; @@ -8174,6 +9260,7 @@ var Phaser; file.data.onerror = function () { return _this.fileError(file.key); }; + file.data.crossOrigin = this.crossOrigin; file.data.src = file.url; break; case 'audio': @@ -8844,7 +9931,7 @@ var Phaser; /** * Phaser * -* v0.9.5 - April 28th 2013 +* v0.9.6 - May 21st 2013 * * A small and feature-packed 2D canvas game framework born from the firey pits of Flixel and Kiwi. * @@ -8858,7 +9945,7 @@ var Phaser; */ var Phaser; (function (Phaser) { - Phaser.VERSION = 'Phaser version 0.9.5'; + Phaser.VERSION = 'Phaser version 0.9.6'; })(Phaser || (Phaser = {})); /// /** @@ -9185,7 +10272,7 @@ var Phaser; * Start color fading cycle. */ function () { - this._fade = this._game.createTween(this._color2); + this._fade = this._game.add.tween(this._color2); this._fade.to({ r: Math.random() * 250, g: Math.random() * 250, @@ -9274,7 +10361,7 @@ var Phaser; * Start fadeOut effect. */ function () { - this._fade = this._game.createTween(this._color); + this._fade = this._game.add.tween(this._color); this._fade.to({ r: 50, g: 50, @@ -9287,7 +10374,7 @@ var Phaser; * Start fadeIn effect. */ function () { - this._fade = this._game.createTween(this._color); + this._fade = this._game.add.tween(this._color); this._fade.to({ r: 255, g: 255, @@ -9414,18 +10501,24 @@ var Phaser; } if(event.type == 'blur' || document['hidden'] == true || document['webkitHidden'] == true) { if(this._game.paused == false) { - this._pauseScreen.onPaused(); - this.saveCanvasValues(); - this._game.paused = true; + this.pauseGame(); } } else { if(this._game.paused == true) { - this._pauseScreen.onResume(); - this._game.paused = false; - this.restoreCanvasValues(); + this.resumeGame(); } } }; + Stage.prototype.pauseGame = function () { + this._pauseScreen.onPaused(); + this.saveCanvasValues(); + this._game.paused = true; + }; + Stage.prototype.resumeGame = function () { + this._pauseScreen.onResume(); + this.restoreCanvasValues(); + this._game.paused = false; + }; Stage.prototype.getOffset = /** * Get the DOM offset values of the given element */ @@ -10357,134 +11450,6 @@ var Phaser; })(); Phaser.TweenManager = TweenManager; })(Phaser || (Phaser = {})); -/// -/** -* Phaser - Vector2 -* -* A simple 2-dimensional vector class. Based on the one included with verlet-js by Sub Protocol released under MIT -*/ -var Phaser; -(function (Phaser) { - var Vector2 = (function () { - /** - * Creates a new Vector2 object. - * @class Vector2 - * @constructor - * @param {Number} x The x coordinate of vector2 - * @param {Number} y The y coordinate of vector2 - * @return {Vector2} This object - **/ - function Vector2(x, y) { - if (typeof x === "undefined") { x = 0; } - if (typeof y === "undefined") { y = 0; } - this.x = x; - this.y = y; - } - Vector2.prototype.setTo = function (x, y) { - this.x = x; - this.y = y; - return this; - }; - Vector2.prototype.add = function (v, output) { - if (typeof output === "undefined") { output = new Vector2(); } - return output.setTo(this.x + v.x, this.y + v.y); - }; - Vector2.prototype.sub = function (v, output) { - if (typeof output === "undefined") { output = new Vector2(); } - return output.setTo(this.x - v.x, this.y - v.y); - }; - Vector2.prototype.mul = function (v, output) { - if (typeof output === "undefined") { output = new Vector2(); } - return output.setTo(this.x * v.x, this.y * v.y); - }; - Vector2.prototype.div = function (v, output) { - if (typeof output === "undefined") { output = new Vector2(); } - return output.setTo(this.x / v.x, this.y / v.y); - }; - Vector2.prototype.scale = function (coef, output) { - if (typeof output === "undefined") { output = new Vector2(); } - return output.setTo(this.x * coef, this.y * coef); - }; - Vector2.prototype.mutableSet = function (v) { - this.x = v.x; - this.y = v.y; - return this; - }; - Vector2.prototype.mutableAdd = function (v) { - this.x += v.x; - this.y += v.y; - return this; - }; - Vector2.prototype.mutableSub = function (v) { - this.x -= v.x; - this.y -= v.y; - return this; - }; - Vector2.prototype.mutableMul = function (v) { - this.x *= v.x; - this.y *= v.y; - return this; - }; - Vector2.prototype.mutableDiv = function (v) { - this.x /= v.x; - this.y /= v.y; - return this; - }; - Vector2.prototype.mutableScale = function (coef) { - this.x *= coef; - this.y *= coef; - return this; - }; - Vector2.prototype.equals = function (v) { - return this.x == v.x && this.y == v.y; - }; - Vector2.prototype.epsilonEquals = function (v, epsilon) { - return Math.abs(this.x - v.x) <= epsilon && Math.abs(this.y - v.y) <= epsilon; - }; - Vector2.prototype.length = function () { - return Math.sqrt(this.x * this.x + this.y * this.y); - }; - Vector2.prototype.length2 = function () { - return this.x * this.x + this.y * this.y; - }; - Vector2.prototype.dist = function (v) { - return Math.sqrt(this.dist2(v)); - }; - Vector2.prototype.dist2 = function (v) { - return ((v.x - this.x) * (v.x - this.x)) + ((v.y - this.y) * (v.y - this.y)); - }; - Vector2.prototype.normal = function (output) { - if (typeof output === "undefined") { output = new Vector2(); } - var m = Math.sqrt(this.x * this.x + this.y * this.y); - return output.setTo(this.x / m, this.y / m); - }; - Vector2.prototype.dot = function (v) { - return this.x * v.x + this.y * v.y; - }; - Vector2.prototype.angle = function (v) { - return Math.atan2(this.x * v.y - this.y * v.x, this.x * v.x + this.y * v.y); - }; - Vector2.prototype.angle2 = function (vLeft, vRight) { - return vLeft.sub(this).angle(vRight.sub(this)); - }; - Vector2.prototype.rotate = function (origin, theta, output) { - if (typeof output === "undefined") { output = new Vector2(); } - var x = this.x - origin.x; - var y = this.y - origin.y; - return output.setTo(x * Math.cos(theta) - y * Math.sin(theta) + origin.x, x * Math.sin(theta) + y * Math.cos(theta) + origin.y); - }; - Vector2.prototype.toString = /** - * Returns a string representation of this object. - * @method toString - * @return {string} a string representation of the object. - **/ - function () { - return "[{Vector2 (x=" + this.x + " y=" + this.y + ")}]"; - }; - return Vector2; - })(); - Phaser.Vector2 = Vector2; -})(Phaser || (Phaser = {})); var Phaser; (function (Phaser) { /// @@ -10581,13 +11546,32 @@ var Phaser; * @return {Composite} This object **/ function Composite(game) { + /** + * Texture of the particles to be rendered. + */ + this._texture = null; + // local rendering related temp vars to help avoid gc spikes + this._sx = 0; + this._sy = 0; + this._sw = 0; + this._sh = 0; + this._dx = 0; + this._dy = 0; + this._dw = 0; + this._dh = 0; + this._hw = 0; + this._hh = 0; this.drawParticles = null; this.drawConstraints = null; + this.hideConstraints = true; + this.constraintLineColor = 'rgba(200,200,200,1)'; this._game = game; + this.sprites = []; this.particles = []; this.constraints = []; + this.frameBounds = new Phaser.Quad(); } - Composite.prototype.createDistanceConstraint = // Map sprites to particles + Composite.prototype.createDistanceConstraint = // Create Constraints function (a, b, stiffness, distance) { if (typeof distance === "undefined") { distance = null; } this.constraints.push(new Phaser.Verlet.DistanceConstraint(a, b, stiffness, distance)); @@ -10601,6 +11585,74 @@ var Phaser; this.constraints.push(new Phaser.Verlet.PinConstraint(a, pos)); return this.constraints[this.constraints.length - 1]; }; + Composite.prototype.loadGraphic = /** + * Load a graphic for this Composite. The graphic cannot be a SpriteSheet yet. + * @param key {string} Key of the graphic you want to load for this sprite. + * @return {Composite} This object + */ + function (key) { + if(this._game.cache.getImage(key) !== null) { + if(this._game.cache.isSpriteSheet(key) == false) { + this._texture = this._game.cache.getImage(key); + this.frameBounds.width = this._texture.width; + this.frameBounds.height = this._texture.height; + this._hw = Math.floor(this.frameBounds.width / 2); + this._hh = Math.floor(this.frameBounds.width / 2); + this.drawParticles = this.render; + this.drawConstraints = this.renderConstraints; + } + } + return this; + }; + Composite.prototype.renderConstraints = function (context) { + if(this.hideConstraints == true || this.constraints.length == 0) { + return; + } + var i; + context.beginPath(); + for(i in this.constraints) { + if(this.constraints[i].b) { + context.moveTo(this.constraints[i].a.pos.x, this.constraints[i].a.pos.y); + context.lineTo(this.constraints[i].b.pos.x, this.constraints[i].b.pos.y); + } + } + context.strokeStyle = this.constraintLineColor; + context.stroke(); + context.closePath(); + }; + Composite.prototype.render = function (context) { + this._sx = 0; + this._sy = 0; + this._sw = this.frameBounds.width; + this._sh = this.frameBounds.height; + this._dw = this.frameBounds.width; + this._dh = this.frameBounds.height; + this._sx = Math.round(this._sx); + this._sy = Math.round(this._sy); + this._sw = Math.round(this._sw); + this._sh = Math.round(this._sh); + this._dw = Math.round(this._dw); + this._dh = Math.round(this._dh); + var i; + for(i in this.particles) { + //this._dx = cameraOffsetX + (this.frameBounds.topLeft.x - camera.worldView.x); + //this._dy = cameraOffsetY + (this.frameBounds.topLeft.y - camera.worldView.y); + this._dx = this.particles[i].pos.x - this._hw; + this._dy = this.particles[i].pos.y - this._hh; + this._dx = Math.round(this._dx); + this._dy = Math.round(this._dy); + context.drawImage(this._texture, // Source Image + this._sx, // Source X (location within the source image) + this._sy, // Source Y + this._sw, // Source Width + this._sh, // Source Height + this._dx, // Destination X (where on the canvas it'll be drawn) + this._dy, // Destination Y + this._dw, // Destination Width (always same as Source Width unless scaled) + this._dh); + // Destination Height (always same as Source Height unless scaled) + } + }; Composite.prototype.pin = function (index, pos) { if (typeof pos === "undefined") { pos = null; } if(pos == null) { @@ -10660,6 +11712,7 @@ var Phaser; ctx.lineTo(this.b.pos.x, this.b.pos.y); ctx.strokeStyle = "#d8dde2"; ctx.stroke(); + ctx.closePath(); }; return DistanceConstraint; })(); @@ -10750,12 +11803,13 @@ var Phaser; * @return {Vector2} This object **/ function VerletManager(game, width, height) { + this._v = new Phaser.Vector2(); this.composites = []; - this.step = 32; + this.step = 16; this.selectionRadius = 20; this.draggedEntity = null; this.highlightColor = '#4f545c'; - this.v = new Phaser.Vector2(); + this.hideNearestEntityCircle = false; this._game = game; this.width = width; this.height = height; @@ -10770,10 +11824,10 @@ var Phaser; VerletManager.prototype.intersectionTime = /** * Computes time of intersection of a particle with a wall * - * @param {Vec2} line wall's root position - * @param {Vec2} p particle's position - * @param {Vec2} dir walls's direction - * @param {Vec2} v particle's velocity + * @param {Vec2} line walls root position + * @param {Vec2} p particle position + * @param {Vec2} dir walls direction + * @param {Vec2} v particles velocity */ function (wall, p, dir, v) { if(dir.x != 0) { @@ -10799,35 +11853,16 @@ var Phaser; return new Phaser.Vector2(p.x + v.x * t, p.y + v.y * t); }; VerletManager.prototype.bounds = function (particle) { - this.v.mutableSet(particle.pos); - this.v.mutableSub(particle.lastPos); + this._v.mutableSet(particle.pos); + this._v.mutableSub(particle.lastPos); if(particle.pos.y > this.height - 1) { - particle.pos.mutableSet(this.intersectionPoint(new Phaser.Vector2(0, this.height - 1), particle.lastPos, new Phaser.Vector2(1, 0), this.v)); + particle.pos.mutableSet(this.intersectionPoint(new Phaser.Vector2(0, this.height - 1), particle.lastPos, new Phaser.Vector2(1, 0), this._v)); } if(particle.pos.x < 0) { - particle.pos.mutableSet(this.intersectionPoint(new Phaser.Vector2(0, 0), particle.pos, new Phaser.Vector2(0, 1), this.v)); + particle.pos.mutableSet(this.intersectionPoint(new Phaser.Vector2(0, 0), particle.pos, new Phaser.Vector2(0, 1), this._v)); } if(particle.pos.x > this.width - 1) { - particle.pos.mutableSet(this.intersectionPoint(new Phaser.Vector2(this.width - 1, 0), particle.pos, new Phaser.Vector2(0, 1), this.v)); - } - }; - VerletManager.prototype.OLDbounds = function (particle) { - if(particle.pos.y > this.height - 1) { - particle.pos.y = this.height - 1; - } - if(particle.pos.x < 0) { - var vx = particle.pos.x - particle.lastPos.x; - var vy = particle.pos.y - particle.lastPos.y; - if(vx == 0) { - particle.pos.x = 0; - } else { - var t = -particle.lastPos.x / vx; - particle.pos.x = particle.lastPos.x + t * vx; - particle.pos.y = particle.lastPos.y + t * vy; - } - } - if(particle.pos.x > this.width - 1) { - particle.pos.x = this.width - 1; + particle.pos.mutableSet(this.intersectionPoint(new Phaser.Vector2(this.width - 1, 0), particle.pos, new Phaser.Vector2(0, 1), this._v)); } }; VerletManager.prototype.createPoint = function (pos) { @@ -10837,8 +11872,8 @@ var Phaser; return composite; }; VerletManager.prototype.createLineSegments = function (vertices, stiffness) { - var i; var composite = new Phaser.Verlet.Composite(this._game); + var i; for(i in vertices) { composite.particles.push(new Phaser.Verlet.Particle(vertices[i])); if(i > 0) { @@ -10852,7 +11887,8 @@ var Phaser; var composite = new Phaser.Verlet.Composite(this._game); var xStride = width / segments; var yStride = height / segments; - var x, y; + var x; + var y; for(y = 0; y < segments; ++y) { for(x = 0; x < segments; ++x) { var px = origin.x + x * xStride - width / 2 + xStride / 2; @@ -10921,7 +11957,7 @@ var Phaser; } // handle dragging of entities if(this.draggedEntity) { - this.draggedEntity.pos.mutableSet(new Phaser.Vector2(this._game.input.x, this._game.input.y)); + this.draggedEntity.pos.mutableSet(this._game.input.position); } // relax var stepCoef = 1 / this.step; @@ -10959,7 +11995,7 @@ var Phaser; for(c in this.composites) { var particles = this.composites[c].particles; for(i in particles) { - var d2 = particles[i].pos.dist2(new Phaser.Vector2(this._game.input.x, this._game.input.y)); + var d2 = particles[i].pos.distance2(this._game.input.position); if(d2 <= this.selectionRadius * this.selectionRadius && (entity == null || d2 < d2Nearest)) { entity = particles[i]; constraintsNearest = this.composites[c].constraints; @@ -10999,11 +12035,12 @@ var Phaser; } // highlight nearest / dragged entity var nearest = this.draggedEntity || this.nearestEntity(); - if(nearest) { + if(nearest && this.hideNearestEntityCircle == false) { this.context.beginPath(); this.context.arc(nearest.pos.x, nearest.pos.y, 8, 0, 2 * Math.PI); this.context.strokeStyle = this.highlightColor; this.context.stroke(); + this.context.closePath(); } }; return VerletManager; @@ -11065,19 +12102,25 @@ var Phaser; World.prototype.setSize = // World methods /** * Update size of this world with specific width and height. - * You can choose update camera bounds automatically or not. + * You can choose update camera bounds and verlet manager automatically or not. * * @param width {number} New width of the world. * @param height {number} New height of the world. * @param [updateCameraBounds] {boolean} update camera bounds automatically or not. Default to true. + * @param [updateVerletBounds] {boolean} update verlet bounds automatically or not. Default to true. */ - function (width, height, updateCameraBounds) { + function (width, height, updateCameraBounds, updateVerletBounds) { if (typeof updateCameraBounds === "undefined") { updateCameraBounds = true; } + if (typeof updateVerletBounds === "undefined") { updateVerletBounds = true; } this.bounds.width = width; this.bounds.height = height; if(updateCameraBounds == true) { this._game.camera.setBounds(0, 0, width, height); } + if(updateVerletBounds == true) { + this._game.verlet.width = width; + this._game.verlet.height = height; + } }; Object.defineProperty(World.prototype, "width", { get: function () { @@ -12030,6 +13073,7 @@ var Phaser; Phaser.RequestAnimationFrame = RequestAnimationFrame; })(Phaser || (Phaser = {})); /// +/// /** * Phaser - Pointer * @@ -12066,17 +13110,17 @@ var Phaser; */ this._nextDrop = 0; /** - * A Point object representing the x/y screen coordinates of the Pointer. - * @property pointA - * @type {Point} + * A Vector object containing the initial position when the Pointer was engaged with the screen. + * @property positionDown + * @type {Vector2} **/ - this.pointA = null; + this.positionDown = null; /** - * A Point object representing the x/y screen coordinates of the Pointer. - * @property pointB - * @type {Point} + * A Vector object containing the current position of the Pointer on the screen. + * @property position + * @type {Vector2} **/ - this.pointB = null; + this.position = null; /** * A Circle object centered on the x/y screen coordinates of the Pointer. * Default size of 44px (Apple's recommended "finger tip" size) @@ -12183,8 +13227,8 @@ var Phaser; this._game = game; this.id = id; this.active = false; - this.pointA = new Phaser.Point(); - this.pointB = new Phaser.Point(); + this.position = new Phaser.Vector2(); + this.positionDown = new Phaser.Vector2(); this.circle = new Phaser.Circle(0, 0, 44); if(id == 0) { this.isMouse = true; @@ -12232,9 +13276,14 @@ var Phaser; if(event.button) { this.button = event.button; } + // Fix to stop rogue browser plugins from blocking the visibility state event + if(this._game.paused == true) { + this._game.stage.resumeGame(); + return this; + } this._history.length = 0; this.move(event); - this.pointA.setTo(this.x, this.y); + this.positionDown.setTo(this.x, this.y); this.active = true; this.withinGame = true; this.isDown = true; @@ -12264,8 +13313,8 @@ var Phaser; if(this._game.input.recordPointerHistory && this._game.time.now >= this._nextDrop) { this._nextDrop = this._game.time.now + this._game.input.recordRate; this._history.push({ - x: this.pointB.x, - y: this.pointB.y + x: this.position.x, + y: this.position.y }); if(this._history.length > this._game.input.recordLimit) { this._history.shift(); @@ -12290,13 +13339,13 @@ var Phaser; this.screenY = event.screenY; this.x = this.pageX - this._game.stage.offset.x; this.y = this.pageY - this._game.stage.offset.y; - this.pointB.setTo(this.x, this.y); + this.position.setTo(this.x, this.y); this.circle.x = this.x; this.circle.y = this.y; if(this._game.input.multiInputOverride == Phaser.Input.MOUSE_OVERRIDES_TOUCH || this._game.input.multiInputOverride == Phaser.Input.MOUSE_TOUCH_COMBINE || (this._game.input.multiInputOverride == Phaser.Input.TOUCH_OVERRIDES_MOUSE && this._game.input.currentPointers == 0)) { this._game.input.x = this.x * this._game.input.scaleX; this._game.input.y = this.y * this._game.input.scaleY; - this._game.input.point.setTo(this._game.input.x, this._game.input.y); + this._game.input.position.setTo(this._game.input.x, this._game.input.y); this._game.input.circle.x = this._game.input.x; this._game.input.circle.y = this._game.input.y; } @@ -12405,8 +13454,8 @@ var Phaser; this._game.stage.context.closePath(); // Render the points this._game.stage.context.beginPath(); - this._game.stage.context.moveTo(this.pointA.x, this.pointA.y); - this._game.stage.context.lineTo(this.pointB.x, this.pointB.y); + this._game.stage.context.moveTo(this.positionDown.x, this.positionDown.y); + this._game.stage.context.lineTo(this.position.x, this.position.y); this._game.stage.context.lineWidth = 2; this._game.stage.context.stroke(); this._game.stage.context.closePath(); @@ -12588,11 +13637,11 @@ var Phaser; */ this.multiInputOverride = Input.MOUSE_TOUCH_COMBINE; /** - * A Point object representing the x/y screen coordinates of the Pointer. - * @property point - * @type {Point} + * A vector object representing the current position of the Pointer. + * @property vector + * @type {Vector2} **/ - this.point = null; + this.position = null; /** * A Circle object centered on the x/y screen coordinates of the Input. * Default size of 44px (Apples recommended "finger tip" size) but can be changed to anything @@ -12684,6 +13733,36 @@ var Phaser; * @type {Number} */ this.recordLimit = 100; + /** + * A Pointer object + * @property pointer6 + * @type {Pointer} + **/ + this.pointer6 = null; + /** + * A Pointer object + * @property pointer7 + * @type {Pointer} + **/ + this.pointer7 = null; + /** + * A Pointer object + * @property pointer8 + * @type {Pointer} + **/ + this.pointer8 = null; + /** + * A Pointer object + * @property pointer9 + * @type {Pointer} + **/ + this.pointer9 = null; + /** + * A Pointer object + * @property pointer10 + * @type {Pointer} + **/ + this.pointer10 = null; this._game = game; this.mousePointer = new Phaser.Pointer(this._game, 0); this.pointer1 = new Phaser.Pointer(this._game, 1); @@ -12691,11 +13770,6 @@ var Phaser; this.pointer3 = new Phaser.Pointer(this._game, 3); this.pointer4 = new Phaser.Pointer(this._game, 4); this.pointer5 = new Phaser.Pointer(this._game, 5); - this.pointer6 = new Phaser.Pointer(this._game, 6); - this.pointer7 = new Phaser.Pointer(this._game, 7); - this.pointer8 = new Phaser.Pointer(this._game, 8); - this.pointer9 = new Phaser.Pointer(this._game, 9); - this.pointer10 = new Phaser.Pointer(this._game, 10); this.mouse = new Phaser.Mouse(this._game); this.keyboard = new Phaser.Keyboard(this._game); this.touch = new Phaser.Touch(this._game); @@ -12705,7 +13779,7 @@ var Phaser; this.onUp = new Phaser.Signal(); this.onTap = new Phaser.Signal(); this.onHold = new Phaser.Signal(); - this.point = new Phaser.Point(); + this.position = new Phaser.Vector2(); this.circle = new Phaser.Circle(0, 0, 44); this.currentPointers = 0; } @@ -12742,25 +13816,74 @@ var Phaser; enumerable: true, configurable: true }); - Input.prototype.start = function () { + Input.prototype.addPointer = /** + * Add a new Pointer object to the Input Manager. By default Input creates 5 pointer objects for you. If you need more + * use this to create a new one, up to a maximum of 10. + * @method addPointer + * @return {Pointer} A reference to the new Pointer object + **/ + function () { + var next = 0; + if(this.pointer10 === null) { + next = 10; + } + if(this.pointer9 === null) { + next = 9; + } + if(this.pointer8 === null) { + next = 8; + } + if(this.pointer7 === null) { + next = 7; + } + if(this.pointer6 === null) { + next = 6; + } + if(next == 0) { + throw new Error("You can only have 10 Pointer objects"); + return null; + } else { + this['pointer' + next] = new Phaser.Pointer(this._game, next); + return this['pointer' + next]; + } + }; + Input.prototype.start = /** + * Starts the Input Manager running + * @method start + **/ + function () { this.mouse.start(); this.keyboard.start(); this.touch.start(); this.mspointer.start(); this.gestures.start(); }; - Input.prototype.update = function () { + Input.prototype.update = /** + * Updates the Input Manager. Called by the core Game loop. + * @method update + **/ + function () { this.mousePointer.update(); this.pointer1.update(); this.pointer2.update(); this.pointer3.update(); this.pointer4.update(); this.pointer5.update(); - this.pointer6.update(); - this.pointer7.update(); - this.pointer8.update(); - this.pointer9.update(); - this.pointer10.update(); + if(this.pointer6) { + this.pointer6.update(); + } + if(this.pointer7) { + this.pointer7.update(); + } + if(this.pointer8) { + this.pointer8.update(); + } + if(this.pointer9) { + this.pointer9.update(); + } + if(this.pointer10) { + this.pointer10.update(); + } }; Input.prototype.reset = /** * Reset all of the Pointers and Input states @@ -12775,11 +13898,21 @@ var Phaser; this.pointer3.reset(); this.pointer4.reset(); this.pointer5.reset(); - this.pointer6.reset(); - this.pointer7.reset(); - this.pointer8.reset(); - this.pointer9.reset(); - this.pointer10.reset(); + if(this.pointer6) { + this.pointer6.reset(); + } + if(this.pointer7) { + this.pointer7.reset(); + } + if(this.pointer8) { + this.pointer8.reset(); + } + if(this.pointer9) { + this.pointer9.reset(); + } + if(this.pointer10) { + this.pointer10.reset(); + } this.currentPointers = 0; if(hard == true) { this.onDown = new Phaser.Signal(); @@ -12818,15 +13951,15 @@ var Phaser; this.currentPointers++; } else if(this.pointer5.active == true) { this.currentPointers++; - } else if(this.pointer6.active == true) { + } else if(this.pointer6 && this.pointer6.active == true) { this.currentPointers++; - } else if(this.pointer7.active == true) { + } else if(this.pointer7 && this.pointer7.active == true) { this.currentPointers++; - } else if(this.pointer8.active == true) { + } else if(this.pointer8 && this.pointer8.active == true) { this.currentPointers++; - } else if(this.pointer9.active == true) { + } else if(this.pointer9 && this.pointer9.active == true) { this.currentPointers++; - } else if(this.pointer10.active == true) { + } else if(this.pointer10 && this.pointer10.active == true) { this.currentPointers++; } return this.currentPointers; @@ -12855,15 +13988,15 @@ var Phaser; return this.pointer4.start(event); } else if(this.pointer5.active == false) { return this.pointer5.start(event); - } else if(this.pointer6.active == false) { + } else if(this.pointer6 && this.pointer6.active == false) { return this.pointer6.start(event); - } else if(this.pointer7.active == false) { + } else if(this.pointer7 && this.pointer7.active == false) { return this.pointer7.start(event); - } else if(this.pointer8.active == false) { + } else if(this.pointer8 && this.pointer8.active == false) { return this.pointer8.start(event); - } else if(this.pointer9.active == false) { + } else if(this.pointer9 && this.pointer9.active == false) { return this.pointer9.start(event); - } else if(this.pointer10.active == false) { + } else if(this.pointer10 && this.pointer10.active == false) { return this.pointer10.start(event); } return null; @@ -12886,15 +14019,15 @@ var Phaser; return this.pointer4.move(event); } else if(this.pointer5.active == true && this.pointer5.identifier == event.identifier) { return this.pointer5.move(event); - } else if(this.pointer6.active == true && this.pointer6.identifier == event.identifier) { + } else if(this.pointer6 && this.pointer6.active == true && this.pointer6.identifier == event.identifier) { return this.pointer6.move(event); - } else if(this.pointer7.active == true && this.pointer7.identifier == event.identifier) { + } else if(this.pointer7 && this.pointer7.active == true && this.pointer7.identifier == event.identifier) { return this.pointer7.move(event); - } else if(this.pointer8.active == true && this.pointer8.identifier == event.identifier) { + } else if(this.pointer8 && this.pointer8.active == true && this.pointer8.identifier == event.identifier) { return this.pointer8.move(event); - } else if(this.pointer9.active == true && this.pointer9.identifier == event.identifier) { + } else if(this.pointer9 && this.pointer9.active == true && this.pointer9.identifier == event.identifier) { return this.pointer9.move(event); - } else if(this.pointer10.active == true && this.pointer10.identifier == event.identifier) { + } else if(this.pointer10 && this.pointer10.active == true && this.pointer10.identifier == event.identifier) { return this.pointer10.move(event); } return null; @@ -12917,15 +14050,15 @@ var Phaser; return this.pointer4.stop(event); } else if(this.pointer5.active == true && this.pointer5.identifier == event.identifier) { return this.pointer5.stop(event); - } else if(this.pointer6.active == true && this.pointer6.identifier == event.identifier) { + } else if(this.pointer6 && this.pointer6.active == true && this.pointer6.identifier == event.identifier) { return this.pointer6.stop(event); - } else if(this.pointer7.active == true && this.pointer7.identifier == event.identifier) { + } else if(this.pointer7 && this.pointer7.active == true && this.pointer7.identifier == event.identifier) { return this.pointer7.stop(event); - } else if(this.pointer8.active == true && this.pointer8.identifier == event.identifier) { + } else if(this.pointer8 && this.pointer8.active == true && this.pointer8.identifier == event.identifier) { return this.pointer8.stop(event); - } else if(this.pointer9.active == true && this.pointer9.identifier == event.identifier) { + } else if(this.pointer9 && this.pointer9.active == true && this.pointer9.identifier == event.identifier) { return this.pointer9.stop(event); - } else if(this.pointer10.active == true && this.pointer10.identifier == event.identifier) { + } else if(this.pointer10 && this.pointer10.active == true && this.pointer10.identifier == event.identifier) { return this.pointer10.stop(event); } return null; @@ -12949,15 +14082,15 @@ var Phaser; return this.pointer4; } else if(this.pointer5.active == state) { return this.pointer5; - } else if(this.pointer6.active == state) { + } else if(this.pointer6 && this.pointer6.active == state) { return this.pointer6; - } else if(this.pointer7.active == state) { + } else if(this.pointer7 && this.pointer7.active == state) { return this.pointer7; - } else if(this.pointer8.active == state) { + } else if(this.pointer8 && this.pointer8.active == state) { return this.pointer8; - } else if(this.pointer9.active == state) { + } else if(this.pointer9 && this.pointer9.active == state) { return this.pointer9; - } else if(this.pointer10.active == state) { + } else if(this.pointer10 && this.pointer10.active == state) { return this.pointer10; } return null; @@ -12980,15 +14113,15 @@ var Phaser; return this.pointer4; } else if(this.pointer5.identifier == identifier) { return this.pointer5; - } else if(this.pointer6.identifier == identifier) { + } else if(this.pointer6 && this.pointer6.identifier == identifier) { return this.pointer6; - } else if(this.pointer7.identifier == identifier) { + } else if(this.pointer7 && this.pointer7.identifier == identifier) { return this.pointer7; - } else if(this.pointer8.identifier == identifier) { + } else if(this.pointer8 && this.pointer8.identifier == identifier) { return this.pointer8; - } else if(this.pointer9.identifier == identifier) { + } else if(this.pointer9 && this.pointer9.identifier == identifier) { return this.pointer9; - } else if(this.pointer10.identifier == identifier) { + } else if(this.pointer10 && this.pointer10.identifier == identifier) { return this.pointer10; } return null; @@ -13021,34 +14154,28 @@ var Phaser; this._game.stage.context.fillText('World X: ' + this.getWorldX() + ' World Y: ' + this.getWorldY(), x, y + 28); this._game.stage.context.fillText('Scale X: ' + this.scaleX.toFixed(1) + ' Scale Y: ' + this.scaleY.toFixed(1), x, y + 42); }; + Input.prototype.getDistance = /** + * Get the distance between two Pointer objects + * @method getDistance + * @param {Pointer} pointer1 + * @param {Pointer} pointer2 + **/ + function (pointer1, pointer2) { + return pointer1.position.distance(pointer2.position); + }; + Input.prototype.getAngle = /** + * Get the angle between two Pointer objects + * @method getAngle + * @param {Pointer} pointer1 + * @param {Pointer} pointer2 + **/ + function (pointer1, pointer2) { + return pointer1.position.angle(pointer2.position); + }; return Input; })(); Phaser.Input = Input; - /** - * - * @method calculateDistance - * @param {Finger} finger1 - * @param {Finger} finger2 - **/ - //public calculateDistance(finger1: Finger, finger2: Finger) { - //} - /** - * - * @method calculateAngle - * @param {Finger} finger1 - * @param {Finger} finger2 - **/ - //public calculateAngle(finger1: Finger, finger2: Finger) { - //} - /** - * - * @method checkOverlap - * @param {Finger} finger1 - * @param {Finger} finger2 - **/ - //public checkOverlap(finger1: Finger, finger2: Finger) { - //} - })(Phaser || (Phaser = {})); +})(Phaser || (Phaser = {})); /// /** * Phaser - Keyboard @@ -13822,6 +14949,7 @@ var Phaser; Phaser.Emitter = Emitter; })(Phaser || (Phaser = {})); /// +/// /** * Phaser - GeomSprite * @@ -13888,6 +15016,7 @@ var Phaser; GeomSprite.LINE = 2; GeomSprite.POINT = 3; GeomSprite.RECTANGLE = 4; + GeomSprite.POLYGON = 5; GeomSprite.prototype.loadCircle = /** * Just like Sprite.loadGraphic(), this will load a circle and set its shape to Circle. * @param circle {Circle} Circle geometry define. @@ -13982,6 +15111,20 @@ var Phaser; this.frameBounds.copyFrom(this.rect); return this; }; + GeomSprite.prototype.createPolygon = /** + * Create a polygon object + * @param width {Number} Width of the rectangle + * @param height {Number} Height of the rectangle + * @return {GeomSprite} GeomSprite instance. + */ + function (points) { + if (typeof points === "undefined") { points = []; } + this.refresh(); + this.polygon = new Phaser.Polygon(new Phaser.Vector2(this.x, this.y), points); + this.type = GeomSprite.POLYGON; + //this.frameBounds.copyFrom(this.rect); + return this; + }; GeomSprite.prototype.refresh = /** * Destroy all geom shapes of this sprite. */ @@ -15572,6 +16715,7 @@ var Phaser; /// /// /// +/// /// /// /// @@ -15751,6 +16895,7 @@ var Phaser; this.math = new Phaser.GameMath(this); this.stage = new Phaser.Stage(this, parent, width, height); this.world = new Phaser.World(this, width, height); + this.add = new Phaser.GameObjectFactory(this); this.sound = new Phaser.SoundManager(this); this.cache = new Phaser.Cache(this); this.collision = new Phaser.Collision(this); @@ -15989,126 +17134,6 @@ var Phaser; enumerable: true, configurable: true }); - Game.prototype.createCamera = // Handy Proxy methods - /** - * Create a new camera with specific position and size. - * - * @param x {number} X position of the new camera. - * @param y {number} Y position of the new camera. - * @param width {number} Width of the new camera. - * @param height {number} Height of the new camera. - * @returns {Camera} The newly created camera object. - */ - function (x, y, width, height) { - return this.world.createCamera(x, y, width, height); - }; - Game.prototype.createGeomSprite = /** - * Create a new GeomSprite with specific position. - * - * @param x {number} X position of the new geom sprite. - * @param y {number} Y position of the new geom sprite. - * @returns {GeomSprite} The newly created geom sprite object. - */ - function (x, y) { - return this.world.createGeomSprite(x, y); - }; - Game.prototype.createSprite = /** - * Create a new Sprite with specific position and sprite sheet key. - * - * @param x {number} X position of the new sprite. - * @param y {number} Y position of the new sprite. - * @param key {string} Optional, key for the sprite sheet you want it to use. - * @returns {Sprite} The newly created sprite object. - */ - function (x, y, key) { - if (typeof key === "undefined") { key = ''; } - return this.world.createSprite(x, y, key); - }; - Game.prototype.createDynamicTexture = /** - * Create a new DynamicTexture with specific size. - * - * @param width {number} Width of the texture. - * @param height {number} Height of the texture. - * @returns {DynamicTexture} The newly created dynamic texture object. - */ - function (width, height) { - return this.world.createDynamicTexture(width, height); - }; - Game.prototype.createGroup = /** - * Create a new object container. - * - * @param maxSize {number} Optional, capacity of this group. - * @returns {Group} The newly created group. - */ - function (maxSize) { - if (typeof maxSize === "undefined") { maxSize = 0; } - return this.world.createGroup(maxSize); - }; - Game.prototype.createParticle = /** - * Create a new Particle. - * - * @return {Particle} The newly created particle object. - */ - function () { - return this.world.createParticle(); - }; - Game.prototype.createEmitter = /** - * Create a new Emitter. - * - * @param x {number} Optional, x position of the emitter. - * @param y {number} Optional, y position of the emitter. - * @param size {number} Optional, size of this emitter. - * @return {Emitter} The newly created emitter object. - */ - function (x, y, size) { - if (typeof x === "undefined") { x = 0; } - if (typeof y === "undefined") { y = 0; } - if (typeof size === "undefined") { size = 0; } - return this.world.createEmitter(x, y, size); - }; - Game.prototype.createScrollZone = /** - * Create a new ScrollZone object with image key, position and size. - * - * @param key {string} Key to a image you wish this object to use. - * @param x {number} X position of this object. - * @param y {number} Y position of this object. - * @param width number} Width of this object. - * @param height {number} Height of this object. - * @returns {ScrollZone} The newly created scroll zone object. - */ - function (key, x, y, width, height) { - if (typeof x === "undefined") { x = 0; } - if (typeof y === "undefined") { y = 0; } - if (typeof width === "undefined") { width = 0; } - if (typeof height === "undefined") { height = 0; } - return this.world.createScrollZone(key, x, y, width, height); - }; - Game.prototype.createTilemap = /** - * Create a new Tilemap. - * - * @param key {string} Key for tileset image. - * @param mapData {string} Data of this tilemap. - * @param format {number} Format of map data. (Tilemap.FORMAT_CSV or Tilemap.FORMAT_TILED_JSON) - * @param [resizeWorld] {boolean} resize the world to make same as tilemap? - * @param [tileWidth] {number} width of each tile. - * @param [tileHeight] {number} height of each tile. - * @return {Tilemap} The newly created tilemap object. - */ - function (key, mapData, format, resizeWorld, tileWidth, tileHeight) { - if (typeof resizeWorld === "undefined") { resizeWorld = true; } - if (typeof tileWidth === "undefined") { tileWidth = 0; } - if (typeof tileHeight === "undefined") { tileHeight = 0; } - return this.world.createTilemap(key, mapData, format, resizeWorld, tileWidth, tileHeight); - }; - Game.prototype.createTween = /** - * Create a tween object for a specific object. - * - * @param obj Object you wish the tween will affect. - * @return {Phaser.Tween} The newly created tween object. - */ - function (obj) { - return this.tweens.create(obj); - }; Game.prototype.collide = /** * Checks for overlaps between two objects using the world QuadTree. Can be GameObject vs. GameObject, GameObject vs. Group or Group vs. Group. * Note: Does not take the objects scrollFactor into account. All overlaps are check in world space. @@ -16302,6 +17327,7 @@ var Phaser; */ function State(game) { this.game = game; + this.add = game.add; this.camera = game.camera; this.cache = game.cache; this.collision = game.collision; @@ -16348,126 +17374,6 @@ var Phaser; */ function () { }; - State.prototype.createCamera = // Handy Proxy methods - /** - * Create a new camera with specific position and size. - * - * @param x {number} X position of the new camera. - * @param y {number} Y position of the new camera. - * @param width {number} Width of the new camera. - * @param height {number} Height of the new camera. - * @returns {Camera} The newly created camera object. - */ - function (x, y, width, height) { - return this.game.world.createCamera(x, y, width, height); - }; - State.prototype.createGeomSprite = /** - * Create a new GeomSprite with specific position. - * - * @param x {number} X position of the new geom sprite. - * @param y {number} Y position of the new geom sprite. - * @returns {GeomSprite} The newly created geom sprite object. - */ - function (x, y) { - return this.world.createGeomSprite(x, y); - }; - State.prototype.createSprite = /** - * Create a new Sprite with specific position and sprite sheet key. - * - * @param x {number} X position of the new sprite. - * @param y {number} Y position of the new sprite. - * @param key {string} [optional] key for the sprite sheet you want it to use. - * @returns {Sprite} The newly created sprite object. - */ - function (x, y, key) { - if (typeof key === "undefined") { key = ''; } - return this.game.world.createSprite(x, y, key); - }; - State.prototype.createDynamicTexture = /** - * Create a new DynamicTexture with specific size. - * - * @param width {number} Width of the texture. - * @param height {number} Height of the texture. - * @returns {DynamicTexture} The newly created dynamic texture object. - */ - function (width, height) { - return this.game.world.createDynamicTexture(width, height); - }; - State.prototype.createGroup = /** - * Create a new object container. - * - * @param maxSize {number} [optional] capacity of this group. - * @returns {Group} The newly created group. - */ - function (maxSize) { - if (typeof maxSize === "undefined") { maxSize = 0; } - return this.game.world.createGroup(maxSize); - }; - State.prototype.createParticle = /** - * Create a new Particle. - * - * @return {Particle} The newly created particle object. - */ - function () { - return this.game.world.createParticle(); - }; - State.prototype.createEmitter = /** - * Create a new Emitter. - * - * @param x {number} [optional] x position of the emitter. - * @param y {number} [optional] y position of the emitter. - * @param size {number} [optional] size of this emitter. - * @return {Emitter} The newly created emitter object. - */ - function (x, y, size) { - if (typeof x === "undefined") { x = 0; } - if (typeof y === "undefined") { y = 0; } - if (typeof size === "undefined") { size = 0; } - return this.game.world.createEmitter(x, y, size); - }; - State.prototype.createScrollZone = /** - * Create a new ScrollZone object with image key, position and size. - * - * @param key {string} Key to a image you wish this object to use. - * @param x {number} X position of this object. - * @param y {number} Y position of this object. - * @param width {number} Width of this object. - * @param height {number} Height of this object. - * @returns {ScrollZone} The newly created scroll zone object. - */ - function (key, x, y, width, height) { - if (typeof x === "undefined") { x = 0; } - if (typeof y === "undefined") { y = 0; } - if (typeof width === "undefined") { width = 0; } - if (typeof height === "undefined") { height = 0; } - return this.game.world.createScrollZone(key, x, y, width, height); - }; - State.prototype.createTilemap = /** - * Create a new Tilemap. - * - * @param key {string} Key for tileset image. - * @param mapData {string} Data of this tilemap. - * @param format {number} Format of map data. (Tilemap.FORMAT_CSV or Tilemap.FORMAT_TILED_JSON) - * @param resizeWorld {boolean} [optional] resize the world to make same as tilemap? - * @param tileWidth {number} [optional] width of each tile. - * @param tileHeight number} [optional] height of each tile. - * @return {Tilemap} The newly created tilemap object. - */ - function (key, mapData, format, resizeWorld, tileWidth, tileHeight) { - if (typeof resizeWorld === "undefined") { resizeWorld = true; } - if (typeof tileWidth === "undefined") { tileWidth = 0; } - if (typeof tileHeight === "undefined") { tileHeight = 0; } - return this.game.world.createTilemap(key, mapData, format, resizeWorld, tileWidth, tileHeight); - }; - State.prototype.createTween = /** - * Create a tween object for a specific object. - * - * @param obj Object you wish the tween will affect. - * @return {Phaser.Tween} The newly created tween object. - */ - function (obj) { - return this.game.tweens.create(obj); - }; State.prototype.collide = /** * Call this method to see if one object collids another. * @return {boolean} Whether the given objects or groups collids.