diff --git a/Phaser/Phaser.csproj b/Phaser/Phaser.csproj index f699e104..13fa3315 100644 --- a/Phaser/Phaser.csproj +++ b/Phaser/Phaser.csproj @@ -56,7 +56,9 @@ ../build/phaser.js true - + + + AnimationManager.ts @@ -161,12 +163,88 @@ + + + Transform.ts + + + + TransformUtils.ts + Vec2.ts Motion.ts + + + + Body.ts + + + + Bounds.ts + + + + + Collision.ts + + + Contact.ts + + + + ContactSolver.ts + + + + + IJoint.ts + + + + Joint.ts + + + Manager.ts + + + + + + Box.ts + + + + Circle.ts + + + IShape.ts + + + + Poly.ts + + + + Segment.ts + + + Shape.ts + + + + Triangle.ts + + + + Space.ts + + + ArcadePhysics.ts + Body.ts diff --git a/Phaser/Statics.ts b/Phaser/Statics.ts index 0c907992..ff6d5006 100644 --- a/Phaser/Statics.ts +++ b/Phaser/Statics.ts @@ -25,9 +25,9 @@ module Phaser { static GEOM_POLYGON: number = 4; static BODY_DISABLED: number = 0; - static BODY_DYNAMIC: number = 1; - static BODY_STATIC: number = 2; - static BODY_KINEMATIC: number = 3; + static BODY_STATIC: number = 1; + static BODY_KINETIC: number = 2; + static BODY_DYNAMIC: number = 3; /** * Flag used to allow GameObjects to collide on their left side diff --git a/Phaser/World.ts b/Phaser/World.ts index 6309e499..cbabd992 100644 --- a/Phaser/World.ts +++ b/Phaser/World.ts @@ -75,7 +75,7 @@ module Phaser { } /** - * Called one by Game during the boot process. + * Called once by Game during the boot process. */ public boot() { diff --git a/Phaser/core/Group.ts b/Phaser/core/Group.ts index 812b0791..252962bb 100644 --- a/Phaser/core/Group.ts +++ b/Phaser/core/Group.ts @@ -623,7 +623,6 @@ module Phaser { this.sort(); // What's the z index of the top most child? - var tempZ: number = child.z; var childIndex: number = this._zCounter; this._i = 0; @@ -632,17 +631,21 @@ module Phaser { { this._member = this.members[this._i++]; - if (this._i > childIndex) + if (this._member) { - this._member.z--; - } - else if (this._member.z == child.z) - { - childIndex = this._i; - this._member.z = this._zCounter; + if (this._i > childIndex) + { + this._member.z--; + } + else if (this._member.z == child.z) + { + childIndex = this._i; + this._member.z = this._zCounter; + } } } + // Maybe redundant? this.sort(); return true; diff --git a/Phaser/math/Transform.ts b/Phaser/math/Transform.ts new file mode 100644 index 00000000..4b5f5926 --- /dev/null +++ b/Phaser/math/Transform.ts @@ -0,0 +1,69 @@ +/// +/// + +/** +* Phaser - 2D Transform +* +* A 2D Transform +*/ + +module Phaser { + + export class Transform { + + /** + * Creates a new 2D Transform object. + * @class Transform + * @constructor + * @return {Transform} This object + **/ + constructor(pos: Phaser.Vec2, angle: number) { + + this.t = Phaser.Vec2Utils.clone(pos); + this.c = Math.cos(angle); + this.s = Math.sin(angle); + + } + + public t: Phaser.Vec2; + public c: number; + public s: number; + + public setTo(pos:Phaser.Vec2, angle:number) { + + this.t.copyFrom(pos); + this.c = Math.cos(angle); + this.s = Math.sin(angle); + + return this; + + } + + public setRotation(angle:number) { + + this.c = Math.cos(angle); + this.s = Math.sin(angle); + return this; + + } + + public setPosition(p:Phaser.Vec2) { + + this.t.copyFrom(p); + return this; + + } + + public identity() { + + this.t.setTo(0, 0); + this.c = 1; + this.s = 0; + + return this; + + } + + } + +} \ No newline at end of file diff --git a/Phaser/math/TransformUtils.ts b/Phaser/math/TransformUtils.ts new file mode 100644 index 00000000..583911f2 --- /dev/null +++ b/Phaser/math/TransformUtils.ts @@ -0,0 +1,39 @@ +/// +/// +/// + +/** +* Phaser - TransformUtils +* +* A collection of methods useful for manipulating and performing operations on 2D Transforms. +* +*/ + +module Phaser { + + export class TransformUtils { + + public static rotate(t: Transform, v:Phaser.Vec2, out?: Vec2 = new Vec2):Phaser.Vec2 { + return out.setTo(v.x * t.c - v.y * t.s, v.x * t.s + v.y * t.c); + } + + public static unrotate(t: Transform, v:Phaser.Vec2, out?: Vec2 = new Vec2):Phaser.Vec2 { + return out.setTo(v.x * t.c + v.y * t.s, -v.x * t.s + v.y * t.c); + } + + public static transform(t: Transform, v:Phaser.Vec2, out?: Vec2 = new Vec2):Phaser.Vec2 { + return out.setTo(v.x * t.c - v.y * t.s + t.t.x, v.x * t.s + v.y * t.c + t.t.y); + } + + public static untransform(t: Transform, v:Phaser.Vec2, out?: Vec2 = new Vec2):Phaser.Vec2 { + + var px = v.x - t.t.x; + var py = v.y - t.t.y; + + return out.setTo(px * t.c + py * t.s, -px * t.s + py * t.c); + + } + + } + +} \ No newline at end of file diff --git a/Phaser/math/Vec2.ts b/Phaser/math/Vec2.ts index 637c78b6..de216c88 100644 --- a/Phaser/math/Vec2.ts +++ b/Phaser/math/Vec2.ts @@ -137,6 +137,20 @@ module Phaser { return (this.x * this.x) + (this.y * this.y); } + /** + * Normalize this vector. + * + * @return {Vec2} This for chaining. + */ + public normalize(): Vec2 { + + var inv = (this.x != 0 || this.y != 0) ? 1 / Math.sqrt(this.x * this.x + this.y * this.y) : 0; + this.x *= inv; + this.y *= inv; + return this; + + } + /** * The dot product of two 2D vectors. * @@ -217,6 +231,21 @@ module Phaser { } + /** + * Adds the given vector to this vector then multiplies by the given scalar. + * + * @param {Vec2} a Reference to a source Vec2 object. + * @param {number} scalar + * @return {Vec2} This for chaining. + */ + public multiplyAddByScalar(a: Vec2, scalar: number): Vec2 { + + this.x += a.x * scalar; + this.y += a.y * scalar; + return this; + + } + /** * Divide this vector by the given scalar. * diff --git a/Phaser/math/Vec2Utils.ts b/Phaser/math/Vec2Utils.ts index 3c093fca..ec46d7b4 100644 --- a/Phaser/math/Vec2Utils.ts +++ b/Phaser/math/Vec2Utils.ts @@ -1,5 +1,5 @@ /// -/// +/// /** * Phaser - Vec2Utils @@ -73,13 +73,48 @@ module Phaser { } /** - * Rotate a 2D vector by 90 degrees. + * Adds two 2D vectors together and multiplies the result by the given scalar. + * + * @param {Vec2} a Reference to a source Vec2 object. + * @param {Vec2} b Reference to a source Vec2 object. + * @param {number} s Scaling value. + * @param {Vec2} out The output Vec2 that is the result of the operation. + * @return {Vec2} A Vec2 that is the sum of the two vectors added and multiplied. + */ + static multiplyAdd(a: Vec2, b: Vec2, s: number, out?: Vec2 = new Vec2): Vec2 { + return out.setTo(a.x + b.x * s, a.y + b.y * s); + } + + /** + * Return a negative vector. + * + * @param {Vec2} a Reference to a source Vec2 object. + * @param {Vec2} out The output Vec2 that is the result of the operation. + * @return {Vec2} A Vec2 that is the negative vector. + */ + static negative(a: Vec2, out?: Vec2 = new Vec2): Vec2 { + return out.setTo(-a.x, -a.y); + } + + /** + * Return a perpendicular vector (90 degrees rotation) * * @param {Vec2} a Reference to a source Vec2 object. * @param {Vec2} out The output Vec2 that is the result of the operation. * @return {Vec2} A Vec2 that is the scaled vector. */ static perp(a: Vec2, out?: Vec2 = new Vec2): Vec2 { + return out.setTo(-a.y, a.x); + } + + /** + * Return a perpendicular vector (-90 degrees rotation) + * + * @param {Vec2} a Reference to a source Vec2 object. + * @param {Vec2} out The output Vec2 that is the result of the operation. + * @return {Vec2} A Vec2 that is the scaled vector. + */ + static rperp(a: Vec2, out?: Vec2 = new Vec2): Vec2 { return out.setTo(a.y, -a.x); } @@ -253,12 +288,30 @@ module Phaser { * @param {Vec2} out The output Vec2 that is the result of the operation. * @return {Vec2} A Vec2. */ - static rotate(a: Vec2, b: Vec2, theta: number, out?: Vec2 = new Vec2): Vec2 { + static rotateAroundOrigin(a: Vec2, b: Vec2, theta: number, out?: Vec2 = new Vec2): Vec2 { var x = a.x - b.x; var y = a.y - b.y; return out.setTo(x * Math.cos(theta) - y * Math.sin(theta) + b.x, x * Math.sin(theta) + y * Math.cos(theta) + b.y); } + /** + * Rotate a 2D vector to the given angle (theta). + * + * @param {Vec2} a Reference to a source Vec2 object. + * @param {Vec2} b Reference to a source Vec2 object. + * @param {Number} theta The angle of rotation in radians. + * @param {Vec2} out The output Vec2 that is the result of the operation. + * @return {Vec2} A Vec2. + */ + static rotate(a: Vec2, theta: number, out?: Vec2 = new Vec2): Vec2 { + + var c = Math.cos(theta); + var s = Math.sin(theta); + + return out.setTo(a.x * c - a.y * s, a.x * s + a.y * c); + + } + /** * Clone a 2D vector. * diff --git a/Phaser/physics/ArcadePhysics.ts b/Phaser/physics/ArcadePhysics.ts new file mode 100644 index 00000000..00ae9b18 --- /dev/null +++ b/Phaser/physics/ArcadePhysics.ts @@ -0,0 +1,1121 @@ +/// +/// +/// +/// +/// + +/** +* Phaser - PhysicsManager +* +* Your game only has one PhysicsManager instance and it's responsible for looking after, creating and colliding +* all of the physics objects in the world. +*/ + + +module Phaser.Physics { + + export class ArcadePhysics { + + constructor(game: Game, width: number, height: number) { + + this.game = game; + + this.gravity = new Vec2; + this.drag = new Vec2; + this.bounce = new Vec2; + this.angularDrag = 0; + + this.bounds = new Rectangle(0, 0, width, height); + + this._distance = new Vec2; + this._tangent = new Vec2; + + this.members = new Group(game); + + } + + /** + * Local private reference to Game. + */ + public game: Game; + + /** + * Physics object pool + */ + public members: Group; + + // Temp calculation vars + private _drag: number; + private _delta: number; + private _velocityDelta: number; + private _length: number = 0; + private _distance: Vec2; + private _tangent: Vec2; + private _separatedX: bool; + private _separatedY: bool; + private _overlap: number; + private _maxOverlap: number; + private _obj1Velocity: number; + private _obj2Velocity: number; + private _obj1NewVelocity: number; + private _obj2NewVelocity: number; + private _average: number; + private _quadTree: QuadTree; + private _quadTreeResult: bool; + + public bounds: Rectangle; + + public gravity: Vec2; + public drag: Vec2; + public bounce: Vec2; + public angularDrag: number; + + /** + * The overlap bias is used when calculating hull overlap before separation - change it if you have especially small or large GameObjects + * @type {number} + */ + static OVERLAP_BIAS: number = 4; + + /** + * The overlap bias is used when calculating hull overlap before separation - change it if you have especially small or large GameObjects + * @type {number} + */ + static TILE_OVERLAP: bool = false; + + /** + * @type {number} + */ + public worldDivisions: number = 6; + + + /* + public update() { + + this._length = this._objects.length; + + for (var i = 0; i < this._length; i++) + { + if (this._objects[i]) + { + this._objects[i].preUpdate(); + this.updateMotion(this._objects[i]); + this.collideWorld(this._objects[i]); + + for (var x = 0; x < this._length; x++) + { + if (this._objects[x] && this._objects[x] !== this._objects[i]) + { + //this.collideShapes(this._objects[i], this._objects[x]); + var r = this.NEWseparate(this._objects[i], this._objects[x]); + //console.log('sep', r); + } + } + + } + } + + } + + public render() { + + // iterate through the objects here, updating and colliding + for (var i = 0; i < this._length; i++) + { + if (this._objects[i]) + { + this._objects[i].render(this.game.stage.context); + } + } + + } +*/ + + public updateMotion(body: Phaser.Physics.Body) { + + if (body.type == Types.BODY_DISABLED) + { + return; + } + + this._velocityDelta = (this.computeVelocity(body.angularVelocity, body.gravity.x, body.angularAcceleration, body.angularDrag, body.maxAngular) - body.angularVelocity) / 2; + body.angularVelocity += this._velocityDelta; + body.sprite.transform.rotation += body.angularVelocity * this.game.time.elapsed; + body.angularVelocity += this._velocityDelta; + + this._velocityDelta = (this.computeVelocity(body.velocity.x, body.gravity.x, body.acceleration.x, body.drag.x) - body.velocity.x) / 2; + body.velocity.x += this._velocityDelta; + this._delta = body.velocity.x * this.game.time.elapsed; + body.velocity.x += this._velocityDelta; + //body.position.x += this._delta; + body.sprite.x += this._delta; + + this._velocityDelta = (this.computeVelocity(body.velocity.y, body.gravity.y, body.acceleration.y, body.drag.y) - body.velocity.y) / 2; + body.velocity.y += this._velocityDelta; + this._delta = body.velocity.y * this.game.time.elapsed; + body.velocity.y += this._velocityDelta; + //body.position.y += this._delta; + body.sprite.y += this._delta; + + } + + /** + * A tween-like function that takes a starting velocity and some other factors and returns an altered velocity. + * + * @param {number} Velocity Any component of velocity (e.g. 20). + * @param {number} Acceleration Rate at which the velocity is changing. + * @param {number} Drag Really kind of a deceleration, this is how much the velocity changes if Acceleration is not set. + * @param {number} Max An absolute value cap for the velocity. + * + * @return {number} The altered Velocity value. + */ + public computeVelocity(velocity: number, gravity: number = 0, acceleration: number = 0, drag: number = 0, max: number = 10000): number { + + if (acceleration !== 0) + { + velocity += (acceleration + gravity) * this.game.time.elapsed; + } + else if (drag !== 0) + { + this._drag = drag * this.game.time.elapsed; + + if (velocity - this._drag > 0) + { + velocity = velocity - this._drag; + } + else if (velocity + this._drag < 0) + { + velocity += this._drag; + } + else + { + velocity = 0; + } + + velocity += gravity; + } + + if ((velocity != 0) && (max != 10000)) + { + if (velocity > max) + { + velocity = max; + } + else if (velocity < -max) + { + velocity = -max; + } + } + + return velocity; + + } + + /** + * The core Collision separation method. + * @param body1 The first Physics.Body to separate + * @param body2 The second Physics.Body to separate + * @returns {boolean} Returns true if the bodies were separated, otherwise false. + */ + public separate(body1: Body, body2: Body): bool { + + this._separatedX = this.separateBodyX(body1, body2); + this._separatedY = this.separateBodyY(body1, body2); + + return this._separatedX || this._separatedY; + + } + + public checkHullIntersection(body1: Body, body2:Body): bool { + return ((body1.hullX + body1.hullWidth > body2.hullX) && (body1.hullX < body2.hullX + body2.hullWidth) && (body1.hullY + body1.hullHeight > body2.hullY) && (body1.hullY < body2.hullY + body2.hullHeight)); + } + + /** + * 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 separateBodyX(body1: Body, body2: Body): bool { + + // Can't separate two disabled or static objects + if ((body1.type == Types.BODY_DISABLED || body1.type == Types.BODY_STATIC) && (body2.type == Types.BODY_DISABLED || body2.type == Types.BODY_STATIC)) + { + return false; + } + + // First, get the two object deltas + this._overlap = 0; + + if (body1.deltaX != body2.deltaX) + { + if (RectangleUtils.intersects(body1.bounds, body2.bounds)) + { + this._maxOverlap = body1.deltaXAbs + body2.deltaXAbs + PhysicsManager.OVERLAP_BIAS; + + // If they did overlap (and can), figure out by how much and flip the corresponding flags + if (body1.deltaX > body2.deltaX) + { + this._overlap = body1.bounds.right - body2.bounds.x; + + if ((this._overlap > this._maxOverlap) || !(body1.allowCollisions & Types.RIGHT) || !(body2.allowCollisions & Types.LEFT)) + { + this._overlap = 0; + } + else + { + body1.touching |= Types.RIGHT; + body2.touching |= Types.LEFT; + } + } + else if (body1.deltaX < body2.deltaX) + { + this._overlap = body1.bounds.x - body2.bounds.width - body2.bounds.x; + + if ((-this._overlap > this._maxOverlap) || !(body1.allowCollisions & Types.LEFT) || !(body2.allowCollisions & Types.RIGHT)) + { + this._overlap = 0; + } + else + { + body1.touching |= Types.LEFT; + body2.touching |= Types.RIGHT; + } + } + } + } + + // Then adjust their positions and velocities accordingly (if there was any overlap) + if (this._overlap != 0) + { + this._obj1Velocity = body1.velocity.x; + this._obj2Velocity = body2.velocity.x; + + /** + * Dynamic = gives and receives impacts + * Static = gives but doesn't receive impacts, cannot be moved by physics + * Kinematic = gives impacts, but never receives, can be moved by physics + */ + + // 2 dynamic bodies will exchange velocities + if (body1.type == Types.BODY_DYNAMIC && body2.type == Types.BODY_DYNAMIC) + { + this._overlap *= 0.5; + body1.position.x = body1.position.x - this._overlap; + body2.position.x += this._overlap; + + this._obj1NewVelocity = Math.sqrt((this._obj2Velocity * this._obj2Velocity * body2.mass) / body1.mass) * ((this._obj2Velocity > 0) ? 1 : -1); + this._obj2NewVelocity = Math.sqrt((this._obj1Velocity * this._obj1Velocity * body1.mass) / body2.mass) * ((this._obj1Velocity > 0) ? 1 : -1); + this._average = (this._obj1NewVelocity + this._obj2NewVelocity) * 0.5; + this._obj1NewVelocity -= this._average; + this._obj2NewVelocity -= this._average; + body1.velocity.x = this._average + this._obj1NewVelocity * body1.bounce.x; + body2.velocity.x = this._average + this._obj2NewVelocity * body2.bounce.x; + } + else if (body2.type != Types.BODY_DYNAMIC) + { + // Body 2 is Static or Kinematic + this._overlap *= 2; + body1.position.x -= this._overlap; + body1.velocity.x = this._obj2Velocity - this._obj1Velocity * body1.bounce.x; + } + else if (body1.type != Types.BODY_DYNAMIC) + { + // Body 1 is Static or Kinematic + this._overlap *= 2; + body2.position.x += this._overlap; + body2.velocity.x = this._obj1Velocity - this._obj2Velocity * body2.bounce.x; + } + + 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 separateBodyY(body1: Body, body2: Body): bool { + + // Can't separate two immovable objects + if ((body1.type == Types.BODY_DISABLED || body1.type == Types.BODY_STATIC) && (body2.type == Types.BODY_DISABLED || body2.type == Types.BODY_STATIC)) + { + return false; + } + + // First, get the two object deltas + this._overlap = 0; + + if (body1.deltaY != body2.deltaY) + { + if (RectangleUtils.intersects(body1.bounds, body2.bounds)) + { + // This is the only place to use the DeltaAbs values + this._maxOverlap = body1.deltaYAbs + body2.deltaYAbs + PhysicsManager.OVERLAP_BIAS; + + // If they did overlap (and can), figure out by how much and flip the corresponding flags + if (body1.deltaY > body2.deltaY) + { + this._overlap = body1.bounds.bottom - body2.bounds.y; + + if ((this._overlap > this._maxOverlap) || !(body1.allowCollisions & Types.DOWN) || !(body2.allowCollisions & Types.UP)) + { + this._overlap = 0; + } + else + { + body1.touching |= Types.DOWN; + body2.touching |= Types.UP; + } + } + else if (body1.deltaY < body2.deltaY) + { + this._overlap = body1.bounds.y - body2.bounds.height - body2.bounds.y; + + if ((-this._overlap > this._maxOverlap) || !(body1.allowCollisions & Types.UP) || !(body2.allowCollisions & Types.DOWN)) + { + this._overlap = 0; + } + else + { + body1.touching |= Types.UP; + body2.touching |= Types.DOWN; + } + } + } + } + + // Then adjust their positions and velocities accordingly (if there was any overlap) + if (this._overlap != 0) + { + this._obj1Velocity = body1.velocity.y; + this._obj2Velocity = body2.velocity.y; + + /** + * Dynamic = gives and receives impacts + * Static = gives but doesn't receive impacts, cannot be moved by physics + * Kinematic = gives impacts, but never receives, can be moved by physics + */ + + if (body1.type == Types.BODY_DYNAMIC && body2.type == Types.BODY_DYNAMIC) + { + this._overlap *= 0.5; + body1.position.y = body1.position.y - this._overlap; + body2.position.y += this._overlap; + + this._obj1NewVelocity = Math.sqrt((this._obj2Velocity * this._obj2Velocity * body2.mass) / body1.mass) * ((this._obj2Velocity > 0) ? 1 : -1); + this._obj2NewVelocity = Math.sqrt((this._obj1Velocity * this._obj1Velocity * body1.mass) / body2.mass) * ((this._obj1Velocity > 0) ? 1 : -1); + var average: number = (this._obj1NewVelocity + this._obj2NewVelocity) * 0.5; + this._obj1NewVelocity -= average; + this._obj2NewVelocity -= average; + body1.velocity.y = average + this._obj1NewVelocity * body1.bounce.y; + body2.velocity.y = average + this._obj2NewVelocity * body2.bounce.y; + } + else if (body2.type != Types.BODY_DYNAMIC) + { + this._overlap *= 2; + body1.position.y -= this._overlap; + body1.velocity.y = this._obj2Velocity - this._obj1Velocity * body1.bounce.y; + // This is special case code that handles things like horizontal moving platforms you can ride + //if (body2.parent.active && body2.moves && (body1.deltaY > body2.deltaY)) + if (body2.sprite.active && (body1.deltaY > body2.deltaY)) + { + body1.position.x += body2.position.x - body2.oldPosition.x; + } + } + else if (body1.type != Types.BODY_DYNAMIC) + { + this._overlap *= 2; + body2.position.y += this._overlap; + body2.velocity.y = this._obj1Velocity - this._obj2Velocity * body2.bounce.y; + // This is special case code that handles things like horizontal moving platforms you can ride + //if (object1.active && body1.moves && (body1.deltaY < body2.deltaY)) + if (body1.sprite.active && (body1.deltaY < body2.deltaY)) + { + body2.position.x += body1.position.x - body1.oldPosition.x; + } + } + + return true; + } + else + { + return false; + } + } + + + + + + + + /* + private TILEseparate(shapeA: IPhysicsShape, shapeB: IPhysicsShape, distance: Vec2, tangent: Vec2) { + + if (tangent.x == 1) + { + console.log('1 The left side of ShapeA hit the right side of ShapeB', Math.floor(distance.x)); + shapeA.physics.touching |= Phaser.Types.LEFT; + shapeB.physics.touching |= Phaser.Types.RIGHT; + } + else if (tangent.x == -1) + { + console.log('2 The right side of ShapeA hit the left side of ShapeB', Math.floor(distance.x)); + shapeA.physics.touching |= Phaser.Types.RIGHT; + shapeB.physics.touching |= Phaser.Types.LEFT; + } + + if (tangent.y == 1) + { + console.log('3 The top of ShapeA hit the bottom of ShapeB', Math.floor(distance.y)); + shapeA.physics.touching |= Phaser.Types.UP; + shapeB.physics.touching |= Phaser.Types.DOWN; + } + else if (tangent.y == -1) + { + console.log('4 The bottom of ShapeA hit the top of ShapeB', Math.floor(distance.y)); + shapeA.physics.touching |= Phaser.Types.DOWN; + shapeB.physics.touching |= Phaser.Types.UP; + } + + + // only apply collision response forces if the object is travelling into, and not out of, the collision + var dot = Vec2Utils.dot(shapeA.physics.velocity, tangent); + + if (dot < 0) + { + console.log('in to', dot); + + // Apply horizontal bounce + if (shapeA.physics.bounce.x > 0) + { + shapeA.physics.velocity.x *= -(shapeA.physics.bounce.x); + } + else + { + shapeA.physics.velocity.x = 0; + } + // Apply horizontal bounce + if (shapeA.physics.bounce.y > 0) + { + shapeA.physics.velocity.y *= -(shapeA.physics.bounce.y); + } + else + { + shapeA.physics.velocity.y = 0; + } + } + else + { + console.log('out of', dot); + } + + shapeA.position.x += Math.floor(distance.x); + //shapeA.bounds.x += Math.floor(distance.x); + + shapeA.position.y += Math.floor(distance.y); + //shapeA.bounds.y += distance.y; + + console.log('------------------------------------------------'); + + } + + private collideWorld(shape:IPhysicsShape) { + + // Collide on the x-axis + this._distance.x = shape.world.bounds.x - (shape.position.x - shape.bounds.halfWidth); + + if (0 < this._distance.x) + { + // Hit Left + this._tangent.setTo(1, 0); + this.separateXWall(shape, this._distance, this._tangent); + } + else + { + this._distance.x = (shape.position.x + shape.bounds.halfWidth) - shape.world.bounds.right; + + if (0 < this._distance.x) + { + // Hit Right + this._tangent.setTo(-1, 0); + this._distance.reverse(); + this.separateXWall(shape, this._distance, this._tangent); + } + } + + // Collide on the y-axis + this._distance.y = shape.world.bounds.y - (shape.position.y - shape.bounds.halfHeight); + + if (0 < this._distance.y) + { + // Hit Top + this._tangent.setTo(0, 1); + this.separateYWall(shape, this._distance, this._tangent); + } + else + { + this._distance.y = (shape.position.y + shape.bounds.halfHeight) - shape.world.bounds.bottom; + + if (0 < this._distance.y) + { + // Hit Bottom + this._tangent.setTo(0, -1); + this._distance.reverse(); + this.separateYWall(shape, this._distance, this._tangent); + } + } + + } + + private separateX(shapeA: IPhysicsShape, shapeB: IPhysicsShape, distance: Vec2, tangent: Vec2) { + + if (tangent.x == 1) + { + console.log('The left side of ShapeA hit the right side of ShapeB', distance.x); + shapeA.physics.touching |= Phaser.Types.LEFT; + shapeB.physics.touching |= Phaser.Types.RIGHT; + } + else + { + console.log('The right side of ShapeA hit the left side of ShapeB', distance.x); + shapeA.physics.touching |= Phaser.Types.RIGHT; + shapeB.physics.touching |= Phaser.Types.LEFT; + } + + // collision edges + //shapeA.oH = tangent.x; + + // only apply collision response forces if the object is travelling into, and not out of, the collision + if (Vec2Utils.dot(shapeA.physics.velocity, tangent) < 0) + { + // Apply horizontal bounce + if (shapeA.physics.bounce.x > 0) + { + shapeA.physics.velocity.x *= -(shapeA.physics.bounce.x); + } + else + { + shapeA.physics.velocity.x = 0; + } + } + + shapeA.position.x += distance.x; + shapeA.bounds.x += distance.x; + + } + + private separateY(shapeA: IPhysicsShape, shapeB: IPhysicsShape, distance: Vec2, tangent: Vec2) { + + if (tangent.y == 1) + { + console.log('The top of ShapeA hit the bottom of ShapeB', distance.y); + shapeA.physics.touching |= Phaser.Types.UP; + shapeB.physics.touching |= Phaser.Types.DOWN; + } + else + { + console.log('The bottom of ShapeA hit the top of ShapeB', distance.y); + shapeA.physics.touching |= Phaser.Types.DOWN; + shapeB.physics.touching |= Phaser.Types.UP; + } + + // collision edges + //shapeA.oV = tangent.y; + + // only apply collision response forces if the object is travelling into, and not out of, the collision + if (Vec2Utils.dot(shapeA.physics.velocity, tangent) < 0) + { + // Apply horizontal bounce + if (shapeA.physics.bounce.y > 0) + { + shapeA.physics.velocity.y *= -(shapeA.physics.bounce.y); + } + else + { + shapeA.physics.velocity.y = 0; + } + } + + shapeA.position.y += distance.y; + shapeA.bounds.y += distance.y; + + } + + private separateXWall(shapeA: IPhysicsShape, distance: Vec2, tangent: Vec2) { + + if (tangent.x == 1) + { + console.log('The left side of ShapeA hit the right side of ShapeB', distance.x); + shapeA.physics.touching |= Phaser.Types.LEFT; + } + else + { + console.log('The right side of ShapeA hit the left side of ShapeB', distance.x); + shapeA.physics.touching |= Phaser.Types.RIGHT; + } + + // collision edges + //shapeA.oH = tangent.x; + + // only apply collision response forces if the object is travelling into, and not out of, the collision + if (Vec2Utils.dot(shapeA.physics.velocity, tangent) < 0) + { + // Apply horizontal bounce + if (shapeA.physics.bounce.x > 0) + { + shapeA.physics.velocity.x *= -(shapeA.physics.bounce.x); + } + else + { + shapeA.physics.velocity.x = 0; + } + } + + shapeA.position.x += distance.x; + + } + + private separateYWall(shapeA: IPhysicsShape, distance: Vec2, tangent: Vec2) { + + if (tangent.y == 1) + { + console.log('The top of ShapeA hit the bottom of ShapeB', distance.y); + shapeA.physics.touching |= Phaser.Types.UP; + } + else + { + console.log('The bottom of ShapeA hit the top of ShapeB', distance.y); + shapeA.physics.touching |= Phaser.Types.DOWN; + } + + // collision edges + //shapeA.oV = tangent.y; + + // only apply collision response forces if the object is travelling into, and not out of, the collision + if (Vec2Utils.dot(shapeA.physics.velocity, tangent) < 0) + { + // Apply horizontal bounce + if (shapeA.physics.bounce.y > 0) + { + shapeA.physics.velocity.y *= -(shapeA.physics.bounce.y); + } + else + { + shapeA.physics.velocity.y = 0; + } + } + + shapeA.position.y += distance.y; + + } + */ + + /** + * Checks for overlaps between two objects using the world QuadTree. Can be Sprite vs. Sprite, Sprite 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 Sprite or Group to check. If null the world.group is used. + * @param object2 The second Sprite or Group to check. + * @param notifyCallback A callback function that is called if the objects overlap. The two objects will be passed to this function in the same order in which you passed them to Collision.overlap. + * @param processCallback A callback function that lets you perform additional checks against the two objects if they overlap. If this is set then notifyCallback will only be called if processCallback returns true. + * @param context The context in which the callbacks will be called + * @returns {boolean} true if the objects overlap, otherwise false. + */ + public overlap(object1 = null, object2 = null, notifyCallback = null, processCallback = null, context = null): bool { + + /* + if (object1 == null) + { + object1 = this.game.world.group; + } + + if (object2 == object1) + { + object2 = null; + } + + QuadTree.divisions = this.worldDivisions; + + this._quadTree = new Phaser.QuadTree(this, this.bounds.x, this.bounds.y, this.bounds.width, this.bounds.height); + + this._quadTree.load(object1, object2, notifyCallback, processCallback, context); + + this._quadTreeResult = this._quadTree.execute(); + + console.log('over', this._quadTreeResult); + + this._quadTree.destroy(); + + this._quadTree = null; + + return this._quadTreeResult; + */ + + return false; + + } + + + + + + + /** + * Collision resolution specifically for GameObjects vs. Tiles. + * @param object The GameObject to separate + * @param tile The Tile to separate + * @returns {boolean} Whether the objects in fact touched and were separated + */ + public separateTile(object:Sprite, x: number, y: number, width: number, height: number, mass: number, collideLeft: bool, collideRight: bool, collideUp: bool, collideDown: bool, separateX: bool, separateY: bool): bool { + + //var separatedX: bool = this.separateTileX(object, x, y, width, height, mass, collideLeft, collideRight, separateX); + //var separatedY: bool = this.separateTileY(object, x, y, width, height, mass, collideUp, collideDown, separateY); + + //return separatedX || separatedY; + + return false; + + } + + /** + * 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 separateTileX(object:Sprite, 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; + 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: Rectangle = new Rectangle(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: number = objDeltaAbs + Collision.OVERLAP_BIAS; + + // If they did overlap (and can), figure out by how much and flip the corresponding flags + if (objDelta > 0) + { + overlap = object.x + object.width - x; + + if ((overlap > maxOverlap) || !(object.allowCollisions & Collision.RIGHT) || collideLeft == false) + { + overlap = 0; + } + else + { + object.touching |= Collision.RIGHT; + } + } + else if (objDelta < 0) + { + overlap = object.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) + { + //console.log(' + 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 separateTileY(object: Sprite, 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 (objDelta != 0) + { + // Check if the Y hulls actually overlap + var objDeltaAbs: number = (objDelta > 0) ? objDelta : -objDelta; + var objBounds: Rectangle = new Rectangle(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)) + { + var maxOverlap: number = objDeltaAbs + Collision.OVERLAP_BIAS; + + // If they did overlap (and can), figure out by how much and flip the corresponding flags + if (objDelta > 0) + { + overlap = object.y + object.height - y; + + if ((overlap > maxOverlap) || !(object.allowCollisions & Collision.DOWN) || collideUp == false) + { + overlap = 0; + } + else + { + object.touching |= Collision.DOWN; + } + } + else if (objDelta < 0) + { + overlap = object.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 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:Sprite, 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: Rectangle = new Rectangle(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 NEWseparateTileY(object: Sprite, 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: Rectangle = new Rectangle(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; + } + } + */ + + } + +} \ No newline at end of file diff --git a/Phaser/physics/advanced/Body.ts b/Phaser/physics/advanced/Body.ts new file mode 100644 index 00000000..a6cdf4c1 --- /dev/null +++ b/Phaser/physics/advanced/Body.ts @@ -0,0 +1,589 @@ +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// + +/** +* Phaser - Advanced Physics - Body +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ + +module Phaser.Physics.Advanced { + + export class Body { + + constructor(sprite: Phaser.Sprite, type: number, x?: number = 0, y?: number = 0) { + + this.id = Phaser.Physics.Advanced.Manager.bodyCounter++; + this.name = 'body' + this.id; + this.type = type; + + if (sprite) + { + this.sprite = sprite; + this.game = sprite.game; + this.position = new Phaser.Vec2(sprite.x, sprite.y); + this.angle = sprite.rotation; + } + else + { + this.position = new Phaser.Vec2(x, y); + this.angle = 0; + } + + this.transform = new Phaser.Transform(this.position, this.angle); + this.centroid = new Phaser.Vec2; + this.velocity = new Phaser.Vec2; + this.force = new Phaser.Vec2; + this.angularVelocity = 0; + this.torque = 0; + this.linearDamping = 0; + this.angularDamping = 0; + this.sleepTime = 0; + this.awaked = false; + + this.shapes = []; + this.joints = []; + this.jointHash = {}; + + this.bounds = new Bounds; + + this.fixedRotation = false; + + this.categoryBits = 0x0001; + this.maskBits = 0xFFFF; + + this.stepCount = 0; + + } + + /** + * Reference to Phaser.Game + */ + public game: Game; + + /** + * Reference to the parent Sprite + */ + public sprite: Phaser.Sprite; + + /** + * The Body ID + */ + public id: number; + + /** + * The Body name + */ + public name: string; + + /** + * The type of Body (disabled, dynamic, static or kinematic) + * Disabled = skips all physics operations / tests (default) + * Dynamic = gives and receives impacts + * Static = gives but doesn't receive impacts, cannot be moved by physics + * Kinematic = gives impacts, but never receives, can be moved by physics + * @type {number} + */ + public type: number; + + public angle: number; + + // Local to world transform + public transform: Phaser.Transform; + + // Local center of mass + public centroid: Phaser.Vec2; + + // World position of centroid + public position: Phaser.Vec2; + + // Velocity + public velocity: Phaser.Vec2; + + // Force + public force: Phaser.Vec2; + + // Angular velocity + public angularVelocity: number; + + // Torque + public torque: number; + + // Linear damping + public linearDamping: number; + + // Angular damping + public angularDamping: number; + + // Sleep time + public sleepTime: number; + + // Awaked + public awaked: bool; + + // Shapes + public shapes: IShape[] = []; + + // Joints + public joints: IJoint[] = []; + public jointHash = {}; + + // Bounds of all shapes + public bounds: Bounds; + + public fixedRotation = false; + public categoryBits = 0x0001; + public maskBits = 0xFFFF; + public stepCount = 0; + public space: Space; + + public duplicate() { + + //console.log('body duplicate called'); + + //var body = new Body(this.type, this.transform.t, this.angle); + + //for (var i = 0; i < this.shapes.length; i++) + //{ + // body.addShape(this.shapes[i].duplicate()); + //} + + //body.resetMassData(); + + //return body; + + } + + public get isDisabled(): bool { + return this.type == Phaser.Types.BODY_DISABLED ? true : false; + } + + public get isStatic(): bool { + return this.type == Phaser.Types.BODY_STATIC ? true : false; + } + + public get isKinetic(): bool { + return this.type == Phaser.Types.BODY_KINETIC ? true : false; + } + + public get isDynamic(): bool { + return this.type == Phaser.Types.BODY_DYNAMIC ? true : false; + } + + public setType(type: number) { + + if (type == this.type) + { + return; + } + + this.force.setTo(0, 0); + this.velocity.setTo(0, 0); + this.torque = 0; + this.angularVelocity = 0; + this.type = type; + + this.awake(true); + + } + + public addShape(shape) { + + // Check not already part of this body + shape.body = this; + + this.shapes.push(shape); + + return shape; + + } + + public removeShape(shape) { + + var index = this.shapes.indexOf(shape); + + if (index != -1) + { + this.shapes.splice(index, 1); + shape.body = undefined; + } + + } + + public mass: number; + public massInverted: number; + public inertia: number; + public inertiaInverted: number; + + private setMass(mass) { + + this.mass = mass; + this.massInverted = mass > 0 ? 1 / mass : 0; + + } + + private setInertia(inertia) { + + this.inertia = inertia; + this.inertiaInverted = inertia > 0 ? 1 / inertia : 0; + + } + + public setTransform(pos, angle) { + + this.transform.setTo(pos, angle); + // inject the transform into this.position + Phaser.TransformUtils.transform(this.transform, this.centroid, this.position); + //this.position.copyFrom(this.transform.transform(this.centroid)); + this.angle = angle; + + } + + public syncTransform() { + + this.transform.setRotation(this.angle); + + //var rotc: Phaser.Vec2 = this.transform.rotate(this.centroid); + //var sub: Phaser.Vec2 = Phaser.Vec2Utils.subtract(this.position, rotc); + //this.transform.setPosition(sub); + + // this.transform.setPosition(vec2.sub(this.position, this.transform.rotate(this.centroid))); + //Phaser.Vec2Utils.subtract(this.position, this.transform.rotate(this.centroid), this.transform.t); + + // OPTIMISE: Creating new vector + Phaser.Vec2Utils.subtract(this.position, Phaser.TransformUtils.rotate(this.transform, this.centroid), this.transform.t); + + } + + public getWorldPoint(p:Phaser.Vec2) { + // OPTIMISE: Creating new vector + return Phaser.TransformUtils.transform(this.transform, p); + } + + public getWorldVector(v) { + // OPTIMISE: Creating new vector + return Phaser.TransformUtils.rotate(this.transform, v); + } + + public getLocalPoint(p) { + // OPTIMISE: Creating new vector + return Phaser.TransformUtils.untransform(this.transform, p); + } + + public getLocalVector(v) { + // OPTIMISE: Creating new vector + return Phaser.TransformUtils.unrotate(this.transform, v); + } + + public setFixedRotation(flag) { + this.fixedRotation = flag; + this.resetMassData(); + } + + public resetMassData() { + + this.centroid.setTo(0, 0); + this.mass = 0; + this.massInverted = 0; + this.inertia = 0; + this.inertiaInverted = 0; + + if (this.isDynamic == false) + { + Phaser.TransformUtils.transform(this.transform, this.centroid, this.position); + //this.position.copyFrom(this.transform.transform(this.centroid)); + return; + } + + var totalMassCentroid = new Phaser.Vec2(0, 0); + var totalMass = 0; + var totalInertia = 0; + + for (var i = 0; i < this.shapes.length; i++) + { + var shape = this.shapes[i]; + var centroid = shape.centroid(); + var mass = shape.area() * shape.density; + var inertia = shape.inertia(mass); + + //console.log('rmd', centroid, shape); + + totalMassCentroid.multiplyAddByScalar(centroid, mass); + totalMass += mass; + totalInertia += inertia; + } + + //this.centroid.copy(vec2.scale(totalMassCentroid, 1 / totalMass)); + Phaser.Vec2Utils.scale(totalMassCentroid, 1 / totalMass, this.centroid); + + this.setMass(totalMass); + + if (!this.fixedRotation) + { + //this.setInertia(totalInertia - totalMass * vec2.dot(this.centroid, this.centroid)); + this.setInertia(totalInertia - totalMass * Phaser.Vec2Utils.dot(this.centroid, this.centroid)); + } + + //console.log("mass = " + this.m + " inertia = " + this.i); + + // Move center of mass + var oldPosition: Phaser.Vec2 = Phaser.Vec2Utils.clone(this.position); + //this.position.copyFrom(this.transform.transform(this.centroid)); + Phaser.TransformUtils.transform(this.transform, this.centroid, this.position); + + // Update center of mass velocity + + //this.velocity.mad(vec2.perp(vec2.sub(this.position, old_p)), this.angularVelocity); + oldPosition.subtract(this.position); + this.velocity.multiplyAddByScalar(Phaser.Vec2Utils.perp(oldPosition, oldPosition), this.angularVelocity); + + } + + public resetJointAnchors() { + + for (var i = 0; i < this.joints.length; i++) + { + var joint = this.joints[i]; + + if (!joint) + { + continue; + } + + var anchor1 = joint.getWorldAnchor1(); + var anchor2 = joint.getWorldAnchor2(); + + joint.setWorldAnchor1(anchor1); + joint.setWorldAnchor2(anchor2); + } + } + + public cacheData() { + + //console.log('Body cacheData', this.name, 'len', this.shapes.length); + + this.bounds.clear(); + + for (var i = 0; i < this.shapes.length; i++) + { + var shape = this.shapes[i]; + shape.cacheData(this.transform); + this.bounds.addBounds(shape.bounds); + } + + } + + private _tempVec2: Phaser.Vec2 = new Phaser.Vec2; + + public updateVelocity(gravity, dt, damping) { + + // this.velocity = vec2.mad(this.velocity, vec2.mad(gravity, this.force, this.massInverted), dt); + Phaser.Vec2Utils.multiplyAdd(gravity, this.force, this.massInverted, this._tempVec2); + Phaser.Vec2Utils.multiplyAdd(this.velocity, this._tempVec2, dt, this.velocity); + + this.angularVelocity = this.angularVelocity + this.torque * this.inertiaInverted * dt; + + // Apply damping. + // ODE: dv/dt + c * v = 0 + // Solution: v(t) = v0 * exp(-c * t) + // Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v * exp(-c * dt) + // v2 = exp(-c * dt) * v1 + // Taylor expansion: + // v2 = (1.0f - c * dt) * v1 + this.velocity.scale(this.game.math.clamp(1 - dt * (damping + this.linearDamping), 0, 1)); + this.angularVelocity *= this.game.math.clamp(1 - dt * (damping + this.angularDamping), 0, 1); + + this.force.setTo(0, 0); + this.torque = 0; + + } + + public updatePosition(dt) { + + //console.log('body update pos', this.position.y); + //console.log('pre add temp', this._tempVec2.y); + + //this.position.addself(vec2.scale(this.velocity, dt)); + this.position.add(Phaser.Vec2Utils.scale(this.velocity, dt, this._tempVec2)); + + //console.log('post add temp', this._tempVec2.y); + //console.log('post add', this.position.y); + + this.angle += this.angularVelocity * dt; + + } + + public resetForce() { + this.force.setTo(0, 0); + this.torque = 0; + } + + public applyForce(force, p) { + + if (this.isDynamic == false) + { + return; + } + + if (this.isAwake == false) + { + this.awake(true); + } + + this.force.add(force); + + // this.f.addself(force); + // this.torque += vec2.cross(vec2.sub(p, this.p), force); + + Phaser.Vec2Utils.subtract(p, this.position, this._tempVec2); + this.torque += Phaser.Vec2Utils.cross(this._tempVec2, force); + + } + + public applyForceToCenter(force) { + + if (this.isDynamic == false) + { + return; + } + + if (this.isAwake == false) + { + this.awake(true); + } + + this.force.add(force); + + } + + public applyTorque(torque) { + + if (this.isDynamic == false) + { + return; + } + + if (this.isAwake == false) + { + this.awake(true); + } + + this.torque += torque; + + } + + public applyLinearImpulse(impulse, p) { + + if (this.isDynamic == false) + { + return; + } + + if (this.isAwake == false) + { + this.awake(true); + } + + this.velocity.multiplyAddByScalar(impulse, this.massInverted); + + // this.angularVelocity += vec2.cross(vec2.sub(p, this.position), impulse) * this.inertiaInverted; + + Phaser.Vec2Utils.subtract(p, this.position, this._tempVec2); + this.angularVelocity += Phaser.Vec2Utils.cross(this._tempVec2, impulse) * this.inertiaInverted; + + } + + public applyAngularImpulse(impulse: number) { + + if (this.isDynamic == false) + { + return; + } + + if (this.isAwake == false) + { + this.awake(true); + } + + this.angularVelocity += impulse * this.inertiaInverted; + + } + + public kineticEnergy() { + + var vsq = this.velocity.dot(this.velocity); + var wsq = this.angularVelocity * this.angularVelocity; + + return 0.5 * (this.mass * vsq + this.inertia * wsq); + + } + + public get isAwake(): bool { + return this.awaked; + } + + public awake(flag) { + + this.awaked = flag; + + if (flag) + { + this.sleepTime = 0; + } + else + { + this.velocity.setTo(0, 0); + this.angularVelocity = 0; + this.force.setTo(0, 0); + this.torque = 0; + } + + } + + public isCollidable(other) { + + if (this == other) + { + return false; + } + + if (this.isDynamic == false && other.isDynamic == false) + { + return false; + } + + if (!(this.maskBits & other.categoryBits) || !(other.maskBits & this.categoryBits)) + { + return false; + } + + for (var i = 0; i < this.joints.length; i++) + { + var joint = this.joints[i]; + + if (!joint) + { + continue; + } + + if (!joint.collideConnected && other.jointHash[joint.id] != undefined) + { + return false; + } + } + + return true; + + } + + } + +} \ No newline at end of file diff --git a/Phaser/physics/advanced/Bounds.ts b/Phaser/physics/advanced/Bounds.ts new file mode 100644 index 00000000..6e7ce285 --- /dev/null +++ b/Phaser/physics/advanced/Bounds.ts @@ -0,0 +1,173 @@ +/// +/// +/// + +/** +* Phaser - 2D AABB +* +* A 2D AABB object +*/ + +module Phaser.Physics.Advanced { + + export class Bounds { + + /** + * Creates a new 2D AABB object. + * @class Bounds + * @constructor + * @return {Bounds} This object + **/ + constructor(mins?: Phaser.Vec2 = null, maxs?: Phaser.Vec2 = null) { + + if (mins) + { + this.mins = Phaser.Vec2Utils.clone(mins); + } + else + { + this.mins = new Phaser.Vec2(999999, 999999); + } + + if (maxs) + { + this.maxs = Phaser.Vec2Utils.clone(maxs); + } + else + { + this.maxs = new Phaser.Vec2(999999, 999999); + } + + } + + public mins: Phaser.Vec2; + public maxs: Phaser.Vec2; + + public toString() { + return ["mins:", this.mins.toString(), "maxs:", this.maxs.toString()].join(" "); + } + + public setTo(mins: Phaser.Vec2, maxs: Phaser.Vec2) { + + this.mins.setTo(mins.x, mins.y); + this.maxs.setTo(maxs.x, maxs.y); + + } + + public copy(b: Bounds): Bounds { + + this.mins.copyFrom(b.mins); + this.maxs.copyFrom(b.maxs); + + return this; + } + + public clear(): Bounds { + + this.mins.setTo(999999, 999999); + this.maxs.setTo(-999999, -999999); + + return this; + + } + + public isEmpty(): bool { + return (this.mins.x > this.maxs.x || this.mins.y > this.maxs.y); + } + + /* + public getCenter() { + return vec2.scale(vec2.add(this.mins, this.maxs), 0.5); + } + + public getExtent() { + return vec2.scale(vec2.sub(this.maxs, this.mins), 0.5); + } + */ + + public getPerimeter(): number { + return (this.maxs.x - this.mins.x + this.maxs.y - this.mins.y) * 2; + } + + public addPoint(p: Phaser.Vec2): Bounds { + + if (this.mins.x > p.x) this.mins.x = p.x; + if (this.maxs.x < p.x) this.maxs.x = p.x; + if (this.mins.y > p.y) this.mins.y = p.y; + if (this.maxs.y < p.y) this.maxs.y = p.y; + + return this; + } + + public addBounds(b: Bounds): Bounds { + + if (this.mins.x > b.mins.x) this.mins.x = b.mins.x; + if (this.maxs.x < b.maxs.x) this.maxs.x = b.maxs.x; + if (this.mins.y > b.mins.y) this.mins.y = b.mins.y; + if (this.maxs.y < b.maxs.y) this.maxs.y = b.maxs.y; + + return this; + } + + public addBounds2(mins, maxs) { + if (this.mins.x > mins.x) this.mins.x = mins.x; + if (this.maxs.x < maxs.x) this.maxs.x = maxs.x; + if (this.mins.y > mins.y) this.mins.y = mins.y; + if (this.maxs.y < maxs.y) this.maxs.y = maxs.y; + return this; + } + + public addExtents(center: Phaser.Vec2, extent_x: number, extent_y: number): Bounds { + + if (this.mins.x > center.x - extent_x) this.mins.x = center.x - extent_x; + if (this.maxs.x < center.x + extent_x) this.maxs.x = center.x + extent_x; + if (this.mins.y > center.y - extent_y) this.mins.y = center.y - extent_y; + if (this.maxs.y < center.y + extent_y) this.maxs.y = center.y + extent_y; + + return this; + + } + + public expand(ax: number, ay: number): Bounds { + + this.mins.x -= ax; + this.mins.y -= ay; + this.maxs.x += ax; + this.maxs.y += ay; + + return this; + + } + + public containPoint(p: Phaser.Vec2): bool { + + if (p.x < this.mins.x || p.x > this.maxs.x || p.y < this.mins.y || p.y > this.maxs.y) + { + return false; + } + + return true; + + } + + public intersectsBounds(b: Bounds): bool { + + if (this.mins.x > b.maxs.x || this.maxs.x < b.mins.x || this.mins.y > b.maxs.y || this.maxs.y < b.mins.y) + { + return false; + } + + return true; + } + + public static expand(b: Bounds, ax, ay) { + + var b = new Bounds(b.mins, b.maxs); + b.expand(ax, ay); + return b; + + } + + } + +} \ No newline at end of file diff --git a/Phaser/physics/advanced/Collision.ts b/Phaser/physics/advanced/Collision.ts new file mode 100644 index 00000000..419a7d29 --- /dev/null +++ b/Phaser/physics/advanced/Collision.ts @@ -0,0 +1,558 @@ +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// + +/** +* Phaser - Advanced Physics - Collision Handlers +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ + +module Phaser.Physics.Advanced { + + export class Collision { + + constructor() { + } + + public collide(a, b, contacts: Contact[]) { + + // Circle (a is the circle) + if (a.type == Manager.SHAPE_TYPE_CIRCLE) + { + if (b.type == Manager.SHAPE_TYPE_CIRCLE) + { + return this.circle2Circle(a, b, contacts); + } + else if (b.type == Manager.SHAPE_TYPE_SEGMENT) + { + return this.circle2Segment(a, b, contacts); + } + else if (b.type == Manager.SHAPE_TYPE_POLY) + { + return this.circle2Poly(a, b, contacts); + } + } + + // Segment (a is the segment) + if (a.type == Manager.SHAPE_TYPE_SEGMENT) + { + if (b.type == Manager.SHAPE_TYPE_CIRCLE) + { + return this.circle2Segment(b, a, contacts); + } + else if (b.type == Manager.SHAPE_TYPE_SEGMENT) + { + return this.segment2Segment(a, b, contacts); + } + else if (b.type == Manager.SHAPE_TYPE_POLY) + { + return this.segment2Poly(a, b, contacts); + } + } + + // Poly (a is the poly) + if (a.type == Manager.SHAPE_TYPE_POLY) + { + if (b.type == Manager.SHAPE_TYPE_CIRCLE) + { + return this.circle2Poly(b, a, contacts); + } + else if (b.type == Manager.SHAPE_TYPE_SEGMENT) + { + return this.segment2Poly(b, a, contacts); + } + else if (b.type == Manager.SHAPE_TYPE_POLY) + { + return this.poly2Poly(a, b, contacts); + } + } + + } + + private _circle2Circle(c1, r1, c2, r2, contactArr) { + + var rmax = r1 + r2; + + var t: Phaser.Vec2 = new Phaser.Vec2; + //var t = vec2.sub(c2, c1); + Phaser.Vec2Utils.subtract(c2, c1, t); + + var distsq = t.lengthSq(); + + if (distsq > rmax * rmax) + { + return 0; + } + + var dist = Math.sqrt(distsq); + + var p: Phaser.Vec2 = new Phaser.Vec2; + Phaser.Vec2Utils.multiplyAdd(c1, t, 0.5 + (r1 - r2) * 0.5 / dist, p); + //var p = vec2.mad(c1, t, 0.5 + (r1 - r2) * 0.5 / dist); + + var n: Phaser.Vec2 = new Phaser.Vec2; + //var n = (dist != 0) ? vec2.scale(t, 1 / dist) : vec2.zero; + + if (dist != 0) + { + Phaser.Vec2Utils.scale(t, 1 / dist, n); + } + + var d = dist - rmax; + + contactArr.push(new Contact(p, n, d, 0)); + + return 1; + + } + + public circle2Circle(circ1: Phaser.Physics.Advanced.Shapes.Circle, circ2: Phaser.Physics.Advanced.Shapes.Circle, contactArr: Contact[]) { + return this._circle2Circle(circ1.tc, circ1.radius, circ2.tc, circ2.radius, contactArr); + } + + public circle2Segment(circ: Phaser.Physics.Advanced.Shapes.Circle, seg: Phaser.Physics.Advanced.Shapes.Segment, contactArr: Contact[]) { + + var rsum = circ.radius + seg.radius; + + // Normal distance from segment + var dn = Phaser.Vec2Utils.dot(circ.tc, seg.tn) - Phaser.Vec2Utils.dot(seg.ta, seg.tn); + var dist = (dn < 0 ? dn * -1 : dn) - rsum; + if (dist > 0) + { + return 0; + } + + // Tangential distance along segment + var dt = Phaser.Vec2Utils.cross(circ.tc, seg.tn); + var dtMin = Phaser.Vec2Utils.cross(seg.ta, seg.tn); + var dtMax = Phaser.Vec2Utils.cross(seg.tb, seg.tn); + + if (dt < dtMin) + { + if (dt < dtMin - rsum) + { + return 0; + } + + return this._circle2Circle(circ.tc, circ.radius, seg.ta, seg.radius, contactArr); + } + else if (dt > dtMax) + { + if (dt > dtMax + rsum) + { + return 0; + } + + return this._circle2Circle(circ.tc, circ.radius, seg.tb, seg.radius, contactArr); + } + + var n: Phaser.Vec2 = new Phaser.Vec2; + + if (dn > 0) + { + n.copyFrom(seg.tn); + } + else + { + Phaser.Vec2Utils.negative(seg.tn, n); + } + //var n = (dn > 0) ? seg.tn : vec2.neg(seg.tn); + + var c1: Phaser.Vec2 = new Phaser.Vec2; + Phaser.Vec2Utils.multiplyAdd(circ.tc, n, -(circ.radius + dist * 0.5), c1); + + var c2: Phaser.Vec2 = new Phaser.Vec2; + Phaser.Vec2Utils.negative(n, c2); + + contactArr.push(new Contact(c1, c2, dist, 0)); + //contactArr.push(new Contact(vec2.mad(circ.tc, n, -(circ.r + dist * 0.5)), vec2.neg(n), dist, 0)); + + return 1; + + } + + public circle2Poly(circ: Phaser.Physics.Advanced.Shapes.Circle, poly: Phaser.Physics.Advanced.Shapes.Poly, contactArr: Contact[]) { + + var minDist = -999999; + var minIdx = -1; + + for (var i = 0; i < poly.verts.length; i++) + { + var plane = poly.tplanes[i]; + var dist = Phaser.Vec2Utils.dot(circ.tc, plane.n) - plane.d - circ.radius; + + if (dist > 0) + { + return 0; + } + else if (dist > minDist) + { + minDist = dist; + minIdx = i; + } + } + + var n = poly.tplanes[minIdx].n; + var a = poly.tverts[minIdx]; + var b = poly.tverts[(minIdx + 1) % poly.verts.length]; + var dta = Phaser.Vec2Utils.cross(a, n); + var dtb = Phaser.Vec2Utils.cross(b, n); + var dt = Phaser.Vec2Utils.cross(circ.tc, n); + + if (dt > dta) + { + return this._circle2Circle(circ.tc, circ.radius, a, 0, contactArr); + } + else if (dt < dtb) + { + return this._circle2Circle(circ.tc, circ.radius, b, 0, contactArr); + } + + var c1: Phaser.Vec2 = new Phaser.Vec2; + Phaser.Vec2Utils.multiplyAdd(circ.tc, n, -(circ.radius + minDist * 0.5), c1); + + var c2: Phaser.Vec2 = new Phaser.Vec2; + Phaser.Vec2Utils.negative(n, c2); + + contactArr.push(new Contact(c1, c2, minDist, 0)); + + //contactArr.push(new Contact(vec2.mad(circ.tc, n, -(circ.r + minDist * 0.5)), vec2.neg(n), minDist, 0)); + + return 1; + + } + + public segmentPointDistanceSq(seg: Phaser.Physics.Advanced.Shapes.Segment, p) { + + var w: Phaser.Vec2 = new Phaser.Vec2; + var d: Phaser.Vec2 = new Phaser.Vec2; + Phaser.Vec2Utils.subtract(p, seg.ta, w); + Phaser.Vec2Utils.subtract(seg.tb, seg.ta, d); + + //var w = vec2.sub(p, seg.ta); + //var d = vec2.sub(seg.tb, seg.ta); + + var proj = w.dot(d); + + if (proj <= 0) + { + return w.dot(w); + } + + var vsq = d.dot(d); + + if (proj >= vsq) + { + return w.dot(w) - 2 * proj + vsq; + } + + return w.dot(w) - proj * proj / vsq; + + } + + // FIXME and optimise me lots!!! + public segment2Segment(seg1: Phaser.Physics.Advanced.Shapes.Segment, seg2: Phaser.Physics.Advanced.Shapes.Segment, contactArr: Contact[]) { + + var d = []; + d[0] = this.segmentPointDistanceSq(seg1, seg2.ta); + d[1] = this.segmentPointDistanceSq(seg1, seg2.tb); + d[2] = this.segmentPointDistanceSq(seg2, seg1.ta); + d[3] = this.segmentPointDistanceSq(seg2, seg1.tb); + + var idx1 = d[0] < d[1] ? 0 : 1; + var idx2 = d[2] < d[3] ? 2 : 3; + var idxm = d[idx1] < d[idx2] ? idx1 : idx2; + var s, t; + + var u = Phaser.Vec2Utils.subtract(seg1.tb, seg1.ta); + var v = Phaser.Vec2Utils.subtract(seg2.tb, seg2.ta); + + switch (idxm) + { + case 0: + s = Phaser.Vec2Utils.dot(Phaser.Vec2Utils.subtract(seg2.ta, seg1.ta), u) / Phaser.Vec2Utils.dot(u, u); + s = s < 0 ? 0 : (s > 1 ? 1 : s); + t = 0; + break; + case 1: + s = Phaser.Vec2Utils.dot(Phaser.Vec2Utils.subtract(seg2.tb, seg1.ta), u) / Phaser.Vec2Utils.dot(u, u); + s = s < 0 ? 0 : (s > 1 ? 1 : s); + t = 1; + break; + case 2: + s = 0; + t = Phaser.Vec2Utils.dot(Phaser.Vec2Utils.subtract(seg1.ta, seg2.ta), v) / Phaser.Vec2Utils.dot(v, v); + t = t < 0 ? 0 : (t > 1 ? 1 : t); + break; + case 3: + s = 1; + t = Phaser.Vec2Utils.dot(Phaser.Vec2Utils.subtract(seg1.tb, seg2.ta), v) / Phaser.Vec2Utils.dot(v, v); + t = t < 0 ? 0 : (t > 1 ? 1 : t); + break; + } + + var minp1 = Phaser.Vec2Utils.multiplyAdd(seg1.ta, u, s); + var minp2 = Phaser.Vec2Utils.multiplyAdd(seg2.ta, v, t); + + return this._circle2Circle(minp1, seg1.radius, minp2, seg2.radius, contactArr); + + } + + // Identify vertexes that have penetrated the segment. + public findPointsBehindSeg(contactArr: Contact[], seg: Phaser.Physics.Advanced.Shapes.Segment, poly: Phaser.Physics.Advanced.Shapes.Poly, dist: number, coef: number) { + + var dta = Phaser.Vec2Utils.cross(seg.tn, seg.ta); + var dtb = Phaser.Vec2Utils.cross(seg.tn, seg.tb); + + var n: Phaser.Vec2 = new Phaser.Vec2; + Phaser.Vec2Utils.scale(seg.tn, coef, n); + //var n = vec2.scale(seg.tn, coef); + + for (var i = 0; i < poly.verts.length; i++) + { + var v = poly.tverts[i]; + + if (Phaser.Vec2Utils.dot(v, n) < Phaser.Vec2Utils.dot(seg.tn, seg.ta) * coef + seg.radius) + { + var dt = Phaser.Vec2Utils.cross(seg.tn, v); + + if (dta >= dt && dt >= dtb) + { + contactArr.push(new Contact(v, n, dist, (poly.id << 16) | i)); + } + } + } + } + + public segment2Poly(seg: Phaser.Physics.Advanced.Shapes.Segment, poly: Phaser.Physics.Advanced.Shapes.Poly, contactArr: Contact[]) { + + var seg_td = Phaser.Vec2Utils.dot(seg.tn, seg.ta); + var seg_d1 = poly.distanceOnPlane(seg.tn, seg_td) - seg.radius; + + if (seg_d1 > 0) + { + return 0; + } + + var n: Phaser.Vec2 = new Phaser.Vec2; + Phaser.Vec2Utils.negative(seg.tn, n); + var seg_d2 = poly.distanceOnPlane(n, -seg_td) - seg.radius; + //var seg_d2 = poly.distanceOnPlane(vec2.neg(seg.tn), -seg_td) - seg.r; + + if (seg_d2 > 0) + { + return 0; + } + + var poly_d = -999999; + var poly_i = -1; + + for (var i = 0; i < poly.verts.length; i++) + { + var plane = poly.tplanes[i]; + var dist = seg.distanceOnPlane(plane.n, plane.d); + + if (dist > 0) + { + return 0; + } + + if (dist > poly_d) + { + poly_d = dist; + poly_i = i; + } + } + + var poly_n: Phaser.Vec2 = new Phaser.Vec2; + Phaser.Vec2Utils.negative(poly.tplanes[poly_i].n, poly_n); + //var poly_n = vec2.neg(poly.tplanes[poly_i].n); + + var va: Phaser.Vec2 = new Phaser.Vec2; + Phaser.Vec2Utils.multiplyAdd(seg.ta, poly_n, seg.radius, va); + //var va = vec2.mad(seg.ta, poly_n, seg.r); + + var vb: Phaser.Vec2 = new Phaser.Vec2; + Phaser.Vec2Utils.multiplyAdd(seg.tb, poly_n, seg.radius, vb); + //var vb = vec2.mad(seg.tb, poly_n, seg.r); + + if (poly.containPoint(va)) + { + contactArr.push(new Contact(va, poly_n, poly_d, (seg.id << 16) | 0)); + } + + if (poly.containPoint(vb)) + { + contactArr.push(new Contact(vb, poly_n, poly_d, (seg.id << 16) | 1)); + } + + // Floating point precision problems here. + // This will have to do for now. + poly_d -= 0.1 + if (seg_d1 >= poly_d || seg_d2 >= poly_d) + { + if (seg_d1 > seg_d2) + { + this.findPointsBehindSeg(contactArr, seg, poly, seg_d1, 1); + } + else + { + this.findPointsBehindSeg(contactArr, seg, poly, seg_d2, -1); + } + } + + // If no other collision points are found, try colliding endpoints. + if (contactArr.length == 0) + { + var poly_a = poly.tverts[poly_i]; + var poly_b = poly.tverts[(poly_i + 1) % poly.verts.length]; + + if (this._circle2Circle(seg.ta, seg.radius, poly_a, 0, contactArr)) + { + return 1; + } + + if (this._circle2Circle(seg.tb, seg.radius, poly_a, 0, contactArr)) + { + return 1; + } + + if (this._circle2Circle(seg.ta, seg.radius, poly_b, 0, contactArr)) + { + return 1; + } + + if (this._circle2Circle(seg.tb, seg.radius, poly_b, 0, contactArr)) + { + return 1; + } + } + + return contactArr.length; + + } + + // Find the minimum separating axis for the given poly and plane list. + public findMSA(poly: Phaser.Physics.Advanced.Shapes.Poly, planes, num: number) { + + var min_dist = -999999; + var min_index = -1; + + for (var i = 0; i < num; i++) + { + var dist = poly.distanceOnPlane(planes[i].n, planes[i].d); + + if (dist > 0) + { + // no collision + return { dist: 0, index: -1 }; + } + else if (dist > min_dist) + { + min_dist = dist; + min_index = i; + } + } + + // new object - see what we can do here + return { dist: min_dist, index: min_index }; + + } + + public findVertsFallback(contactArr: Contact[], poly1: Phaser.Physics.Advanced.Shapes.Poly, poly2: Phaser.Physics.Advanced.Shapes.Poly, n, dist: number) { + + var num = 0; + + for (var i = 0; i < poly1.verts.length; i++) + { + var v = poly1.tverts[i]; + + if (poly2.containPointPartial(v, n)) + { + contactArr.push(new Contact(v, n, dist, (poly1.id << 16) | i)); + num++; + } + } + + for (var i = 0; i < poly2.verts.length; i++) + { + var v = poly2.tverts[i]; + + if (poly1.containPointPartial(v, n)) + { + contactArr.push(new Contact(v, n, dist, (poly2.id << 16) | i)); + num++; + } + } + + return num; + + } + + // Find the overlapped vertices. + public findVerts(contactArr: Contact[], poly1: Phaser.Physics.Advanced.Shapes.Poly, poly2: Phaser.Physics.Advanced.Shapes.Poly, n, dist: number) { + + var num = 0; + + for (var i = 0; i < poly1.verts.length; i++) + { + var v = poly1.tverts[i]; + + if (poly2.containPoint(v)) + { + contactArr.push(new Contact(v, n, dist, (poly1.id << 16) | i)); + num++; + } + } + + for (var i = 0; i < poly2.verts.length; i++) + { + var v = poly2.tverts[i]; + + if (poly1.containPoint(v)) + { + contactArr.push(new Contact(v, n, dist, (poly2.id << 16) | i)); + num++; + } + } + + return num > 0 ? num : this.findVertsFallback(contactArr, poly1, poly2, n, dist); + + } + + public poly2Poly(poly1: Phaser.Physics.Advanced.Shapes.Poly, poly2: Phaser.Physics.Advanced.Shapes.Poly, contactArr: Contact[]) { + + var msa1 = this.findMSA(poly2, poly1.tplanes, poly1.verts.length); + + if (msa1.index == -1) + { + return 0; + } + + var msa2 = this.findMSA(poly1, poly2.tplanes, poly2.verts.length); + if (msa2.index == -1) + { + return 0; + } + + // Penetration normal direction shoud be from poly1 to poly2 + if (msa1.dist > msa2.dist) + { + return this.findVerts(contactArr, poly1, poly2, poly1.tplanes[msa1.index].n, msa1.dist); + } + + return this.findVerts(contactArr, poly1, poly2, Phaser.Vec2Utils.negative(poly2.tplanes[msa2.index].n), msa2.dist); + + } + + } + +} \ No newline at end of file diff --git a/Phaser/physics/advanced/Contact.ts b/Phaser/physics/advanced/Contact.ts new file mode 100644 index 00000000..d927764b --- /dev/null +++ b/Phaser/physics/advanced/Contact.ts @@ -0,0 +1,62 @@ +/// +/// +/// +/// +/// + +/** +* Phaser - Advanced Physics - Contact +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ + +module Phaser.Physics.Advanced { + + export class Contact { + + constructor(p, n, d, hash) { + + this.hash = hash; + this.point = p; + this.normal = n; + this.depth = d; + this.lambdaNormal = 0; + this.lambdaTangential = 0; + + this.r1 = new Phaser.Vec2; + this.r2 = new Phaser.Vec2; + this.r1_local = new Phaser.Vec2; + this.r2_local = new Phaser.Vec2; + + } + + public hash; + + // Linear velocities at contact point + public r1: Phaser.Vec2; + public r2: Phaser.Vec2; + public r1_local: Phaser.Vec2; + public r2_local: Phaser.Vec2; + // Bounce velocity + public bounce: number; + public emn: number; + public emt: number; + + // Contact point + public point; + + // Contact normal (toward shape2) + public normal: Phaser.Vec2; + + // Penetration depth (d < 0) + public depth; + + // Accumulated normal constraint impulse + public lambdaNormal; + + // Accumulated tangential constraint impulse + public lambdaTangential; + + } + +} diff --git a/Phaser/physics/advanced/ContactSolver.ts b/Phaser/physics/advanced/ContactSolver.ts new file mode 100644 index 00000000..965919a7 --- /dev/null +++ b/Phaser/physics/advanced/ContactSolver.ts @@ -0,0 +1,359 @@ +/// +/// +/// +/// +/// +/// +/// + +/** +* Phaser - Advanced Physics - ContactSolver +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ + +//------------------------------------------------------------------------------------------------- +// Contact Constraint +// +// Non-penetration constraint: +// C = dot(p2 - p1, n) +// Cdot = dot(v2 - v1, n) +// J = [ -n, -cross(r1, n), n, cross(r2, n) ] +// +// impulse = JT * lambda = [ -n * lambda, -cross(r1, n) * lambda, n * lambda, cross(r1, n) * lambda ] +// +// Friction constraint: +// C = dot(p2 - p1, t) +// Cdot = dot(v2 - v1, t) +// J = [ -t, -cross(r1, t), t, cross(r2, t) ] +// +// impulse = JT * lambda = [ -t * lambda, -cross(r1, t) * lambda, t * lambda, cross(r1, t) * lambda ] +// +// NOTE: lambda is an impulse in constraint space. +//------------------------------------------------------------------------------------------------- + +module Phaser.Physics.Advanced { + + export class ContactSolver { + + constructor(shape1, shape2) { + + //console.log('ContactSolver super'); + + this.shape1 = shape1; + this.shape2 = shape2; + + this.contacts = []; + this.elasticity = 1; + this.friction = 1; + + } + + public shape1; + public shape2; + + // Contact list + public contacts: Contact[]; + + // Coefficient of restitution (elasticity) + public elasticity: number; + + // Frictional coefficient + public friction: number; + + public update(newContactArr: Contact[]) { + + for (var i = 0; i < newContactArr.length; i++) + { + var newContact = newContactArr[i]; + var k = -1; + + for (var j = 0; j < this.contacts.length; j++) + { + if (newContact.hash == this.contacts[j].hash) + { + k = j; + break; + } + } + + if (k > -1) + { + newContact.lambdaNormal = this.contacts[k].lambdaNormal; + newContact.lambdaTangential = this.contacts[k].lambdaTangential; + } + } + + this.contacts = newContactArr; + + } + + public initSolver(dt_inv) { + + var body1: Body = this.shape1.body; + var body2: Body = this.shape2.body; + + var sum_m_inv = body1.massInverted + body2.massInverted; + + for (var i = 0; i < this.contacts.length; i++) + { + var con: Contact = this.contacts[i]; + + //console.log('initSolver con'); + //console.log(con); + + // Transformed r1, r2 + Phaser.Vec2Utils.subtract(con.point, body1.position, con.r1); + Phaser.Vec2Utils.subtract(con.point, body2.position, con.r2); + //con.r1 = vec2.sub(con.point, body1.p); + //con.r2 = vec2.sub(con.point, body2.p); + + // Local r1, r2 + Phaser.TransformUtils.unrotate(body1.transform, con.r1, con.r1_local); + Phaser.TransformUtils.unrotate(body2.transform, con.r2, con.r2_local); + //con.r1_local = body1.transform.unrotate(con.r1); + //con.r2_local = body2.transform.unrotate(con.r2); + + var n = con.normal; + var t = Phaser.Vec2Utils.perp(con.normal); + + // invEMn = J * invM * JT + // J = [ -n, -cross(r1, n), n, cross(r2, n) ] + var sn1 = Phaser.Vec2Utils.cross(con.r1, n); + var sn2 = Phaser.Vec2Utils.cross(con.r2, n); + var emn_inv = sum_m_inv + body1.inertiaInverted * sn1 * sn1 + body2.inertiaInverted * sn2 * sn2; + con.emn = emn_inv == 0 ? 0 : 1 / emn_inv; + + // invEMt = J * invM * JT + // J = [ -t, -cross(r1, t), t, cross(r2, t) ] + var st1 = Phaser.Vec2Utils.cross(con.r1, t); + var st2 = Phaser.Vec2Utils.cross(con.r2, t); + var emt_inv = sum_m_inv + body1.inertiaInverted * st1 * st1 + body2.inertiaInverted * st2 * st2; + con.emt = emt_inv == 0 ? 0 : 1 / emt_inv; + + // Linear velocities at contact point + // in 2D: cross(w, r) = perp(r) * w + + var v1 = new Phaser.Vec2; + var v2 = new Phaser.Vec2; + + Phaser.Vec2Utils.multiplyAdd(body1.velocity, Phaser.Vec2Utils.perp(con.r1), body1.angularVelocity, v1); + Phaser.Vec2Utils.multiplyAdd(body2.velocity, Phaser.Vec2Utils.perp(con.r2), body2.angularVelocity, v2); + //var v1 = vec2.mad(body1.v, vec2.perp(con.r1), body1.w); + //var v2 = vec2.mad(body2.v, vec2.perp(con.r2), body2.w); + + // relative velocity at contact point + var rv = new Phaser.Vec2; + Phaser.Vec2Utils.subtract(v2, v1, rv); + //var rv = vec2.sub(v2, v1); + + // bounce velocity dot n + con.bounce = Phaser.Vec2Utils.dot(rv, con.normal) * this.elasticity; + + } + } + + public warmStart() { + + var body1: Body = this.shape1.body; + var body2: Body = this.shape2.body; + + for (var i = 0; i < this.contacts.length; i++) + { + var con = this.contacts[i]; + var n = con.normal; + var lambda_n = con.lambdaNormal; + var lambda_t = con.lambdaTangential; + + // Apply accumulated impulses + //var impulse = vec2.rotate_vec(new vec2(lambda_n, lambda_t), n); + //var impulse = new vec2(lambda_n * n.x - lambda_t * n.y, lambda_t * n.x + lambda_n * n.y); + var impulse = new Phaser.Vec2(lambda_n * n.x - lambda_t * n.y, lambda_t * n.x + lambda_n * n.y); + + body1.velocity.multiplyAddByScalar(impulse, -body1.massInverted); + //body1.v.mad(impulse, -body1.m_inv); + + body1.angularVelocity -= Phaser.Vec2Utils.cross(con.r1, impulse) * body1.inertiaInverted; + //body1.w -= vec2.cross(con.r1, impulse) * body1.i_inv; + + body2.velocity.multiplyAddByScalar(impulse, -body2.massInverted); + //body2.v.mad(impulse, body2.m_inv); + + body2.angularVelocity -= Phaser.Vec2Utils.cross(con.r2, impulse) * body2.inertiaInverted; + //body2.w += vec2.cross(con.r2, impulse) * body2.i_inv; + } + + } + + public solveVelocityConstraints() { + + var body1: Body = this.shape1.body; + var body2: Body = this.shape2.body; + + var m1_inv = body1.massInverted; + var i1_inv = body1.inertiaInverted; + var m2_inv = body2.massInverted; + var i2_inv = body2.inertiaInverted; + + for (var i = 0; i < this.contacts.length; i++) + { + var con = this.contacts[i]; + var n = con.normal; + var t = Phaser.Vec2Utils.perp(n); + var r1 = con.r1; + var r2 = con.r2; + + // Linear velocities at contact point + // in 2D: cross(w, r) = perp(r) * w + + var v1 = new Phaser.Vec2; + var v2 = new Phaser.Vec2; + + Phaser.Vec2Utils.multiplyAdd(body1.velocity, Phaser.Vec2Utils.perp(r1), body1.angularVelocity, v1); + //var v1 = vec2.mad(body1.v, vec2.perp(r1), body1.w); + + Phaser.Vec2Utils.multiplyAdd(body2.velocity, Phaser.Vec2Utils.perp(r2), body2.angularVelocity, v2); + //var v2 = vec2.mad(body2.v, vec2.perp(r2), body2.w); + + // Relative velocity at contact point + var rv = new Phaser.Vec2; + Phaser.Vec2Utils.subtract(v2, v1, rv); + //var rv = vec2.sub(v2, v1); + + // Compute normal constraint impulse + adding bounce as a velocity bias + // lambda_n = -EMn * J * V + var lambda_n = -con.emn * (Phaser.Vec2Utils.dot(n, rv) + con.bounce); + + // Accumulate and clamp + var lambda_n_old = con.lambdaNormal; + con.lambdaNormal = Math.max(lambda_n_old + lambda_n, 0); + lambda_n = con.lambdaNormal - lambda_n_old; + + // Compute frictional constraint impulse + // lambda_t = -EMt * J * V + var lambda_t = -con.emt * Phaser.Vec2Utils.dot(t, rv); + + // Max friction constraint impulse (Coulomb's Law) + var lambda_t_max = con.lambdaNormal * this.friction; + + // Accumulate and clamp + var lambda_t_old = con.lambdaTangential; + con.lambdaTangential = this.clamp(lambda_t_old + lambda_t, -lambda_t_max, lambda_t_max); + lambda_t = con.lambdaTangential - lambda_t_old; + + // Apply the final impulses + //var impulse = vec2.rotate_vec(new vec2(lambda_n, lambda_t), n); + var impulse = new Phaser.Vec2(lambda_n * n.x - lambda_t * n.y, lambda_t * n.x + lambda_n * n.y); + + + body1.velocity.multiplyAddByScalar(impulse, -m1_inv); + //body1.v.mad(impulse, -m1_inv); + + body1.angularVelocity -= Phaser.Vec2Utils.cross(r1, impulse) * i1_inv; + //body1.w -= vec2.cross(r1, impulse) * i1_inv; + + body2.velocity.multiplyAddByScalar(impulse, m2_inv); + //body2.v.mad(impulse, m2_inv); + + body1.angularVelocity += Phaser.Vec2Utils.cross(r2, impulse) * i2_inv; + //body2.w += vec2.cross(r2, impulse) * i2_inv; + + } + + } + + public solvePositionConstraints() { + + var body1: Body = this.shape1.body; + var body2: Body = this.shape2.body; + + var m1_inv = body1.massInverted; + var i1_inv = body1.inertiaInverted; + var m2_inv = body2.massInverted; + var i2_inv = body2.inertiaInverted; + var sum_m_inv = m1_inv + m2_inv; + + var max_penetration = 0; + + for (var i = 0; i < this.contacts.length; i++) + { + var con = this.contacts[i]; + var n = con.normal; + + var r1 = new Phaser.Vec2; + var r2 = new Phaser.Vec2; + + // Transformed r1, r2 + + Phaser.Vec2Utils.rotate(con.r1_local, body1.angle, r1); + //var r1 = vec2.rotate(con.r1_local, body1.a); + + + Phaser.Vec2Utils.rotate(con.r2_local, body2.angle, r2); + //var r2 = vec2.rotate(con.r2_local, body2.a); + + // Contact points (corrected) + var p1 = new Phaser.Vec2; + var p2 = new Phaser.Vec2; + + Phaser.Vec2Utils.add(body1.position, r1, p1); + //var p1 = vec2.add(body1.p, r1); + + Phaser.Vec2Utils.add(body2.position, r2, p2); + //var p2 = vec2.add(body2.p, r2); + + // Corrected delta vector + var dp = new Phaser.Vec2; + Phaser.Vec2Utils.subtract(p2, p1); + //var dp = vec2.sub(p2, p1); + + // Position constraint + var c = Phaser.Vec2Utils.dot(dp, n) + con.depth; + + var correction = this.clamp(Manager.CONTACT_SOLVER_BAUMGARTE * (c + Manager.CONTACT_SOLVER_COLLISION_SLOP), -Manager.CONTACT_SOLVER_MAX_LINEAR_CORRECTION, 0); + + if (correction == 0) + { + continue; + } + + // We don't need max_penetration less than or equal slop + max_penetration = Math.max(max_penetration, -c); + + // Compute lambda for position constraint + // Solve (J * invM * JT) * lambda = -C / dt + var sn1 = Phaser.Vec2Utils.cross(r1, n); + var sn2 = Phaser.Vec2Utils.cross(r2, n); + + var em_inv = sum_m_inv + body1.inertiaInverted * sn1 * sn1 + body2.inertiaInverted * sn2 * sn2; + + var lambda_dt = em_inv == 0 ? 0 : -correction / em_inv; + + // Apply correction impulses + var impulse_dt = new Phaser.Vec2; + Phaser.Vec2Utils.scale(n, lambda_dt, impulse_dt); + //var impulse_dt = vec2.scale(n, lambda_dt); + + body1.position.multiplyAddByScalar(impulse_dt, -m1_inv); + //body1.p.mad(impulse_dt, -m1_inv); + + body1.angle -= sn1 * lambda_dt * i1_inv; + + body2.position.multiplyAddByScalar(impulse_dt, m2_inv); + //body2.p.mad(impulse_dt, m2_inv); + + body2.angle += sn2 * lambda_dt * i2_inv; + } + + return max_penetration <= Manager.CONTACT_SOLVER_COLLISION_SLOP * 3; + + } + + public clamp(v, min, max) { + return v < min ? min : (v > max ? max : v); + } + + + } + +} diff --git a/Phaser/physics/advanced/Manager.ts b/Phaser/physics/advanced/Manager.ts new file mode 100644 index 00000000..a83b7335 --- /dev/null +++ b/Phaser/physics/advanced/Manager.ts @@ -0,0 +1,328 @@ +/// +/// +/// + +/** +* Phaser - Advanced Physics Manager +* +* Your game only has one PhysicsManager instance and it's responsible for looking after, creating and colliding +* all of the physics objects in the world. +*/ + +module Phaser.Physics.Advanced { + + export class Manager { + + constructor(game: Game) { + + this.game = game; + + this.space = new Space(); + + Manager.collision = new Collision(); + + } + + /** + * Local reference to Game. + */ + public game: Game; + + public static collision: Collision; + + public static SHAPE_TYPE_CIRCLE: number = 0; + public static SHAPE_TYPE_SEGMENT: number = 1; + public static SHAPE_TYPE_POLY: number = 2; + public static SHAPE_NUM_TYPES: number = 3; + + public static JOINT_TYPE_ANGLE: number = 0; + public static JOINT_TYPE_REVOLUTE: number = 1; + public static JOINT_TYPE_WELD: number = 2; + public static JOINT_TYPE_WHEEL: number = 3; + public static JOINT_TYPE_PRISMATIC: number = 4; + public static JOINT_TYPE_DISTANCE: number = 5; + public static JOINT_TYPE_ROPE: number = 6; + public static JOINT_TYPE_MOUSE: number = 7; + + public static JOINT_LINEAR_SLOP: number = 0.0008; + public static JOINT_ANGULAR_SLOP: number = 2 * Phaser.GameMath.DEG_TO_RAD; + public static JOINT_MAX_LINEAR_CORRECTION: number = 0.5; + public static JOINT_MAX_ANGULAR_CORRECTION: number = 8 * Phaser.GameMath.DEG_TO_RAD; + + public static JOINT_LIMIT_STATE_INACTIVE: number = 0; + public static JOINT_LIMIT_STATE_AT_LOWER: number = 1; + public static JOINT_LIMIT_STATE_AT_UPPER: number = 2; + public static JOINT_LIMIT_STATE_EQUAL_LIMITS: number = 3; + + public static CONTACT_SOLVER_COLLISION_SLOP: number = 0.0008; + public static CONTACT_SOLVER_BAUMGARTE: number = 0.28; + public static CONTACT_SOLVER_MAX_LINEAR_CORRECTION: number = 1;//Infinity; + + public static bodyCounter: number = 0; + public static jointCounter: number = 0; + public static shapeCounter: number = 0; + + public space: Space; + public lastTime: number = Date.now(); + public frameRateHz: number = 60; + public timeDelta: number = 0; + public paused: bool = false; + public step: bool = false; // step through the simulation (i.e. per click) + public velocityIterations: number = 8; + public positionIterations: number = 4; + public allowSleep: bool = true; + public warmStarting: bool = true; + + public update() { + + var time = Date.now(); + var frameTime = (time - this.lastTime) / 1000; + this.lastTime = time; + + // if rAf - why? + frameTime = Math.floor(frameTime * 60 + 0.5) / 60; + + //if (!mouseDown) + //{ + // var p = canvasToWorld(mousePosition); + // var body = space.findBodyByPoint(p); + // //domCanvas.style.cursor = body ? "pointer" : "default"; + //} + + if (!this.paused || this.step) + { + var h = 1 / this.frameRateHz; + + this.timeDelta += frameTime; + + if (this.step) + { + this.step = false; + this.timeDelta = h; + } + + for (var maxSteps = 4; maxSteps > 0 && this.timeDelta >= h; maxSteps--) + { + this.space.step(h, this.velocityIterations, this.positionIterations, this.warmStarting, this.allowSleep); + this.timeDelta -= h; + } + + if (this.timeDelta > h) + { + this.timeDelta = 0; + } + + //if (sceneIndex < demoArr.length) + //{ + // demo = demoArr[sceneIndex]; + // demo.runFrame(); + //} + } + + //frameCount++; + + } + + + + + + + + + + + + + + + + + + + + + + + + + public pixelsToMeters(value: number): number { + return value * 0.02; + } + + public metersToPixels(value: number): number { + return value * 50; + } + + public static pixelsToMeters(value: number): number { + return value * 0.02; + } + + public static metersToPixels(value: number): number { + return value * 50; + } + + public static p2m(value: number): number { + return value * 0.02; + } + + public static m2p(value: number): number { + return value * 50; + } + + public static areaForCircle(radius_outer, radius_inner): number { + return Math.PI * (radius_outer * radius_outer - radius_inner * radius_inner); + } + + public static inertiaForCircle(mass, center, radius_outer, radius_inner): number { + return mass * ((radius_outer * radius_outer + radius_inner * radius_inner) * 0.5 + center.lengthSq()); + } + + public static areaForSegment(a, b, radius): number { + return radius * (Math.PI * radius + 2 * Phaser.Vec2Utils.distance(a, b)); + } + + public static centroidForSegment(a, b): Phaser.Vec2 { + return Phaser.Vec2Utils.scale(Phaser.Vec2Utils.add(a, b), 0.5); + } + + public static inertiaForSegment(mass, a, b): number { + + var distsq = Phaser.Vec2Utils.distanceSq(b, a); + var offset: Phaser.Vec2 = Phaser.Vec2Utils.scale(Phaser.Vec2Utils.add(a, b), 0.5); + + return mass * (distsq / 12 + offset.lengthSq()); + } + + public static areaForPoly(verts): number { + + var area = 0; + + for (var i = 0; i < verts.length; i++) + { + area += Phaser.Vec2Utils.cross(verts[i], verts[(i + 1) % verts.length]); + } + + return area / 2; + } + + public static centroidForPoly(verts): Phaser.Vec2 { + + var area = 0; + var vsum = new Phaser.Vec2; + + for (var i = 0; i < verts.length; i++) + { + var v1 = verts[i]; + var v2 = verts[(i + 1) % verts.length]; + var cross = Phaser.Vec2Utils.cross(v1, v2); + + area += cross; + + // SO many vecs created here - unroll these bad boys + vsum.add(Phaser.Vec2Utils.scale(Phaser.Vec2Utils.add(v1, v2), cross)); + } + + return Phaser.Vec2Utils.scale(vsum, 1 / (3 * area)); + } + + public static inertiaForPoly(mass, verts, offset): number { + + var sum1 = 0; + var sum2 = 0; + + for (var i = 0; i < verts.length; i++) + { + var v1 = Phaser.Vec2Utils.add(verts[i], offset); + var v2 = Phaser.Vec2Utils.add(verts[(i + 1) % verts.length], offset); + + var a = Phaser.Vec2Utils.cross(v2, v1); + var b = Phaser.Vec2Utils.dot(v1, v1) + Phaser.Vec2Utils.dot(v1, v2) + Phaser.Vec2Utils.dot(v2, v2); + + sum1 += a * b; + sum2 += a; + } + + return (mass * sum1) / (6 * sum2); + } + + public static inertiaForBox(mass, w, h) { + return mass * (w * w + h * h) / 12; + } + + // Create the convex hull using the Gift wrapping algorithm (http://en.wikipedia.org/wiki/Gift_wrapping_algorithm) + public static createConvexHull(points) { + + // Find the right most point on the hull + var i0 = 0; + var x0 = points[0].x; + + for (var i = 1; i < points.length; i++) + { + var x = points[i].x; + + if (x > x0 || (x == x0 && points[i].y < points[i0].y)) + { + i0 = i; + x0 = x; + } + } + + var n = points.length; + var hull = []; + var m = 0; + var ih = i0; + + while (1) + { + hull[m] = ih; + + var ie = 0; + + for (var j = 1; j < n; j++) + { + if (ie == ih) + { + ie = j; + continue; + } + + var r = Phaser.Vec2Utils.subtract(points[ie], points[hull[m]]); + var v = Phaser.Vec2Utils.subtract(points[j], points[hull[m]]); + var c = Phaser.Vec2Utils.cross(r, v); + + if (c < 0) + { + ie = j; + } + + // Collinearity check + if (c == 0 && v.lengthSq() > r.lengthSq()) + { + ie = j; + } + } + + m++; + ih = ie; + + if (ie == i0) + { + break; + } + } + + // Copy vertices + var newPoints = []; + + for (var i = 0; i < m; ++i) + { + newPoints.push(points[hull[i]]); + } + + return newPoints; + } + + } + +} \ No newline at end of file diff --git a/Phaser/physics/advanced/Space.ts b/Phaser/physics/advanced/Space.ts new file mode 100644 index 00000000..52e09a5c --- /dev/null +++ b/Phaser/physics/advanced/Space.ts @@ -0,0 +1,850 @@ +/// +/// +/// +/// +/// +/// +/// +/// +/// + +/** +* Phaser - Advanced Physics - Space +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ + +module Phaser.Physics.Advanced { + + export class Space { + + constructor() { + + this.bodyArr = []; + this.bodyHash = {}; + + this.jointArr = []; + this.jointHash = {}; + + this.numContacts = 0; + this.contactSolvers = []; + + //this.postSolve(arb) { }; + + this.gravity = new Phaser.Vec2(0, 10); + this.damping = 0; + + } + + public static TIME_TO_SLEEP = 0.5; + public static SLEEP_LINEAR_TOLERANCE = 0.5; + public static SLEEP_ANGULAR_TOLERANCE = 2 * Phaser.GameMath.DEG_TO_RAD; + + public bodyArr: Body[]; + public bodyHash; + public jointArr: IJoint[]; + public jointHash; + public numContacts: number; + public contactSolvers: ContactSolver[]; + public postSolve; + public gravity: Phaser.Vec2; + public damping: number; + public stepCount: number = 0; + + public clear() { + + Manager.shapeCounter = 0; + Manager.bodyCounter = 0; + Manager.jointCounter = 0; + + for (var i = 0; i < this.bodyArr.length; i++) + { + if (this.bodyArr[i]) + { + this.removeBody(this.bodyArr[i]); + } + } + + this.bodyArr = []; + this.bodyHash = {}; + + this.jointArr = []; + this.jointHash = {}; + + this.contactSolvers = []; + + this.stepCount = 0; + + } + + public addBody(body: Body) { + + if (this.bodyHash[body.id] != undefined) + { + return; + } + + //console.log('Body added to space', body.name); + + var index = this.bodyArr.push(body) - 1; + this.bodyHash[body.id] = index; + + body.awake(true); + body.space = this; + body.cacheData(); + + } + + public removeBody(body: Body) { + + if (this.bodyHash[body.id] == undefined) + { + return; + } + + // Remove linked joint + for (var i = 0; i < body.joints.length; i++) + { + if (body.joints[i]) + { + this.removeJoint(body.joints[i]); + } + } + + body.space = null; + + var index = this.bodyHash[body.id]; + delete this.bodyHash[body.id]; + delete this.bodyArr[index]; + + } + + public addJoint(joint: IJoint) { + + if (this.jointHash[joint.id] != undefined) + { + return; + } + + joint.body1.awake(true); + joint.body2.awake(true); + + var index = this.jointArr.push(joint) - 1; + this.jointHash[joint.id] = index; + + var index = joint.body1.joints.push(joint) - 1; + joint.body1.jointHash[joint.id] = index; + + var index = joint.body2.joints.push(joint) - 1; + joint.body2.jointHash[joint.id] = index; + + } + + public removeJoint(joint: IJoint) { + + if (this.jointHash[joint.id] == undefined) + { + return; + } + + joint.body1.awake(true); + joint.body2.awake(true); + + var index = joint.body1.jointHash[joint.id]; + delete joint.body1.jointHash[joint.id]; + delete joint.body1.joints[index]; + + var index = joint.body2.jointHash[joint.id]; + delete joint.body2.jointHash[joint.id]; + delete joint.body2.joints[index]; + + var index = this.jointHash[joint.id]; + delete this.jointHash[joint.id]; + delete this.jointArr[index]; + + } + + public findShapeByPoint(p, refShape) { + + var firstShape; + + for (var i = 0; i < this.bodyArr.length; i++) + { + var body = this.bodyArr[i]; + + if (!body) + { + continue; + } + + for (var j = 0; j < body.shapes.length; j++) + { + var shape = body.shapes[j]; + + if (shape.pointQuery(p)) + { + if (!refShape) + { + return shape; + } + + if (!firstShape) + { + firstShape = shape; + } + + if (shape == refShape) + { + refShape = null; + } + } + } + } + + return firstShape; + } + + public findBodyByPoint(p, refBody: Body) { + + var firstBody; + + for (var i = 0; i < this.bodyArr.length; i++) + { + var body = this.bodyArr[i]; + + if (!body) + { + continue; + } + + for (var j = 0; j < body.shapes.length; j++) + { + var shape = body.shapes[j]; + + if (shape.pointQuery(p)) + { + if (!refBody) + { + return shape.body; + } + + if (!firstBody) + { + firstBody = shape.body; + } + + if (shape.body == refBody) + { + refBody = null; + } + + break; + } + } + } + + return firstBody; + + } + + // TODO: Replace this function to shape hashing + public shapeById(id) { + + var shape; + + for (var i = 0; i < this.bodyArr.length; i++) + { + var body = this.bodyArr[i]; + if (!body) + { + continue; + } + + for (var j = 0; j < body.shapes.length; j++) + { + if (body.shapes[j].id == id) + { + return body.shapes[j]; + } + } + } + + return null; + } + + public jointById(id) { + + var index = this.jointHash[id]; + + if (index != undefined) + { + return this.jointArr[index]; + } + + return null; + } + + public findVertexByPoint(p, minDist, refVertexId) { + + var firstVertexId = -1; + + refVertexId = refVertexId || -1; + + for (var i = 0; i < this.bodyArr.length; i++) + { + var body = this.bodyArr[i]; + + if (!body) + { + continue; + } + + for (var j = 0; j < body.shapes.length; j++) + { + var shape = body.shapes[j]; + var index = shape.findVertexByPoint(p, minDist); + + if (index != -1) + { + var vertex = (shape.id << 16) | index; + + if (refVertexId == -1) + { + return vertex; + } + + if (firstVertexId == -1) + { + firstVertexId = vertex; + } + + if (vertex == refVertexId) + { + refVertexId = -1; + } + } + } + } + + return firstVertexId; + + } + + public findEdgeByPoint(p, minDist, refEdgeId) { + + var firstEdgeId = -1; + + refEdgeId = refEdgeId || -1; + + for (var i = 0; i < this.bodyArr.length; i++) + { + var body = this.bodyArr[i]; + + if (!body) + { + continue; + } + + for (var j = 0; j < body.shapes.length; j++) + { + var shape = body.shapes[j]; + + if (shape.type != Manager.SHAPE_TYPE_POLY) + { + continue; + } + + var index = shape.findEdgeByPoint(p, minDist); + + if (index != -1) + { + var edge = (shape.id << 16) | index; + + if (refEdgeId == -1) + { + return edge; + } + + if (firstEdgeId == -1) + { + firstEdgeId = edge; + } + + if (edge == refEdgeId) + { + refEdgeId = -1; + } + } + } + } + + return firstEdgeId; + } + + public findJointByPoint(p, minDist, refJointId) { + + var firstJointId = -1; + + var dsq = minDist * minDist; + + refJointId = refJointId || -1; + + for (var i = 0; i < this.jointArr.length; i++) + { + var joint = this.jointArr[i]; + + if (!joint) + { + continue; + } + + var jointId = -1; + + if (Phaser.Vec2Utils.distanceSq(p, joint.getWorldAnchor1()) < dsq) + { + jointId = (joint.id << 16 | 0); + } + else if (Phaser.Vec2Utils.distanceSq(p, joint.getWorldAnchor2()) < dsq) + { + jointId = (joint.id << 16 | 1); + } + + if (jointId != -1) + { + if (refJointId == -1) + { + return jointId; + } + + if (firstJointId == -1) + { + firstJointId = jointId; + } + + if (jointId == refJointId) + { + refJointId = -1; + } + } + } + + return firstJointId; + } + + public findContactSolver(shape1, shape2) { + + for (var i = 0; i < this.contactSolvers.length; i++) + { + var contactSolver = this.contactSolvers[i]; + + if (shape1 == contactSolver.shape1 && shape2 == contactSolver.shape2) + { + return contactSolver; + } + } + + return null; + } + + public genTemporalContactSolvers() { + + //console.log('genTemporalContactSolvers'); + + //var t0 = Date.now(); + + var newContactSolverArr = []; + + this.numContacts = 0; + + for (var body1_index = 0; body1_index < this.bodyArr.length; body1_index++) + { + var body1: Body = this.bodyArr[body1_index]; + + //console.log('body1', body1_index, body1.type); + + if (!body1) + { + continue; + } + + body1.stepCount = this.stepCount; + + for (var body2_index = 0; body2_index < this.bodyArr.length; body2_index++) + { + var body2: Body = this.bodyArr[body2_index]; + + //console.log('body2', body2_index, body2.type); + + if (!body2) + { + continue; + } + + if (body1.stepCount == body2.stepCount) + { + continue; + } + + //console.log('step'); + + var active1 = body1.isAwake && !body1.isStatic; + var active2 = body2.isAwake && !body2.isStatic; + + if (!active1 && !active2) + { + continue; + } + + //console.log('active'); + + if (!body1.isCollidable(body2)) + { + continue; + } + + //console.log('collideable'); + + if (!body1.bounds.intersectsBounds(body2.bounds)) + { + continue; + } + + //console.log('>>>>>>>>>> intersects'); + + for (var i = 0; i < body1.shapes.length; i++) + { + for (var j = 0; j < body2.shapes.length; j++) + { + var shape1 = body1.shapes[i]; + var shape2 = body2.shapes[j]; + + var contactArr = []; + + if (!Manager.collision.collide(shape1, shape2, contactArr)) + { + continue; + } + + if (shape1.type > shape2.type) + { + var temp = shape1; + shape1 = shape2; + shape2 = temp; + } + + this.numContacts += contactArr.length; + + var contactSolver = this.findContactSolver(shape1, shape2); + + if (contactSolver) + { + contactSolver.update(contactArr); + newContactSolverArr.push(contactSolver); + } + else + { + body1.awake(true); + body2.awake(true); + + var newContactSolver = new ContactSolver(shape1, shape2); + newContactSolver.contacts = contactArr; + newContactSolver.elasticity = Math.max(shape1.elasticity, shape2.elasticity); + newContactSolver.friction = Math.sqrt(shape1.friction * shape2.friction); + newContactSolverArr.push(newContactSolver); + } + } + } + } + } + + //stats.timeCollision = Date.now() - t0; + + return newContactSolverArr; + } + + public initSolver(dt, dt_inv, warmStarting) { + + //var t0 = Date.now(); + + // Initialize contact solvers + for (var i = 0; i < this.contactSolvers.length; i++) + { + this.contactSolvers[i].initSolver(dt_inv); + } + + // Initialize joint solver + for (var i = 0; i < this.jointArr.length; i++) + { + if (this.jointArr[i]) + { + this.jointArr[i].initSolver(dt, warmStarting); + } + } + + // Warm starting (apply cached impulse) + if (warmStarting) + { + for (var i = 0; i < this.contactSolvers.length; i++) + { + this.contactSolvers[i].warmStart(); + } + } + + //stats.timeInitSolver = Date.now() - t0; + } + + public velocitySolver(iteration) { + + //var t0 = Date.now(); + + for (var i = 0; i < iteration; i++) + { + for (var j = 0; j < this.jointArr.length; j++) + { + if (this.jointArr[j]) + { + this.jointArr[j].solveVelocityConstraints(); + } + } + + for (var j = 0; j < this.contactSolvers.length; j++) + { + this.contactSolvers[j].solveVelocityConstraints(); + } + } + + //stats.timeVelocitySolver = Date.now() - t0; + } + + public positionSolver(iteration) { + + //var t0 = Date.now(); + + var positionSolved = false; + + //stats.positionIterations = 0; + + for (var i = 0; i < iteration; i++) + { + var contactsOk = true; + var jointsOk = true; + + for (var j = 0; j < this.contactSolvers.length; j++) + { + var contactOk = this.contactSolvers[j].solvePositionConstraints(); + contactsOk = contactOk && contactsOk; + } + + for (var j = 0; j < this.jointArr.length; j++) + { + if (this.jointArr[j]) + { + var jointOk = this.jointArr[j].solvePositionConstraints(); + jointsOk = jointOk && jointsOk; + } + } + + if (contactsOk && jointsOk) + { + // exit early if the position errors are small + positionSolved = true; + break; + } + + //stats.positionIterations++; + } + + //stats.timePositionSolver = Date.now() - t0; + + return positionSolved; + + } + + public step(dt, vel_iteration, pos_iteration, warmStarting, allowSleep) { + + var dt_inv = 1 / dt; + + this.stepCount++; + + // Generate contact & contactSolver + this.contactSolvers = this.genTemporalContactSolvers(); + + // Initialize contacts & joints solver + this.initSolver(dt, dt_inv, warmStarting); + + // Intergrate velocity + for (var i = 0; i < this.bodyArr.length; i++) + { + var body = this.bodyArr[i]; + + if (!body) + { + continue; + } + + if (body.isDynamic && body.isAwake) + { + body.updateVelocity(this.gravity, dt, this.damping); + } + } + + for (var i = 0; i < this.jointArr.length; i++) + { + var joint = this.jointArr[i]; + + if (!joint) + { + continue; + } + + var body1 = joint.body1; + var body2 = joint.body2; + + var awake1 = body1.isAwake && !body1.isStatic; + var awake2 = body2.isAwake && !body2.isStatic; + + if (awake1 ^ awake2) + { + if (!awake1) + { + body1.awake(true); + } + + if (!awake2) + { + body2.awake(true); + } + } + } + + // Iterative velocity constraints solver + this.velocitySolver(vel_iteration); + + // Intergrate position + for (var i = 0; i < this.bodyArr.length; i++) + { + var body = this.bodyArr[i]; + + if (!body) + { + continue + } + + if (body.isDynamic && body.isAwake) + { + body.updatePosition(dt); + } + } + + // Process breakable joint + for (var i = 0; i < this.jointArr.length; i++) + { + var joint = this.jointArr[i]; + + if (!joint) + { + continue; + } + + if (joint.breakable) + { + if (joint.getReactionForce(dt_inv).lengthsq() >= joint.maxForce * joint.maxForce) + { + this.removeJoint(joint); + } + } + } + + // Iterative position constraints solver + var positionSolved = this.positionSolver(pos_iteration); + + for (var i = 0; i < this.bodyArr.length; i++) + { + var body = this.bodyArr[i]; + + if (!body) + { + continue; + } + + body.syncTransform(); + } + + // Post solve collision callback + for (var i = 0; i < this.contactSolvers.length; i++) + { + var arb = this.contactSolvers[i]; + + // Re-enable this + //this.postSolve(arb); + } + + for (var i = 0; i < this.bodyArr.length; i++) + { + var body = this.bodyArr[i]; + + if (!body) + { + continue; + } + + if (body.isDynamic && body.isAwake) + { + body.cacheData(); + } + } + + // Process sleeping + if (allowSleep) + { + var minSleepTime = 999999; + + var linTolSqr = Space.SLEEP_LINEAR_TOLERANCE * Space.SLEEP_LINEAR_TOLERANCE; + var angTolSqr = Space.SLEEP_ANGULAR_TOLERANCE * Space.SLEEP_ANGULAR_TOLERANCE; + + for (var i = 0; i < this.bodyArr.length; i++) + { + var body = this.bodyArr[i]; + + if (!body) + { + continue; + } + + if (!body.isDynamic) + { + continue; + } + + if (body.angularVelocity * body.angularVelocity > angTolSqr || body.velocity.dot(body.velocity) > linTolSqr) + { + body.sleepTime = 0; + minSleepTime = 0; + } + else + { + body.sleepTime += dt; + minSleepTime = Math.min(minSleepTime, body.sleepTime); + } + } + + if (positionSolved && minSleepTime >= Space.TIME_TO_SLEEP) + { + for (var i = 0; i < this.bodyArr.length; i++) + { + var body = this.bodyArr[i]; + + if (!body) + { + continue; + } + + body.awake(false); + } + } + } + } + + } + +} \ No newline at end of file diff --git a/Phaser/physics/advanced/joints/IJoint.ts b/Phaser/physics/advanced/joints/IJoint.ts new file mode 100644 index 00000000..1f0f1fb9 --- /dev/null +++ b/Phaser/physics/advanced/joints/IJoint.ts @@ -0,0 +1,43 @@ +/// +/// +/// +/// +/// + +/** +* Phaser - Advanced Physics - Joint +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ + +module Phaser.Physics.Advanced { + + export interface IJoint { + + id: number; + type: number; + + body1: Phaser.Physics.Advanced.Body; + body2: Phaser.Physics.Advanced.Body; + + collideConnected; // bool? + maxForce: number; + breakable: bool; + + anchor1: Phaser.Vec2; + anchor2: Phaser.Vec2; + + getWorldAnchor1(); + getWorldAnchor2(); + setWorldAnchor1(anchor1); + setWorldAnchor2(anchor2); + + initSolver(dt, warmStarting); + solveVelocityConstraints(); + solvePositionConstraints(); + getReactionForce(dt_inv); + + } + +} + diff --git a/Phaser/physics/advanced/joints/Joint.ts b/Phaser/physics/advanced/joints/Joint.ts new file mode 100644 index 00000000..6a64ae79 --- /dev/null +++ b/Phaser/physics/advanced/joints/Joint.ts @@ -0,0 +1,64 @@ +/// +/// +/// +/// +/// + +/** +* Phaser - Advanced Physics - Joint +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ + +module Phaser.Physics.Advanced { + + export class Joint { + + constructor(type: number, body1:Phaser.Physics.Advanced.Body, body2:Phaser.Physics.Advanced.Body, collideConnected) { + + this.id = Phaser.Physics.Advanced.Manager.jointCounter++; + this.type = type; + + this.body1 = body1; + this.body2 = body2; + + this.collideConnected = collideConnected; + + this.maxForce = 9999999999; + this.breakable = false; + + } + + public id: number; + public type: number; + + public body1: Phaser.Physics.Advanced.Body; + public body2: Phaser.Physics.Advanced.Body; + + public collideConnected; // bool? + public maxForce: number; + public breakable: bool; + + public anchor1: Phaser.Vec2; + public anchor2: Phaser.Vec2; + + public getWorldAnchor1() { + return this.body1.getWorldPoint(this.anchor1); + } + + public getWorldAnchor2() { + return this.body2.getWorldPoint(this.anchor2); + } + + public setWorldAnchor1(anchor1) { + this.anchor1 = this.body1.getLocalPoint(anchor1); + } + + public setWorldAnchor2(anchor2) { + this.anchor2 = this.body2.getLocalPoint(anchor2); + } + + } + + +} \ No newline at end of file diff --git a/Phaser/physics/advanced/shapes/Box.ts b/Phaser/physics/advanced/shapes/Box.ts new file mode 100644 index 00000000..3159e317 --- /dev/null +++ b/Phaser/physics/advanced/shapes/Box.ts @@ -0,0 +1,39 @@ +/// +/// +/// +/// +/// + +/** +* Phaser - Advanced Physics - Shapes - Box +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ + +module Phaser.Physics.Advanced.Shapes { + + export class Box extends Phaser.Physics.Advanced.Shapes.Poly { + + // Give in pixels + constructor(x, y, width, height) { + + x = Manager.pixelsToMeters(x); + y = Manager.pixelsToMeters(y); + width = Manager.pixelsToMeters(width); + height = Manager.pixelsToMeters(height); + + var hw = width * 0.5; + var hh = height * 0.5; + + super([ + new Phaser.Vec2(-hw + x, +hh + y), + new Phaser.Vec2(-hw + x, -hh + y), + new Phaser.Vec2(+hw + x, -hh + y), + new Phaser.Vec2(+hw + x, +hh + y) + ]); + + } + + } + +} diff --git a/Phaser/physics/advanced/shapes/Circle.ts b/Phaser/physics/advanced/shapes/Circle.ts new file mode 100644 index 00000000..6911f118 --- /dev/null +++ b/Phaser/physics/advanced/shapes/Circle.ts @@ -0,0 +1,101 @@ +/// +/// +/// +/// +/// + +/** +* Phaser - Advanced Physics - Shape - Circle +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ + +module Phaser.Physics.Advanced.Shapes { + + export class Circle extends Phaser.Physics.Advanced.Shape implements IShape { + + constructor(radius: number, x?: number = 0, y?: number = 0) { + + super(Manager.SHAPE_TYPE_CIRCLE); + + this.center = new Phaser.Vec2(x, y); + this.radius = radius; + this.tc = new Phaser.Vec2; + + this.finishVerts(); + + } + + public radius: number; + public center: Phaser.Vec2; + public tc: Phaser.Vec2; + + public finishVerts() { + this.radius = Math.abs(this.radius); + } + + public duplicate(): Circle { + return new Circle(this.center.x, this.center.y, this.radius); + } + + public recenter(c:Phaser.Vec2) { + this.center.subtract(c); + } + + public transform(xf: Transform) { + + Phaser.TransformUtils.transform(xf, this.center, this.center); + //this.center = xf.transform(this.center); + } + + public untransform(xf: Transform) { + Phaser.TransformUtils.untransform(xf, this.center, this.center); + //this.center = xf.untransform(this.center); + } + + public area(): number { + return Manager.areaForCircle(this.radius, 0); + } + + public centroid(): Phaser.Vec2 { + return Phaser.Vec2Utils.clone(this.center); + } + + public inertia(mass: number): number { + return Manager.inertiaForCircle(mass, this.center, this.radius, 0); + } + + public cacheData(xf: Transform) { + + Phaser.TransformUtils.transform(xf, this.center, this.tc); + //this.tc = xf.transform(this.center); + + this.bounds.mins.setTo(this.tc.x - this.radius, this.tc.y - this.radius); + this.bounds.maxs.setTo(this.tc.x + this.radius, this.tc.y + this.radius); + + } + + public pointQuery(p:Phaser.Vec2): bool { + //return vec2.distsq(this.tc, p) < (this.r * this.r); + return Phaser.Vec2Utils.distanceSq(this.tc, p) < (this.radius * this.radius); + } + + public findVertexByPoint(p:Phaser.Vec2, minDist: number): number { + + var dsq = minDist * minDist; + + if (Phaser.Vec2Utils.distanceSq(this.tc, p) < dsq) + { + return 0; + } + + return -1; + } + + public distanceOnPlane(n, d) { + Phaser.Vec2Utils.dot(n, this.tc) - this.radius - d; + } + + } + +} \ No newline at end of file diff --git a/Phaser/physics/advanced/shapes/IShape.ts b/Phaser/physics/advanced/shapes/IShape.ts new file mode 100644 index 00000000..1fe6ef32 --- /dev/null +++ b/Phaser/physics/advanced/shapes/IShape.ts @@ -0,0 +1,39 @@ +/// +/// +/// +/// +/// +/// + +/** +* Phaser - Advanced Physics - IShape +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ + +module Phaser.Physics.Advanced { + + export interface IShape { + + id: number; + type: number; + + elasticity: number; + friction: number; + density: number; + + body: Body; + bounds: Bounds; + + area(): number; + centroid(): Phaser.Vec2; + inertia(mass: number): number; + cacheData(xf:Transform); + pointQuery(p: Phaser.Vec2): bool; + findEdgeByPoint(p: Phaser.Vec2, minDist: number): number; + findVertexByPoint(p: Phaser.Vec2, minDist: number): number; + + } + +} + diff --git a/Phaser/physics/advanced/shapes/Poly.ts b/Phaser/physics/advanced/shapes/Poly.ts new file mode 100644 index 00000000..f4d50699 --- /dev/null +++ b/Phaser/physics/advanced/shapes/Poly.ts @@ -0,0 +1,295 @@ +/// +/// +/// +/// +/// + +/** +* Phaser - Advanced Physics - Shapes - Convex Polygon +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ + +module Phaser.Physics.Advanced.Shapes { + + export class Poly extends Phaser.Physics.Advanced.Shape implements IShape { + + constructor(verts?:Phaser.Vec2[]) { + + super(Manager.SHAPE_TYPE_POLY); + + this.verts = []; + this.planes = []; + + this.tverts = []; + this.tplanes = []; + + if (verts) + { + for (var i = 0; i < verts.length; i++) + { + this.verts[i] = Phaser.Vec2Utils.clone(verts[i]); + this.tverts[i] = this.verts[i]; + + this.tplanes[i] = {}; + this.tplanes[i].n = new Phaser.Vec2; + this.tplanes[i].d = 0; + } + } + + this.finishVerts(); + + } + + public verts: Phaser.Vec2[]; + public planes; + + public tverts; + public tplanes; + + public convexity: bool; + + public finishVerts() { + + if (this.verts.length < 2) + { + this.convexity = false; + this.planes = []; + return; + } + + this.convexity = true; + this.tverts = []; + this.tplanes = []; + + // Must be counter-clockwise verts + for (var i = 0; i < this.verts.length; i++) + { + var a = this.verts[i]; + var b = this.verts[(i + 1) % this.verts.length]; + var n = Phaser.Vec2Utils.normalize(Phaser.Vec2Utils.perp(Phaser.Vec2Utils.subtract(a, b))); + + this.planes[i] = {}; + this.planes[i].n = n; + this.planes[i].d = Phaser.Vec2Utils.dot(n, a); + + this.tverts[i] = this.verts[i]; + + this.tplanes[i] = {}; + this.tplanes[i].n = new Phaser.Vec2; + this.tplanes[i].d = 0; + } + + for (var i = 0; i < this.verts.length; i++) + { + var b = this.verts[(i + 2) % this.verts.length]; + var n = this.planes[i].n; + var d = this.planes[i].d; + + if (Phaser.Vec2Utils.dot(n, b) - d > 0) + { + this.convexity = false; + } + } + } + + public duplicate() { + return new Phaser.Physics.Advanced.Shapes.Poly(this.verts); + } + + public recenter(c) { + + for (var i = 0; i < this.verts.length; i++) + { + this.verts[i].subtract(c); + } + + } + + public transform(xf) { + for (var i = 0; i < this.verts.length; i++) + { + this.verts[i] = xf.transform(this.verts[i]); + } + } + + public untransform(xf) { + for (var i = 0; i < this.verts.length; i++) + { + this.verts[i] = xf.untransform(this.verts[i]); + } + } + + public area(): number { + return Manager.areaForPoly(this.verts); + } + + public centroid(): Phaser.Vec2 { + return Manager.centroidForPoly(this.verts); + } + + public inertia(mass: number): number { + return Manager.inertiaForPoly(mass, this.verts, new Phaser.Vec2); + } + + public cacheData(xf:Transform) { + + this.bounds.clear(); + + var numVerts = this.verts.length; + + //console.log('shapePoly cacheData', numVerts); + + if (numVerts == 0) + { + return; + } + + for (var i = 0; i < numVerts; i++) + { + Phaser.TransformUtils.transform(xf, this.tverts[i], this.tverts[i]); + //this.tverts[i] = xf.transform(this.verts[i]); + } + + if (numVerts < 2) + { + this.bounds.addPoint(this.tverts[0]); + return; + } + + for (var i = 0; i < numVerts; i++) + { + var a = this.tverts[i]; + var b = this.tverts[(i + 1) % numVerts]; + + var n = Phaser.Vec2Utils.normalize(Phaser.Vec2Utils.perp(Phaser.Vec2Utils.subtract(a, b))); + + this.tplanes[i].n = n; + this.tplanes[i].d = Phaser.Vec2Utils.dot(n, a); + + this.bounds.addPoint(a); + } + + } + + public pointQuery(p: Phaser.Vec2): bool { + + if (!this.bounds.containPoint(p)) + { + return false; + } + + return this.containPoint(p); + } + + public findVertexByPoint(p:Phaser.Vec2, minDist: number): number { + + var dsq = minDist * minDist; + + for (var i = 0; i < this.tverts.length; i++) + { + if (Phaser.Vec2Utils.distanceSq(this.tverts[i], p) < dsq) + { + return i; + } + } + + return -1; + } + + public findEdgeByPoint(p: Phaser.Vec2, minDist: number): number { + + var dsq = minDist * minDist; + var numVerts = this.tverts.length; + + for (var i = 0; i < this.tverts.length; i++) + { + var v1 = this.tverts[i]; + var v2 = this.tverts[(i + 1) % numVerts]; + var n = this.tplanes[i].n; + + var dtv1 = Phaser.Vec2Utils.cross(v1, n); + var dtv2 = Phaser.Vec2Utils.cross(v2, n); + var dt = Phaser.Vec2Utils.cross(p, n); + + if (dt > dtv1) + { + if (Phaser.Vec2Utils.distanceSq(v1, p) < dsq) + { + return i; + } + } + else if (dt < dtv2) + { + if (Phaser.Vec2Utils.distanceSq(v2, p) < dsq) + { + return i; + } + } + else + { + var dist = Phaser.Vec2Utils.dot(n, p) - Phaser.Vec2Utils.dot(n, v1); + + if (dist * dist < dsq) + { + return i; + } + } + } + + return -1; + + } + + public distanceOnPlane(n, d) { + + var min = 999999; + + for (var i = 0; i < this.verts.length; i++) + { + min = Math.min(min, Phaser.Vec2Utils.dot(n, this.tverts[i])); + } + + return min - d; + + } + + public containPoint(p) { + + for (var i = 0; i < this.verts.length; i++) + { + var plane = this.tplanes[i]; + + if (Phaser.Vec2Utils.dot(plane.n, p) - plane.d > 0) + { + return false; + } + } + + return true; + + } + + public containPointPartial(p, n) { + + for (var i = 0; i < this.verts.length; i++) + { + var plane = this.tplanes[i]; + + if (Phaser.Vec2Utils.dot(plane.n, n) < 0.0001) + { + continue; + } + + if (Phaser.Vec2Utils.dot(plane.n, p) - plane.d > 0) + { + return false; + } + } + + return true; + } + + } + +} \ No newline at end of file diff --git a/Phaser/physics/advanced/shapes/Segment.ts b/Phaser/physics/advanced/shapes/Segment.ts new file mode 100644 index 00000000..51ebc470 --- /dev/null +++ b/Phaser/physics/advanced/shapes/Segment.ts @@ -0,0 +1,204 @@ +/// +/// +/// +/// +/// + +/** +* Phaser - Advanced Physics - Shapes - Segment +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ + +module Phaser.Physics.Advanced.Shapes { + + export class Segment extends Phaser.Physics.Advanced.Shape implements IShape { + + constructor(a, b, radius: number) { + + super(Manager.SHAPE_TYPE_SEGMENT); + + this.a = a.duplicate(); + this.b = b.duplicate(); + this.radius = radius; + + this.normal = Phaser.Vec2Utils.perp(Phaser.Vec2Utils.subtract(b, a)); + this.normal.normalize(); + + this.ta = new Phaser.Vec2; + this.tb = new Phaser.Vec2; + this.tn = new Phaser.Vec2; + + this.finishVerts(); + + } + + public a: Phaser.Vec2; + public b: Phaser.Vec2; + public radius: number; + + public normal: Phaser.Vec2; + public ta: Phaser.Vec2; + public tb: Phaser.Vec2; + public tn: Phaser.Vec2; + + public finishVerts() { + + this.normal = Phaser.Vec2Utils.perp(Phaser.Vec2Utils.subtract(this.b, this.a)); + this.normal.normalize(); + + this.radius = Math.abs(this.radius); + + } + + public duplicate() { + return new Phaser.Physics.Advanced.Shapes.Segment(this.a, this.b, this.radius); + } + + public recenter(c) { + this.a.subtract(c); + this.b.subtract(c); + } + + public transform(xf:Transform) { + + Phaser.TransformUtils.transform(xf, this.a, this.a); + Phaser.TransformUtils.transform(xf, this.b, this.b); + + //this.a = xf.transform(this.a); + //this.b = xf.transform(this.b); + + } + + public untransform(xf:Transform) { + + Phaser.TransformUtils.untransform(xf, this.a, this.a); + Phaser.TransformUtils.untransform(xf, this.b, this.b); + + //this.a = xf.untransform(this.a); + //this.b = xf.untransform(this.b); + + } + + public area(): number { + return Manager.areaForSegment(this.a, this.b, this.radius); + } + + public centroid(): Phaser.Vec2 { + return Manager.centroidForSegment(this.a, this.b); + } + + public inertia(mass: number): number { + return Manager.inertiaForSegment(mass, this.a, this.b); + } + + public cacheData(xf:Transform) { + + Phaser.TransformUtils.transform(xf, this.a, this.ta); + Phaser.TransformUtils.transform(xf, this.b, this.tb); + + //this.ta = xf.transform(this.a); + //this.tb = xf.transform(this.b); + + this.tn = Phaser.Vec2Utils.perp(Phaser.Vec2Utils.subtract(this.tb, this.ta)).normalize(); + + var l; + var r; + var t; + var b; + + if (this.ta.x < this.tb.x) + { + l = this.ta.x; + r = this.tb.x; + } + else + { + l = this.tb.x; + r = this.ta.x; + } + + if (this.ta.y < this.tb.y) + { + b = this.ta.y; + t = this.tb.y; + } else + { + b = this.tb.y; + t = this.ta.y; + } + + this.bounds.mins.setTo(l - this.radius, b - this.radius); + this.bounds.maxs.setTo(r + this.radius, t + this.radius); + + } + + public pointQuery(p: Phaser.Vec2): bool { + + if (!this.bounds.containPoint(p)) + { + return false; + } + + var dn = Phaser.Vec2Utils.dot(this.tn, p) - Phaser.Vec2Utils.dot(this.ta, this.tn); + var dist = Math.abs(dn); + + if (dist > this.radius) + { + return false; + } + + var dt = Phaser.Vec2Utils.cross(p, this.tn); + var dta = Phaser.Vec2Utils.cross(this.ta, this.tn); + var dtb = Phaser.Vec2Utils.cross(this.tb, this.tn); + + if (dt <= dta) + { + if (dt < dta - this.radius) + { + return false; + } + + return Phaser.Vec2Utils.distanceSq(this.ta, p) < (this.radius * this.radius); + } + else if (dt > dtb) + { + if (dt > dtb + this.radius) + { + return false; + } + + return Phaser.Vec2Utils.distanceSq(this.tb, p) < (this.radius * this.radius); + } + + return true; + } + + public findVertexByPoint(p:Phaser.Vec2, minDist: number): number { + + var dsq = minDist * minDist; + + if (Phaser.Vec2Utils.distanceSq(this.ta, p) < dsq) + { + return 0; + } + + if (Phaser.Vec2Utils.distanceSq(this.tb, p) < dsq) + { + return 1; + } + + return -1; + } + + public distanceOnPlane(n, d) { + + var a = Phaser.Vec2Utils.dot(n, this.ta) - this.radius; + var b = Phaser.Vec2Utils.dot(n, this.tb) - this.radius; + + return Math.min(a, b) - d; + } + + } + +} \ No newline at end of file diff --git a/Phaser/physics/advanced/shapes/Shape.ts b/Phaser/physics/advanced/shapes/Shape.ts new file mode 100644 index 00000000..e1260c88 --- /dev/null +++ b/Phaser/physics/advanced/shapes/Shape.ts @@ -0,0 +1,54 @@ +/// +/// +/// +/// +/// +/// + +/** +* Phaser - Advanced Physics - Shape +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ + +module Phaser.Physics.Advanced { + + export class Shape { + + constructor(type: number) { + + this.id = Phaser.Physics.Advanced.Manager.shapeCounter++; + this.type = type; + + this.elasticity = 0.0; + this.friction = 1.0; + this.density = 1; + + this.bounds = new Bounds; + + } + + public id: number; + public type: number; + public body: Body; + + // Coefficient of restitution (elasticity) + public elasticity: number; + + // Frictional coefficient + public friction: number; + + // Mass density + public density: number; + + // Axis-aligned bounding box + public bounds: Bounds; + + // Over-ridden by ShapePoly + public findEdgeByPoint(p: Phaser.Vec2, minDist: number): number { + return -1; + } + + } + +} \ No newline at end of file diff --git a/Phaser/physics/advanced/shapes/Triangle.ts b/Phaser/physics/advanced/shapes/Triangle.ts new file mode 100644 index 00000000..90147ca8 --- /dev/null +++ b/Phaser/physics/advanced/shapes/Triangle.ts @@ -0,0 +1,25 @@ +/// +/// +/// +/// +/// + +/** +* Phaser - Advanced Physics - Shapes - Triangle +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ + +module Phaser.Physics.Advanced.Shapes { + + export class Triangle extends Phaser.Physics.Advanced.Shapes.Poly { + + constructor(p1, p2, p3) { + + super( [ new Phaser.Vec2(p1.x, p1.y), new Phaser.Vec2(p2.x, p2.y), new Phaser.Vec2(p3.x, p3.y) ] ); + + } + + } + +} diff --git a/README.md b/README.md index eefeff1e..6d5fe091 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,10 @@ TODO: * Sprite collision events * See which functions in the input component can move elsewhere (utils) * Move all of the renderDebugInfo methods to the DebugUtils class +* Check bounds/edge points when sprite is only 1x1 sized :) + +* See what I can move out of Body and into a BodyUtils class. +* See about optimising Advanced Physics a lot more, so it doesn't create lots of Vec2s everywhere. V1.0.0 @@ -112,7 +116,7 @@ V1.0.0 * Added SpriteUtils.overlapsXY and overlapsPoint to check if a point is within a sprite, taking scale and rotation into account. * Added Cache.getImageKeys (and similar) to return an array of all the keys for all currently cached objects. * Added Group.bringToTop feature. Will sort the Group, move the given sprites z-index to the top and shift the rest down by one. - +* Brand new Advanced Physics system added and working! Woohoo :) diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index a4bdc215..27a5bcf9 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -186,6 +186,14 @@ aabb vs aabb 1.ts + + + + body1.ts + + + obb vs obb.ts + ballscroller.ts diff --git a/Tests/phaser.js b/Tests/phaser.js index 6826f466..0f9b2dab 100644 --- a/Tests/phaser.js +++ b/Tests/phaser.js @@ -833,6 +833,17 @@ var Phaser; function () { return (this.x * this.x) + (this.y * this.y); }; + Vec2.prototype.normalize = /** + * Normalize this vector. + * + * @return {Vec2} This for chaining. + */ + function () { + var inv = (this.x != 0 || this.y != 0) ? 1 / Math.sqrt(this.x * this.x + this.y * this.y) : 0; + this.x *= inv; + this.y *= inv; + return this; + }; Vec2.prototype.dot = /** * The dot product of two 2D vectors. * @@ -897,6 +908,18 @@ var Phaser; this.y *= scalar; return this; }; + Vec2.prototype.multiplyAddByScalar = /** + * Adds the given vector to this vector then multiplies by the given scalar. + * + * @param {Vec2} a Reference to a source Vec2 object. + * @param {number} scalar + * @return {Vec2} This for chaining. + */ + function (a, scalar) { + this.x += a.x * scalar; + this.y += a.y * scalar; + return this; + }; Vec2.prototype.divideByScalar = /** * Divide this vector by the given scalar. * @@ -1247,9 +1270,9 @@ var Phaser; Types.GEOM_LINE = 3; Types.GEOM_POLYGON = 4; Types.BODY_DISABLED = 0; - Types.BODY_DYNAMIC = 1; - Types.BODY_STATIC = 2; - Types.BODY_KINEMATIC = 3; + Types.BODY_STATIC = 1; + Types.BODY_KINETIC = 2; + Types.BODY_DYNAMIC = 3; Types.LEFT = 0x0001; Types.RIGHT = 0x0010; Types.UP = 0x0100; @@ -3890,7 +3913,7 @@ var Phaser; var Components = Phaser.Components; })(Phaser || (Phaser = {})); /// -/// +/// /** * Phaser - Vec2Utils * @@ -3961,14 +3984,49 @@ var Phaser; if (typeof out === "undefined") { out = new Phaser.Vec2(); } return out.setTo(a.x * s, a.y * s); }; + Vec2Utils.multiplyAdd = /** + * Adds two 2D vectors together and multiplies the result by the given scalar. + * + * @param {Vec2} a Reference to a source Vec2 object. + * @param {Vec2} b Reference to a source Vec2 object. + * @param {number} s Scaling value. + * @param {Vec2} out The output Vec2 that is the result of the operation. + * @return {Vec2} A Vec2 that is the sum of the two vectors added and multiplied. + */ + function multiplyAdd(a, b, s, out) { + if (typeof out === "undefined") { out = new Phaser.Vec2(); } + return out.setTo(a.x + b.x * s, a.y + b.y * s); + }; + Vec2Utils.negative = /** + * Return a negative vector. + * + * @param {Vec2} a Reference to a source Vec2 object. + * @param {Vec2} out The output Vec2 that is the result of the operation. + * @return {Vec2} A Vec2 that is the negative vector. + */ + function negative(a, out) { + if (typeof out === "undefined") { out = new Phaser.Vec2(); } + return out.setTo(-a.x, -a.y); + }; Vec2Utils.perp = /** - * Rotate a 2D vector by 90 degrees. + * Return a perpendicular vector (90 degrees rotation) * * @param {Vec2} a Reference to a source Vec2 object. * @param {Vec2} out The output Vec2 that is the result of the operation. * @return {Vec2} A Vec2 that is the scaled vector. */ function perp(a, out) { + if (typeof out === "undefined") { out = new Phaser.Vec2(); } + return out.setTo(-a.y, a.x); + }; + Vec2Utils.rperp = /** + * Return a perpendicular vector (-90 degrees rotation) + * + * @param {Vec2} a Reference to a source Vec2 object. + * @param {Vec2} out The output Vec2 that is the result of the operation. + * @return {Vec2} A Vec2 that is the scaled vector. + */ + function rperp(a, out) { if (typeof out === "undefined") { out = new Phaser.Vec2(); } return out.setTo(a.y, -a.x); }; @@ -4111,7 +4169,7 @@ var Phaser; function angleSq(a, b) { return a.subtract(b).angle(b.subtract(a)); }; - Vec2Utils.rotate = /** + Vec2Utils.rotateAroundOrigin = /** * Rotate a 2D vector around the origin to the given angle (theta). * * @param {Vec2} a Reference to a source Vec2 object. @@ -4120,12 +4178,27 @@ var Phaser; * @param {Vec2} out The output Vec2 that is the result of the operation. * @return {Vec2} A Vec2. */ - function rotate(a, b, theta, out) { + function rotateAroundOrigin(a, b, theta, out) { if (typeof out === "undefined") { out = new Phaser.Vec2(); } var x = a.x - b.x; var y = a.y - b.y; return out.setTo(x * Math.cos(theta) - y * Math.sin(theta) + b.x, x * Math.sin(theta) + y * Math.cos(theta) + b.y); }; + Vec2Utils.rotate = /** + * Rotate a 2D vector to the given angle (theta). + * + * @param {Vec2} a Reference to a source Vec2 object. + * @param {Vec2} b Reference to a source Vec2 object. + * @param {Number} theta The angle of rotation in radians. + * @param {Vec2} out The output Vec2 that is the result of the operation. + * @return {Vec2} A Vec2. + */ + function rotate(a, theta, out) { + if (typeof out === "undefined") { out = new Phaser.Vec2(); } + var c = Math.cos(theta); + var s = Math.sin(theta); + return out.setTo(a.x * c - a.y * s, a.x * s + a.y * c); + }; Vec2Utils.clone = /** * Clone a 2D vector. * @@ -5506,18 +5579,20 @@ var Phaser; } this.sort(); // What's the z index of the top most child? - var tempZ = child.z; var childIndex = this._zCounter; this._i = 0; while(this._i < this.length) { this._member = this.members[this._i++]; - if(this._i > childIndex) { - this._member.z--; - } else if(this._member.z == child.z) { - childIndex = this._i; - this._member.z = this._zCounter; + if(this._member) { + if(this._i > childIndex) { + this._member.z--; + } else if(this._member.z == child.z) { + childIndex = this._i; + this._member.z = this._zCounter; + } } } + // Maybe redundant? this.sort(); return true; }; @@ -6805,13 +6880,39 @@ var Phaser; } return null; }; - Cache.prototype.getImageKeys = function () { + Cache.prototype.getImageKeys = /** + * Returns an array containing all of the keys of Images in the Cache. + * @return {Array} The string based keys in the Cache. + */ + function () { var output = []; for(var item in this._images) { output.push(item); } return output; }; + Cache.prototype.getSoundKeys = /** + * Returns an array containing all of the keys of Sounds in the Cache. + * @return {Array} The string based keys in the Cache. + */ + function () { + var output = []; + for(var item in this._sounds) { + output.push(item); + } + return output; + }; + Cache.prototype.getTextKeys = /** + * Returns an array containing all of the keys of Text Files in the Cache. + * @return {Array} The string based keys in the Cache. + */ + function () { + var output = []; + for(var item in this._text) { + output.push(item); + } + return output; + }; Cache.prototype.destroy = /** * Clean up cache memory. */ @@ -13503,7 +13604,7 @@ var Phaser; return this._groupCounter++; }; World.prototype.boot = /** - * Called one by Game during the boot process. + * Called once by Game during the boot process. */ function () { this.group = new Phaser.Group(this._game, 0); @@ -17940,6 +18041,3566 @@ var Phaser; Phaser.Mat3Utils = Mat3Utils; })(Phaser || (Phaser = {})); /// +/// +/** +* Phaser - 2D Transform +* +* A 2D Transform +*/ +var Phaser; +(function (Phaser) { + var Transform = (function () { + /** + * Creates a new 2D Transform object. + * @class Transform + * @constructor + * @return {Transform} This object + **/ + function Transform(pos, angle) { + this.t = Phaser.Vec2Utils.clone(pos); + this.c = Math.cos(angle); + this.s = Math.sin(angle); + } + Transform.prototype.setTo = function (pos, angle) { + this.t.copyFrom(pos); + this.c = Math.cos(angle); + this.s = Math.sin(angle); + return this; + }; + Transform.prototype.setRotation = function (angle) { + this.c = Math.cos(angle); + this.s = Math.sin(angle); + return this; + }; + Transform.prototype.setPosition = function (p) { + this.t.copyFrom(p); + return this; + }; + Transform.prototype.identity = function () { + this.t.setTo(0, 0); + this.c = 1; + this.s = 0; + return this; + }; + return Transform; + })(); + Phaser.Transform = Transform; +})(Phaser || (Phaser = {})); +/// +/// +/// +/** +* Phaser - TransformUtils +* +* A collection of methods useful for manipulating and performing operations on 2D Transforms. +* +*/ +var Phaser; +(function (Phaser) { + var TransformUtils = (function () { + function TransformUtils() { } + TransformUtils.rotate = function rotate(t, v, out) { + if (typeof out === "undefined") { out = new Phaser.Vec2(); } + return out.setTo(v.x * t.c - v.y * t.s, v.x * t.s + v.y * t.c); + }; + TransformUtils.unrotate = function unrotate(t, v, out) { + if (typeof out === "undefined") { out = new Phaser.Vec2(); } + return out.setTo(v.x * t.c + v.y * t.s, -v.x * t.s + v.y * t.c); + }; + TransformUtils.transform = function transform(t, v, out) { + if (typeof out === "undefined") { out = new Phaser.Vec2(); } + return out.setTo(v.x * t.c - v.y * t.s + t.t.x, v.x * t.s + v.y * t.c + t.t.y); + }; + TransformUtils.untransform = function untransform(t, v, out) { + if (typeof out === "undefined") { out = new Phaser.Vec2(); } + var px = v.x - t.t.x; + var py = v.y - t.t.y; + return out.setTo(px * t.c + py * t.s, -px * t.s + py * t.c); + }; + return TransformUtils; + })(); + Phaser.TransformUtils = TransformUtils; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + /// + /// + /// + /// + /// + /** + * Phaser - PhysicsManager + * + * Your game only has one PhysicsManager instance and it's responsible for looking after, creating and colliding + * all of the physics objects in the world. + */ + (function (Physics) { + var ArcadePhysics = (function () { + function ArcadePhysics(game, width, height) { + this._length = 0; + /** + * @type {number} + */ + this.worldDivisions = 6; + this.game = game; + this.gravity = new Phaser.Vec2(); + this.drag = new Phaser.Vec2(); + this.bounce = new Phaser.Vec2(); + this.angularDrag = 0; + this.bounds = new Phaser.Rectangle(0, 0, width, height); + this._distance = new Phaser.Vec2(); + this._tangent = new Phaser.Vec2(); + this.members = new Phaser.Group(game); + } + ArcadePhysics.OVERLAP_BIAS = 4; + ArcadePhysics.TILE_OVERLAP = false; + ArcadePhysics.prototype.updateMotion = /* + public update() { + + this._length = this._objects.length; + + for (var i = 0; i < this._length; i++) + { + if (this._objects[i]) + { + this._objects[i].preUpdate(); + this.updateMotion(this._objects[i]); + this.collideWorld(this._objects[i]); + + for (var x = 0; x < this._length; x++) + { + if (this._objects[x] && this._objects[x] !== this._objects[i]) + { + //this.collideShapes(this._objects[i], this._objects[x]); + var r = this.NEWseparate(this._objects[i], this._objects[x]); + //console.log('sep', r); + } + } + + } + } + + } + + public render() { + + // iterate through the objects here, updating and colliding + for (var i = 0; i < this._length; i++) + { + if (this._objects[i]) + { + this._objects[i].render(this.game.stage.context); + } + } + + } + */ + function (body) { + if(body.type == Phaser.Types.BODY_DISABLED) { + return; + } + this._velocityDelta = (this.computeVelocity(body.angularVelocity, body.gravity.x, body.angularAcceleration, body.angularDrag, body.maxAngular) - body.angularVelocity) / 2; + body.angularVelocity += this._velocityDelta; + body.sprite.transform.rotation += body.angularVelocity * this.game.time.elapsed; + body.angularVelocity += this._velocityDelta; + this._velocityDelta = (this.computeVelocity(body.velocity.x, body.gravity.x, body.acceleration.x, body.drag.x) - body.velocity.x) / 2; + body.velocity.x += this._velocityDelta; + this._delta = body.velocity.x * this.game.time.elapsed; + body.velocity.x += this._velocityDelta; + //body.position.x += this._delta; + body.sprite.x += this._delta; + this._velocityDelta = (this.computeVelocity(body.velocity.y, body.gravity.y, body.acceleration.y, body.drag.y) - body.velocity.y) / 2; + body.velocity.y += this._velocityDelta; + this._delta = body.velocity.y * this.game.time.elapsed; + body.velocity.y += this._velocityDelta; + //body.position.y += this._delta; + body.sprite.y += this._delta; + }; + ArcadePhysics.prototype.computeVelocity = /** + * A tween-like function that takes a starting velocity and some other factors and returns an altered velocity. + * + * @param {number} Velocity Any component of velocity (e.g. 20). + * @param {number} Acceleration Rate at which the velocity is changing. + * @param {number} Drag Really kind of a deceleration, this is how much the velocity changes if Acceleration is not set. + * @param {number} Max An absolute value cap for the velocity. + * + * @return {number} The altered Velocity value. + */ + function (velocity, gravity, acceleration, drag, max) { + if (typeof gravity === "undefined") { gravity = 0; } + if (typeof acceleration === "undefined") { acceleration = 0; } + if (typeof drag === "undefined") { drag = 0; } + if (typeof max === "undefined") { max = 10000; } + if(acceleration !== 0) { + velocity += (acceleration + gravity) * this.game.time.elapsed; + } else if(drag !== 0) { + this._drag = drag * this.game.time.elapsed; + if(velocity - this._drag > 0) { + velocity = velocity - this._drag; + } else if(velocity + this._drag < 0) { + velocity += this._drag; + } else { + velocity = 0; + } + velocity += gravity; + } + if((velocity != 0) && (max != 10000)) { + if(velocity > max) { + velocity = max; + } else if(velocity < -max) { + velocity = -max; + } + } + return velocity; + }; + ArcadePhysics.prototype.separate = /** + * The core Collision separation method. + * @param body1 The first Physics.Body to separate + * @param body2 The second Physics.Body to separate + * @returns {boolean} Returns true if the bodies were separated, otherwise false. + */ + function (body1, body2) { + this._separatedX = this.separateBodyX(body1, body2); + this._separatedY = this.separateBodyY(body1, body2); + return this._separatedX || this._separatedY; + }; + ArcadePhysics.prototype.checkHullIntersection = function (body1, body2) { + return ((body1.hullX + body1.hullWidth > body2.hullX) && (body1.hullX < body2.hullX + body2.hullWidth) && (body1.hullY + body1.hullHeight > body2.hullY) && (body1.hullY < body2.hullY + body2.hullHeight)); + }; + ArcadePhysics.prototype.separateBodyX = /** + * 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 (body1, body2) { + // Can't separate two disabled or static objects + if((body1.type == Phaser.Types.BODY_DISABLED || body1.type == Phaser.Types.BODY_STATIC) && (body2.type == Phaser.Types.BODY_DISABLED || body2.type == Phaser.Types.BODY_STATIC)) { + return false; + } + // First, get the two object deltas + this._overlap = 0; + if(body1.deltaX != body2.deltaX) { + if(Phaser.RectangleUtils.intersects(body1.bounds, body2.bounds)) { + this._maxOverlap = body1.deltaXAbs + body2.deltaXAbs + Physics.PhysicsManager.OVERLAP_BIAS; + // If they did overlap (and can), figure out by how much and flip the corresponding flags + if(body1.deltaX > body2.deltaX) { + this._overlap = body1.bounds.right - body2.bounds.x; + if((this._overlap > this._maxOverlap) || !(body1.allowCollisions & Phaser.Types.RIGHT) || !(body2.allowCollisions & Phaser.Types.LEFT)) { + this._overlap = 0; + } else { + body1.touching |= Phaser.Types.RIGHT; + body2.touching |= Phaser.Types.LEFT; + } + } else if(body1.deltaX < body2.deltaX) { + this._overlap = body1.bounds.x - body2.bounds.width - body2.bounds.x; + if((-this._overlap > this._maxOverlap) || !(body1.allowCollisions & Phaser.Types.LEFT) || !(body2.allowCollisions & Phaser.Types.RIGHT)) { + this._overlap = 0; + } else { + body1.touching |= Phaser.Types.LEFT; + body2.touching |= Phaser.Types.RIGHT; + } + } + } + } + // Then adjust their positions and velocities accordingly (if there was any overlap) + if(this._overlap != 0) { + this._obj1Velocity = body1.velocity.x; + this._obj2Velocity = body2.velocity.x; + /** + * Dynamic = gives and receives impacts + * Static = gives but doesn't receive impacts, cannot be moved by physics + * Kinematic = gives impacts, but never receives, can be moved by physics + */ + // 2 dynamic bodies will exchange velocities + if(body1.type == Phaser.Types.BODY_DYNAMIC && body2.type == Phaser.Types.BODY_DYNAMIC) { + this._overlap *= 0.5; + body1.position.x = body1.position.x - this._overlap; + body2.position.x += this._overlap; + this._obj1NewVelocity = Math.sqrt((this._obj2Velocity * this._obj2Velocity * body2.mass) / body1.mass) * ((this._obj2Velocity > 0) ? 1 : -1); + this._obj2NewVelocity = Math.sqrt((this._obj1Velocity * this._obj1Velocity * body1.mass) / body2.mass) * ((this._obj1Velocity > 0) ? 1 : -1); + this._average = (this._obj1NewVelocity + this._obj2NewVelocity) * 0.5; + this._obj1NewVelocity -= this._average; + this._obj2NewVelocity -= this._average; + body1.velocity.x = this._average + this._obj1NewVelocity * body1.bounce.x; + body2.velocity.x = this._average + this._obj2NewVelocity * body2.bounce.x; + } else if(body2.type != Phaser.Types.BODY_DYNAMIC) { + // Body 2 is Static or Kinematic + this._overlap *= 2; + body1.position.x -= this._overlap; + body1.velocity.x = this._obj2Velocity - this._obj1Velocity * body1.bounce.x; + } else if(body1.type != Phaser.Types.BODY_DYNAMIC) { + // Body 1 is Static or Kinematic + this._overlap *= 2; + body2.position.x += this._overlap; + body2.velocity.x = this._obj1Velocity - this._obj2Velocity * body2.bounce.x; + } + return true; + } else { + return false; + } + }; + ArcadePhysics.prototype.separateBodyY = /** + * 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 (body1, body2) { + // Can't separate two immovable objects + if((body1.type == Phaser.Types.BODY_DISABLED || body1.type == Phaser.Types.BODY_STATIC) && (body2.type == Phaser.Types.BODY_DISABLED || body2.type == Phaser.Types.BODY_STATIC)) { + return false; + } + // First, get the two object deltas + this._overlap = 0; + if(body1.deltaY != body2.deltaY) { + if(Phaser.RectangleUtils.intersects(body1.bounds, body2.bounds)) { + // This is the only place to use the DeltaAbs values + this._maxOverlap = body1.deltaYAbs + body2.deltaYAbs + Physics.PhysicsManager.OVERLAP_BIAS; + // If they did overlap (and can), figure out by how much and flip the corresponding flags + if(body1.deltaY > body2.deltaY) { + this._overlap = body1.bounds.bottom - body2.bounds.y; + if((this._overlap > this._maxOverlap) || !(body1.allowCollisions & Phaser.Types.DOWN) || !(body2.allowCollisions & Phaser.Types.UP)) { + this._overlap = 0; + } else { + body1.touching |= Phaser.Types.DOWN; + body2.touching |= Phaser.Types.UP; + } + } else if(body1.deltaY < body2.deltaY) { + this._overlap = body1.bounds.y - body2.bounds.height - body2.bounds.y; + if((-this._overlap > this._maxOverlap) || !(body1.allowCollisions & Phaser.Types.UP) || !(body2.allowCollisions & Phaser.Types.DOWN)) { + this._overlap = 0; + } else { + body1.touching |= Phaser.Types.UP; + body2.touching |= Phaser.Types.DOWN; + } + } + } + } + // Then adjust their positions and velocities accordingly (if there was any overlap) + if(this._overlap != 0) { + this._obj1Velocity = body1.velocity.y; + this._obj2Velocity = body2.velocity.y; + /** + * Dynamic = gives and receives impacts + * Static = gives but doesn't receive impacts, cannot be moved by physics + * Kinematic = gives impacts, but never receives, can be moved by physics + */ + if(body1.type == Phaser.Types.BODY_DYNAMIC && body2.type == Phaser.Types.BODY_DYNAMIC) { + this._overlap *= 0.5; + body1.position.y = body1.position.y - this._overlap; + body2.position.y += this._overlap; + this._obj1NewVelocity = Math.sqrt((this._obj2Velocity * this._obj2Velocity * body2.mass) / body1.mass) * ((this._obj2Velocity > 0) ? 1 : -1); + this._obj2NewVelocity = Math.sqrt((this._obj1Velocity * this._obj1Velocity * body1.mass) / body2.mass) * ((this._obj1Velocity > 0) ? 1 : -1); + var average = (this._obj1NewVelocity + this._obj2NewVelocity) * 0.5; + this._obj1NewVelocity -= average; + this._obj2NewVelocity -= average; + body1.velocity.y = average + this._obj1NewVelocity * body1.bounce.y; + body2.velocity.y = average + this._obj2NewVelocity * body2.bounce.y; + } else if(body2.type != Phaser.Types.BODY_DYNAMIC) { + this._overlap *= 2; + body1.position.y -= this._overlap; + body1.velocity.y = this._obj2Velocity - this._obj1Velocity * body1.bounce.y; + // This is special case code that handles things like horizontal moving platforms you can ride + //if (body2.parent.active && body2.moves && (body1.deltaY > body2.deltaY)) + if(body2.sprite.active && (body1.deltaY > body2.deltaY)) { + body1.position.x += body2.position.x - body2.oldPosition.x; + } + } else if(body1.type != Phaser.Types.BODY_DYNAMIC) { + this._overlap *= 2; + body2.position.y += this._overlap; + body2.velocity.y = this._obj1Velocity - this._obj2Velocity * body2.bounce.y; + // This is special case code that handles things like horizontal moving platforms you can ride + //if (object1.active && body1.moves && (body1.deltaY < body2.deltaY)) + if(body1.sprite.active && (body1.deltaY < body2.deltaY)) { + body2.position.x += body1.position.x - body1.oldPosition.x; + } + } + return true; + } else { + return false; + } + }; + ArcadePhysics.prototype.overlap = /* + private TILEseparate(shapeA: IPhysicsShape, shapeB: IPhysicsShape, distance: Vec2, tangent: Vec2) { + + if (tangent.x == 1) + { + console.log('1 The left side of ShapeA hit the right side of ShapeB', Math.floor(distance.x)); + shapeA.physics.touching |= Phaser.Types.LEFT; + shapeB.physics.touching |= Phaser.Types.RIGHT; + } + else if (tangent.x == -1) + { + console.log('2 The right side of ShapeA hit the left side of ShapeB', Math.floor(distance.x)); + shapeA.physics.touching |= Phaser.Types.RIGHT; + shapeB.physics.touching |= Phaser.Types.LEFT; + } + + if (tangent.y == 1) + { + console.log('3 The top of ShapeA hit the bottom of ShapeB', Math.floor(distance.y)); + shapeA.physics.touching |= Phaser.Types.UP; + shapeB.physics.touching |= Phaser.Types.DOWN; + } + else if (tangent.y == -1) + { + console.log('4 The bottom of ShapeA hit the top of ShapeB', Math.floor(distance.y)); + shapeA.physics.touching |= Phaser.Types.DOWN; + shapeB.physics.touching |= Phaser.Types.UP; + } + + + // only apply collision response forces if the object is travelling into, and not out of, the collision + var dot = Vec2Utils.dot(shapeA.physics.velocity, tangent); + + if (dot < 0) + { + console.log('in to', dot); + + // Apply horizontal bounce + if (shapeA.physics.bounce.x > 0) + { + shapeA.physics.velocity.x *= -(shapeA.physics.bounce.x); + } + else + { + shapeA.physics.velocity.x = 0; + } + // Apply horizontal bounce + if (shapeA.physics.bounce.y > 0) + { + shapeA.physics.velocity.y *= -(shapeA.physics.bounce.y); + } + else + { + shapeA.physics.velocity.y = 0; + } + } + else + { + console.log('out of', dot); + } + + shapeA.position.x += Math.floor(distance.x); + //shapeA.bounds.x += Math.floor(distance.x); + + shapeA.position.y += Math.floor(distance.y); + //shapeA.bounds.y += distance.y; + + console.log('------------------------------------------------'); + + } + + private collideWorld(shape:IPhysicsShape) { + + // Collide on the x-axis + this._distance.x = shape.world.bounds.x - (shape.position.x - shape.bounds.halfWidth); + + if (0 < this._distance.x) + { + // Hit Left + this._tangent.setTo(1, 0); + this.separateXWall(shape, this._distance, this._tangent); + } + else + { + this._distance.x = (shape.position.x + shape.bounds.halfWidth) - shape.world.bounds.right; + + if (0 < this._distance.x) + { + // Hit Right + this._tangent.setTo(-1, 0); + this._distance.reverse(); + this.separateXWall(shape, this._distance, this._tangent); + } + } + + // Collide on the y-axis + this._distance.y = shape.world.bounds.y - (shape.position.y - shape.bounds.halfHeight); + + if (0 < this._distance.y) + { + // Hit Top + this._tangent.setTo(0, 1); + this.separateYWall(shape, this._distance, this._tangent); + } + else + { + this._distance.y = (shape.position.y + shape.bounds.halfHeight) - shape.world.bounds.bottom; + + if (0 < this._distance.y) + { + // Hit Bottom + this._tangent.setTo(0, -1); + this._distance.reverse(); + this.separateYWall(shape, this._distance, this._tangent); + } + } + + } + + private separateX(shapeA: IPhysicsShape, shapeB: IPhysicsShape, distance: Vec2, tangent: Vec2) { + + if (tangent.x == 1) + { + console.log('The left side of ShapeA hit the right side of ShapeB', distance.x); + shapeA.physics.touching |= Phaser.Types.LEFT; + shapeB.physics.touching |= Phaser.Types.RIGHT; + } + else + { + console.log('The right side of ShapeA hit the left side of ShapeB', distance.x); + shapeA.physics.touching |= Phaser.Types.RIGHT; + shapeB.physics.touching |= Phaser.Types.LEFT; + } + + // collision edges + //shapeA.oH = tangent.x; + + // only apply collision response forces if the object is travelling into, and not out of, the collision + if (Vec2Utils.dot(shapeA.physics.velocity, tangent) < 0) + { + // Apply horizontal bounce + if (shapeA.physics.bounce.x > 0) + { + shapeA.physics.velocity.x *= -(shapeA.physics.bounce.x); + } + else + { + shapeA.physics.velocity.x = 0; + } + } + + shapeA.position.x += distance.x; + shapeA.bounds.x += distance.x; + + } + + private separateY(shapeA: IPhysicsShape, shapeB: IPhysicsShape, distance: Vec2, tangent: Vec2) { + + if (tangent.y == 1) + { + console.log('The top of ShapeA hit the bottom of ShapeB', distance.y); + shapeA.physics.touching |= Phaser.Types.UP; + shapeB.physics.touching |= Phaser.Types.DOWN; + } + else + { + console.log('The bottom of ShapeA hit the top of ShapeB', distance.y); + shapeA.physics.touching |= Phaser.Types.DOWN; + shapeB.physics.touching |= Phaser.Types.UP; + } + + // collision edges + //shapeA.oV = tangent.y; + + // only apply collision response forces if the object is travelling into, and not out of, the collision + if (Vec2Utils.dot(shapeA.physics.velocity, tangent) < 0) + { + // Apply horizontal bounce + if (shapeA.physics.bounce.y > 0) + { + shapeA.physics.velocity.y *= -(shapeA.physics.bounce.y); + } + else + { + shapeA.physics.velocity.y = 0; + } + } + + shapeA.position.y += distance.y; + shapeA.bounds.y += distance.y; + + } + + private separateXWall(shapeA: IPhysicsShape, distance: Vec2, tangent: Vec2) { + + if (tangent.x == 1) + { + console.log('The left side of ShapeA hit the right side of ShapeB', distance.x); + shapeA.physics.touching |= Phaser.Types.LEFT; + } + else + { + console.log('The right side of ShapeA hit the left side of ShapeB', distance.x); + shapeA.physics.touching |= Phaser.Types.RIGHT; + } + + // collision edges + //shapeA.oH = tangent.x; + + // only apply collision response forces if the object is travelling into, and not out of, the collision + if (Vec2Utils.dot(shapeA.physics.velocity, tangent) < 0) + { + // Apply horizontal bounce + if (shapeA.physics.bounce.x > 0) + { + shapeA.physics.velocity.x *= -(shapeA.physics.bounce.x); + } + else + { + shapeA.physics.velocity.x = 0; + } + } + + shapeA.position.x += distance.x; + + } + + private separateYWall(shapeA: IPhysicsShape, distance: Vec2, tangent: Vec2) { + + if (tangent.y == 1) + { + console.log('The top of ShapeA hit the bottom of ShapeB', distance.y); + shapeA.physics.touching |= Phaser.Types.UP; + } + else + { + console.log('The bottom of ShapeA hit the top of ShapeB', distance.y); + shapeA.physics.touching |= Phaser.Types.DOWN; + } + + // collision edges + //shapeA.oV = tangent.y; + + // only apply collision response forces if the object is travelling into, and not out of, the collision + if (Vec2Utils.dot(shapeA.physics.velocity, tangent) < 0) + { + // Apply horizontal bounce + if (shapeA.physics.bounce.y > 0) + { + shapeA.physics.velocity.y *= -(shapeA.physics.bounce.y); + } + else + { + shapeA.physics.velocity.y = 0; + } + } + + shapeA.position.y += distance.y; + + } + */ + /** + * Checks for overlaps between two objects using the world QuadTree. Can be Sprite vs. Sprite, Sprite 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 Sprite or Group to check. If null the world.group is used. + * @param object2 The second Sprite or Group to check. + * @param notifyCallback A callback function that is called if the objects overlap. The two objects will be passed to this function in the same order in which you passed them to Collision.overlap. + * @param processCallback A callback function that lets you perform additional checks against the two objects if they overlap. If this is set then notifyCallback will only be called if processCallback returns true. + * @param context The context in which the callbacks will be called + * @returns {boolean} true if the objects overlap, otherwise false. + */ + function (object1, object2, notifyCallback, processCallback, context) { + if (typeof object1 === "undefined") { object1 = null; } + if (typeof object2 === "undefined") { object2 = null; } + if (typeof notifyCallback === "undefined") { notifyCallback = null; } + if (typeof processCallback === "undefined") { processCallback = null; } + if (typeof context === "undefined") { context = null; } + /* + if (object1 == null) + { + object1 = this.game.world.group; + } + + if (object2 == object1) + { + object2 = null; + } + + QuadTree.divisions = this.worldDivisions; + + this._quadTree = new Phaser.QuadTree(this, this.bounds.x, this.bounds.y, this.bounds.width, this.bounds.height); + + this._quadTree.load(object1, object2, notifyCallback, processCallback, context); + + this._quadTreeResult = this._quadTree.execute(); + + console.log('over', this._quadTreeResult); + + this._quadTree.destroy(); + + this._quadTree = null; + + return this._quadTreeResult; + */ + return false; + }; + ArcadePhysics.prototype.separateTile = /** + * Collision resolution specifically for GameObjects vs. Tiles. + * @param object The GameObject to separate + * @param tile The Tile to separate + * @returns {boolean} Whether the objects in fact touched and were separated + */ + function (object, x, y, width, height, mass, collideLeft, collideRight, collideUp, collideDown, separateX, separateY) { + //var separatedX: bool = this.separateTileX(object, x, y, width, height, mass, collideLeft, collideRight, separateX); + //var separatedY: bool = this.separateTileY(object, x, y, width, height, mass, collideUp, collideDown, separateY); + //return separatedX || separatedY; + return false; + }; + return ArcadePhysics; + })(); + Physics.ArcadePhysics = ArcadePhysics; + /** + * 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 separateTileX(object:Sprite, 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; + 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: Rectangle = new Rectangle(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: number = objDeltaAbs + Collision.OVERLAP_BIAS; + + // If they did overlap (and can), figure out by how much and flip the corresponding flags + if (objDelta > 0) + { + overlap = object.x + object.width - x; + + if ((overlap > maxOverlap) || !(object.allowCollisions & Collision.RIGHT) || collideLeft == false) + { + overlap = 0; + } + else + { + object.touching |= Collision.RIGHT; + } + } + else if (objDelta < 0) + { + overlap = object.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) + { + //console.log(' + 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 separateTileY(object: Sprite, 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 (objDelta != 0) + { + // Check if the Y hulls actually overlap + var objDeltaAbs: number = (objDelta > 0) ? objDelta : -objDelta; + var objBounds: Rectangle = new Rectangle(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)) + { + var maxOverlap: number = objDeltaAbs + Collision.OVERLAP_BIAS; + + // If they did overlap (and can), figure out by how much and flip the corresponding flags + if (objDelta > 0) + { + overlap = object.y + object.height - y; + + if ((overlap > maxOverlap) || !(object.allowCollisions & Collision.DOWN) || collideUp == false) + { + overlap = 0; + } + else + { + object.touching |= Collision.DOWN; + } + } + else if (objDelta < 0) + { + overlap = object.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 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:Sprite, 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: Rectangle = new Rectangle(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 NEWseparateTileY(object: Sprite, 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: Rectangle = new Rectangle(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; + } + } + */ + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - Joint + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + (function (Advanced) { + var Joint = (function () { + function Joint(type, body1, body2, collideConnected) { + this.id = Phaser.Physics.Advanced.Manager.jointCounter++; + this.type = type; + this.body1 = body1; + this.body2 = body2; + this.collideConnected = collideConnected; + this.maxForce = 9999999999; + this.breakable = false; + } + Joint.prototype.getWorldAnchor1 = function () { + return this.body1.getWorldPoint(this.anchor1); + }; + Joint.prototype.getWorldAnchor2 = function () { + return this.body2.getWorldPoint(this.anchor2); + }; + Joint.prototype.setWorldAnchor1 = function (anchor1) { + this.anchor1 = this.body1.getLocalPoint(anchor1); + }; + Joint.prototype.setWorldAnchor2 = function (anchor2) { + this.anchor2 = this.body2.getLocalPoint(anchor2); + }; + return Joint; + })(); + Advanced.Joint = Joint; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + /// + /// + /// + /** + * Phaser - Advanced Physics Manager + * + * Your game only has one PhysicsManager instance and it's responsible for looking after, creating and colliding + * all of the physics objects in the world. + */ + (function (Advanced) { + var Manager = (function () { + function Manager(game) { + this.lastTime = Date.now(); + this.frameRateHz = 60; + this.timeDelta = 0; + this.paused = false; + this.step = false; + // step through the simulation (i.e. per click) + this.velocityIterations = 8; + this.positionIterations = 4; + this.allowSleep = true; + this.warmStarting = true; + this.game = game; + this.space = new Advanced.Space(); + Manager.collision = new Advanced.Collision(); + } + Manager.SHAPE_TYPE_CIRCLE = 0; + Manager.SHAPE_TYPE_SEGMENT = 1; + Manager.SHAPE_TYPE_POLY = 2; + Manager.SHAPE_NUM_TYPES = 3; + Manager.JOINT_TYPE_ANGLE = 0; + Manager.JOINT_TYPE_REVOLUTE = 1; + Manager.JOINT_TYPE_WELD = 2; + Manager.JOINT_TYPE_WHEEL = 3; + Manager.JOINT_TYPE_PRISMATIC = 4; + Manager.JOINT_TYPE_DISTANCE = 5; + Manager.JOINT_TYPE_ROPE = 6; + Manager.JOINT_TYPE_MOUSE = 7; + Manager.JOINT_LINEAR_SLOP = 0.0008; + Manager.JOINT_ANGULAR_SLOP = 2 * Phaser.GameMath.DEG_TO_RAD; + Manager.JOINT_MAX_LINEAR_CORRECTION = 0.5; + Manager.JOINT_MAX_ANGULAR_CORRECTION = 8 * Phaser.GameMath.DEG_TO_RAD; + Manager.JOINT_LIMIT_STATE_INACTIVE = 0; + Manager.JOINT_LIMIT_STATE_AT_LOWER = 1; + Manager.JOINT_LIMIT_STATE_AT_UPPER = 2; + Manager.JOINT_LIMIT_STATE_EQUAL_LIMITS = 3; + Manager.CONTACT_SOLVER_COLLISION_SLOP = 0.0008; + Manager.CONTACT_SOLVER_BAUMGARTE = 0.28; + Manager.CONTACT_SOLVER_MAX_LINEAR_CORRECTION = 1; + Manager.bodyCounter = 0; + Manager.jointCounter = 0; + Manager.shapeCounter = 0; + Manager.prototype.update = function () { + var time = Date.now(); + var frameTime = (time - this.lastTime) / 1000; + this.lastTime = time; + // if rAf - why? + frameTime = Math.floor(frameTime * 60 + 0.5) / 60; + //if (!mouseDown) + //{ + // var p = canvasToWorld(mousePosition); + // var body = space.findBodyByPoint(p); + // //domCanvas.style.cursor = body ? "pointer" : "default"; + //} + if(!this.paused || this.step) { + var h = 1 / this.frameRateHz; + this.timeDelta += frameTime; + if(this.step) { + this.step = false; + this.timeDelta = h; + } + for(var maxSteps = 4; maxSteps > 0 && this.timeDelta >= h; maxSteps--) { + this.space.step(h, this.velocityIterations, this.positionIterations, this.warmStarting, this.allowSleep); + this.timeDelta -= h; + } + if(this.timeDelta > h) { + this.timeDelta = 0; + } + //if (sceneIndex < demoArr.length) + //{ + // demo = demoArr[sceneIndex]; + // demo.runFrame(); + //} + } + //frameCount++; + }; + Manager.prototype.pixelsToMeters = function (value) { + return value * 0.02; + }; + Manager.prototype.metersToPixels = function (value) { + return value * 50; + }; + Manager.pixelsToMeters = function pixelsToMeters(value) { + return value * 0.02; + }; + Manager.metersToPixels = function metersToPixels(value) { + return value * 50; + }; + Manager.p2m = function p2m(value) { + return value * 0.02; + }; + Manager.m2p = function m2p(value) { + return value * 50; + }; + Manager.areaForCircle = function areaForCircle(radius_outer, radius_inner) { + return Math.PI * (radius_outer * radius_outer - radius_inner * radius_inner); + }; + Manager.inertiaForCircle = function inertiaForCircle(mass, center, radius_outer, radius_inner) { + return mass * ((radius_outer * radius_outer + radius_inner * radius_inner) * 0.5 + center.lengthSq()); + }; + Manager.areaForSegment = function areaForSegment(a, b, radius) { + return radius * (Math.PI * radius + 2 * Phaser.Vec2Utils.distance(a, b)); + }; + Manager.centroidForSegment = function centroidForSegment(a, b) { + return Phaser.Vec2Utils.scale(Phaser.Vec2Utils.add(a, b), 0.5); + }; + Manager.inertiaForSegment = function inertiaForSegment(mass, a, b) { + var distsq = Phaser.Vec2Utils.distanceSq(b, a); + var offset = Phaser.Vec2Utils.scale(Phaser.Vec2Utils.add(a, b), 0.5); + return mass * (distsq / 12 + offset.lengthSq()); + }; + Manager.areaForPoly = function areaForPoly(verts) { + var area = 0; + for(var i = 0; i < verts.length; i++) { + area += Phaser.Vec2Utils.cross(verts[i], verts[(i + 1) % verts.length]); + } + return area / 2; + }; + Manager.centroidForPoly = function centroidForPoly(verts) { + var area = 0; + var vsum = new Phaser.Vec2(); + for(var i = 0; i < verts.length; i++) { + var v1 = verts[i]; + var v2 = verts[(i + 1) % verts.length]; + var cross = Phaser.Vec2Utils.cross(v1, v2); + area += cross; + // SO many vecs created here - unroll these bad boys + vsum.add(Phaser.Vec2Utils.scale(Phaser.Vec2Utils.add(v1, v2), cross)); + } + return Phaser.Vec2Utils.scale(vsum, 1 / (3 * area)); + }; + Manager.inertiaForPoly = function inertiaForPoly(mass, verts, offset) { + var sum1 = 0; + var sum2 = 0; + for(var i = 0; i < verts.length; i++) { + var v1 = Phaser.Vec2Utils.add(verts[i], offset); + var v2 = Phaser.Vec2Utils.add(verts[(i + 1) % verts.length], offset); + var a = Phaser.Vec2Utils.cross(v2, v1); + var b = Phaser.Vec2Utils.dot(v1, v1) + Phaser.Vec2Utils.dot(v1, v2) + Phaser.Vec2Utils.dot(v2, v2); + sum1 += a * b; + sum2 += a; + } + return (mass * sum1) / (6 * sum2); + }; + Manager.inertiaForBox = function inertiaForBox(mass, w, h) { + return mass * (w * w + h * h) / 12; + }; + Manager.createConvexHull = // Create the convex hull using the Gift wrapping algorithm (http://en.wikipedia.org/wiki/Gift_wrapping_algorithm) + function createConvexHull(points) { + // Find the right most point on the hull + var i0 = 0; + var x0 = points[0].x; + for(var i = 1; i < points.length; i++) { + var x = points[i].x; + if(x > x0 || (x == x0 && points[i].y < points[i0].y)) { + i0 = i; + x0 = x; + } + } + var n = points.length; + var hull = []; + var m = 0; + var ih = i0; + while(1) { + hull[m] = ih; + var ie = 0; + for(var j = 1; j < n; j++) { + if(ie == ih) { + ie = j; + continue; + } + var r = Phaser.Vec2Utils.subtract(points[ie], points[hull[m]]); + var v = Phaser.Vec2Utils.subtract(points[j], points[hull[m]]); + var c = Phaser.Vec2Utils.cross(r, v); + if(c < 0) { + ie = j; + } + // Collinearity check + if(c == 0 && v.lengthSq() > r.lengthSq()) { + ie = j; + } + } + m++; + ih = ie; + if(ie == i0) { + break; + } + } + // Copy vertices + var newPoints = []; + for(var i = 0; i < m; ++i) { + newPoints.push(points[hull[i]]); + } + return newPoints; + }; + return Manager; + })(); + Advanced.Manager = Manager; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + /// + /// + /// + /** + * Phaser - 2D AABB + * + * A 2D AABB object + */ + (function (Advanced) { + var Bounds = (function () { + /** + * Creates a new 2D AABB object. + * @class Bounds + * @constructor + * @return {Bounds} This object + **/ + function Bounds(mins, maxs) { + if (typeof mins === "undefined") { mins = null; } + if (typeof maxs === "undefined") { maxs = null; } + if(mins) { + this.mins = Phaser.Vec2Utils.clone(mins); + } else { + this.mins = new Phaser.Vec2(999999, 999999); + } + if(maxs) { + this.maxs = Phaser.Vec2Utils.clone(maxs); + } else { + this.maxs = new Phaser.Vec2(999999, 999999); + } + } + Bounds.prototype.toString = function () { + return [ + "mins:", + this.mins.toString(), + "maxs:", + this.maxs.toString() + ].join(" "); + }; + Bounds.prototype.setTo = function (mins, maxs) { + this.mins.setTo(mins.x, mins.y); + this.maxs.setTo(maxs.x, maxs.y); + }; + Bounds.prototype.copy = function (b) { + this.mins.copyFrom(b.mins); + this.maxs.copyFrom(b.maxs); + return this; + }; + Bounds.prototype.clear = function () { + this.mins.setTo(999999, 999999); + this.maxs.setTo(-999999, -999999); + return this; + }; + Bounds.prototype.isEmpty = function () { + return (this.mins.x > this.maxs.x || this.mins.y > this.maxs.y); + }; + Bounds.prototype.getPerimeter = /* + public getCenter() { + return vec2.scale(vec2.add(this.mins, this.maxs), 0.5); + } + + public getExtent() { + return vec2.scale(vec2.sub(this.maxs, this.mins), 0.5); + } + */ + function () { + return (this.maxs.x - this.mins.x + this.maxs.y - this.mins.y) * 2; + }; + Bounds.prototype.addPoint = function (p) { + if(this.mins.x > p.x) { + this.mins.x = p.x; + } + if(this.maxs.x < p.x) { + this.maxs.x = p.x; + } + if(this.mins.y > p.y) { + this.mins.y = p.y; + } + if(this.maxs.y < p.y) { + this.maxs.y = p.y; + } + return this; + }; + Bounds.prototype.addBounds = function (b) { + if(this.mins.x > b.mins.x) { + this.mins.x = b.mins.x; + } + if(this.maxs.x < b.maxs.x) { + this.maxs.x = b.maxs.x; + } + if(this.mins.y > b.mins.y) { + this.mins.y = b.mins.y; + } + if(this.maxs.y < b.maxs.y) { + this.maxs.y = b.maxs.y; + } + return this; + }; + Bounds.prototype.addBounds2 = function (mins, maxs) { + if(this.mins.x > mins.x) { + this.mins.x = mins.x; + } + if(this.maxs.x < maxs.x) { + this.maxs.x = maxs.x; + } + if(this.mins.y > mins.y) { + this.mins.y = mins.y; + } + if(this.maxs.y < maxs.y) { + this.maxs.y = maxs.y; + } + return this; + }; + Bounds.prototype.addExtents = function (center, extent_x, extent_y) { + if(this.mins.x > center.x - extent_x) { + this.mins.x = center.x - extent_x; + } + if(this.maxs.x < center.x + extent_x) { + this.maxs.x = center.x + extent_x; + } + if(this.mins.y > center.y - extent_y) { + this.mins.y = center.y - extent_y; + } + if(this.maxs.y < center.y + extent_y) { + this.maxs.y = center.y + extent_y; + } + return this; + }; + Bounds.prototype.expand = function (ax, ay) { + this.mins.x -= ax; + this.mins.y -= ay; + this.maxs.x += ax; + this.maxs.y += ay; + return this; + }; + Bounds.prototype.containPoint = function (p) { + if(p.x < this.mins.x || p.x > this.maxs.x || p.y < this.mins.y || p.y > this.maxs.y) { + return false; + } + return true; + }; + Bounds.prototype.intersectsBounds = function (b) { + if(this.mins.x > b.maxs.x || this.maxs.x < b.mins.x || this.mins.y > b.maxs.y || this.maxs.y < b.mins.y) { + return false; + } + return true; + }; + Bounds.expand = function expand(b, ax, ay) { + var b = new Bounds(b.mins, b.maxs); + b.expand(ax, ay); + return b; + }; + return Bounds; + })(); + Advanced.Bounds = Bounds; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + /// + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - Shape + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + (function (Advanced) { + var Shape = (function () { + function Shape(type) { + this.id = Phaser.Physics.Advanced.Manager.shapeCounter++; + this.type = type; + this.elasticity = 0.0; + this.friction = 1.0; + this.density = 1; + this.bounds = new Advanced.Bounds(); + } + Shape.prototype.findEdgeByPoint = // Over-ridden by ShapePoly + function (p, minDist) { + return -1; + }; + return Shape; + })(); + Advanced.Shape = Shape; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - Contact + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + (function (Advanced) { + var Contact = (function () { + function Contact(p, n, d, hash) { + this.hash = hash; + this.point = p; + this.normal = n; + this.depth = d; + this.lambdaNormal = 0; + this.lambdaTangential = 0; + this.r1 = new Phaser.Vec2(); + this.r2 = new Phaser.Vec2(); + this.r1_local = new Phaser.Vec2(); + this.r2_local = new Phaser.Vec2(); + } + return Contact; + })(); + Advanced.Contact = Contact; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + /// + /// + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - ContactSolver + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + //------------------------------------------------------------------------------------------------- + // Contact Constraint + // + // Non-penetration constraint: + // C = dot(p2 - p1, n) + // Cdot = dot(v2 - v1, n) + // J = [ -n, -cross(r1, n), n, cross(r2, n) ] + // + // impulse = JT * lambda = [ -n * lambda, -cross(r1, n) * lambda, n * lambda, cross(r1, n) * lambda ] + // + // Friction constraint: + // C = dot(p2 - p1, t) + // Cdot = dot(v2 - v1, t) + // J = [ -t, -cross(r1, t), t, cross(r2, t) ] + // + // impulse = JT * lambda = [ -t * lambda, -cross(r1, t) * lambda, t * lambda, cross(r1, t) * lambda ] + // + // NOTE: lambda is an impulse in constraint space. + //------------------------------------------------------------------------------------------------- + (function (Advanced) { + var ContactSolver = (function () { + function ContactSolver(shape1, shape2) { + //console.log('ContactSolver super'); + this.shape1 = shape1; + this.shape2 = shape2; + this.contacts = []; + this.elasticity = 1; + this.friction = 1; + } + ContactSolver.prototype.update = function (newContactArr) { + for(var i = 0; i < newContactArr.length; i++) { + var newContact = newContactArr[i]; + var k = -1; + for(var j = 0; j < this.contacts.length; j++) { + if(newContact.hash == this.contacts[j].hash) { + k = j; + break; + } + } + if(k > -1) { + newContact.lambdaNormal = this.contacts[k].lambdaNormal; + newContact.lambdaTangential = this.contacts[k].lambdaTangential; + } + } + this.contacts = newContactArr; + }; + ContactSolver.prototype.initSolver = function (dt_inv) { + var body1 = this.shape1.body; + var body2 = this.shape2.body; + var sum_m_inv = body1.massInverted + body2.massInverted; + for(var i = 0; i < this.contacts.length; i++) { + var con = this.contacts[i]; + //console.log('initSolver con'); + //console.log(con); + // Transformed r1, r2 + Phaser.Vec2Utils.subtract(con.point, body1.position, con.r1); + Phaser.Vec2Utils.subtract(con.point, body2.position, con.r2); + //con.r1 = vec2.sub(con.point, body1.p); + //con.r2 = vec2.sub(con.point, body2.p); + // Local r1, r2 + Phaser.TransformUtils.unrotate(body1.transform, con.r1, con.r1_local); + Phaser.TransformUtils.unrotate(body2.transform, con.r2, con.r2_local); + //con.r1_local = body1.transform.unrotate(con.r1); + //con.r2_local = body2.transform.unrotate(con.r2); + var n = con.normal; + var t = Phaser.Vec2Utils.perp(con.normal); + // invEMn = J * invM * JT + // J = [ -n, -cross(r1, n), n, cross(r2, n) ] + var sn1 = Phaser.Vec2Utils.cross(con.r1, n); + var sn2 = Phaser.Vec2Utils.cross(con.r2, n); + var emn_inv = sum_m_inv + body1.inertiaInverted * sn1 * sn1 + body2.inertiaInverted * sn2 * sn2; + con.emn = emn_inv == 0 ? 0 : 1 / emn_inv; + // invEMt = J * invM * JT + // J = [ -t, -cross(r1, t), t, cross(r2, t) ] + var st1 = Phaser.Vec2Utils.cross(con.r1, t); + var st2 = Phaser.Vec2Utils.cross(con.r2, t); + var emt_inv = sum_m_inv + body1.inertiaInverted * st1 * st1 + body2.inertiaInverted * st2 * st2; + con.emt = emt_inv == 0 ? 0 : 1 / emt_inv; + // Linear velocities at contact point + // in 2D: cross(w, r) = perp(r) * w + var v1 = new Phaser.Vec2(); + var v2 = new Phaser.Vec2(); + Phaser.Vec2Utils.multiplyAdd(body1.velocity, Phaser.Vec2Utils.perp(con.r1), body1.angularVelocity, v1); + Phaser.Vec2Utils.multiplyAdd(body2.velocity, Phaser.Vec2Utils.perp(con.r2), body2.angularVelocity, v2); + //var v1 = vec2.mad(body1.v, vec2.perp(con.r1), body1.w); + //var v2 = vec2.mad(body2.v, vec2.perp(con.r2), body2.w); + // relative velocity at contact point + var rv = new Phaser.Vec2(); + Phaser.Vec2Utils.subtract(v2, v1, rv); + //var rv = vec2.sub(v2, v1); + // bounce velocity dot n + con.bounce = Phaser.Vec2Utils.dot(rv, con.normal) * this.elasticity; + } + }; + ContactSolver.prototype.warmStart = function () { + var body1 = this.shape1.body; + var body2 = this.shape2.body; + for(var i = 0; i < this.contacts.length; i++) { + var con = this.contacts[i]; + var n = con.normal; + var lambda_n = con.lambdaNormal; + var lambda_t = con.lambdaTangential; + // Apply accumulated impulses + //var impulse = vec2.rotate_vec(new vec2(lambda_n, lambda_t), n); + //var impulse = new vec2(lambda_n * n.x - lambda_t * n.y, lambda_t * n.x + lambda_n * n.y); + var impulse = new Phaser.Vec2(lambda_n * n.x - lambda_t * n.y, lambda_t * n.x + lambda_n * n.y); + body1.velocity.multiplyAddByScalar(impulse, -body1.massInverted); + //body1.v.mad(impulse, -body1.m_inv); + body1.angularVelocity -= Phaser.Vec2Utils.cross(con.r1, impulse) * body1.inertiaInverted; + //body1.w -= vec2.cross(con.r1, impulse) * body1.i_inv; + body2.velocity.multiplyAddByScalar(impulse, -body2.massInverted); + //body2.v.mad(impulse, body2.m_inv); + body2.angularVelocity -= Phaser.Vec2Utils.cross(con.r2, impulse) * body2.inertiaInverted; + //body2.w += vec2.cross(con.r2, impulse) * body2.i_inv; + } + }; + ContactSolver.prototype.solveVelocityConstraints = function () { + var body1 = this.shape1.body; + var body2 = this.shape2.body; + var m1_inv = body1.massInverted; + var i1_inv = body1.inertiaInverted; + var m2_inv = body2.massInverted; + var i2_inv = body2.inertiaInverted; + for(var i = 0; i < this.contacts.length; i++) { + var con = this.contacts[i]; + var n = con.normal; + var t = Phaser.Vec2Utils.perp(n); + var r1 = con.r1; + var r2 = con.r2; + // Linear velocities at contact point + // in 2D: cross(w, r) = perp(r) * w + var v1 = new Phaser.Vec2(); + var v2 = new Phaser.Vec2(); + Phaser.Vec2Utils.multiplyAdd(body1.velocity, Phaser.Vec2Utils.perp(r1), body1.angularVelocity, v1); + //var v1 = vec2.mad(body1.v, vec2.perp(r1), body1.w); + Phaser.Vec2Utils.multiplyAdd(body2.velocity, Phaser.Vec2Utils.perp(r2), body2.angularVelocity, v2); + //var v2 = vec2.mad(body2.v, vec2.perp(r2), body2.w); + // Relative velocity at contact point + var rv = new Phaser.Vec2(); + Phaser.Vec2Utils.subtract(v2, v1, rv); + //var rv = vec2.sub(v2, v1); + // Compute normal constraint impulse + adding bounce as a velocity bias + // lambda_n = -EMn * J * V + var lambda_n = -con.emn * (Phaser.Vec2Utils.dot(n, rv) + con.bounce); + // Accumulate and clamp + var lambda_n_old = con.lambdaNormal; + con.lambdaNormal = Math.max(lambda_n_old + lambda_n, 0); + lambda_n = con.lambdaNormal - lambda_n_old; + // Compute frictional constraint impulse + // lambda_t = -EMt * J * V + var lambda_t = -con.emt * Phaser.Vec2Utils.dot(t, rv); + // Max friction constraint impulse (Coulomb's Law) + var lambda_t_max = con.lambdaNormal * this.friction; + // Accumulate and clamp + var lambda_t_old = con.lambdaTangential; + con.lambdaTangential = this.clamp(lambda_t_old + lambda_t, -lambda_t_max, lambda_t_max); + lambda_t = con.lambdaTangential - lambda_t_old; + // Apply the final impulses + //var impulse = vec2.rotate_vec(new vec2(lambda_n, lambda_t), n); + var impulse = new Phaser.Vec2(lambda_n * n.x - lambda_t * n.y, lambda_t * n.x + lambda_n * n.y); + body1.velocity.multiplyAddByScalar(impulse, -m1_inv); + //body1.v.mad(impulse, -m1_inv); + body1.angularVelocity -= Phaser.Vec2Utils.cross(r1, impulse) * i1_inv; + //body1.w -= vec2.cross(r1, impulse) * i1_inv; + body2.velocity.multiplyAddByScalar(impulse, m2_inv); + //body2.v.mad(impulse, m2_inv); + body1.angularVelocity += Phaser.Vec2Utils.cross(r2, impulse) * i2_inv; + //body2.w += vec2.cross(r2, impulse) * i2_inv; + } + }; + ContactSolver.prototype.solvePositionConstraints = function () { + var body1 = this.shape1.body; + var body2 = this.shape2.body; + var m1_inv = body1.massInverted; + var i1_inv = body1.inertiaInverted; + var m2_inv = body2.massInverted; + var i2_inv = body2.inertiaInverted; + var sum_m_inv = m1_inv + m2_inv; + var max_penetration = 0; + for(var i = 0; i < this.contacts.length; i++) { + var con = this.contacts[i]; + var n = con.normal; + var r1 = new Phaser.Vec2(); + var r2 = new Phaser.Vec2(); + // Transformed r1, r2 + Phaser.Vec2Utils.rotate(con.r1_local, body1.angle, r1); + //var r1 = vec2.rotate(con.r1_local, body1.a); + Phaser.Vec2Utils.rotate(con.r2_local, body2.angle, r2); + //var r2 = vec2.rotate(con.r2_local, body2.a); + // Contact points (corrected) + var p1 = new Phaser.Vec2(); + var p2 = new Phaser.Vec2(); + Phaser.Vec2Utils.add(body1.position, r1, p1); + //var p1 = vec2.add(body1.p, r1); + Phaser.Vec2Utils.add(body2.position, r2, p2); + //var p2 = vec2.add(body2.p, r2); + // Corrected delta vector + var dp = new Phaser.Vec2(); + Phaser.Vec2Utils.subtract(p2, p1); + //var dp = vec2.sub(p2, p1); + // Position constraint + var c = Phaser.Vec2Utils.dot(dp, n) + con.depth; + var correction = this.clamp(Advanced.Manager.CONTACT_SOLVER_BAUMGARTE * (c + Advanced.Manager.CONTACT_SOLVER_COLLISION_SLOP), -Advanced.Manager.CONTACT_SOLVER_MAX_LINEAR_CORRECTION, 0); + if(correction == 0) { + continue; + } + // We don't need max_penetration less than or equal slop + max_penetration = Math.max(max_penetration, -c); + // Compute lambda for position constraint + // Solve (J * invM * JT) * lambda = -C / dt + var sn1 = Phaser.Vec2Utils.cross(r1, n); + var sn2 = Phaser.Vec2Utils.cross(r2, n); + var em_inv = sum_m_inv + body1.inertiaInverted * sn1 * sn1 + body2.inertiaInverted * sn2 * sn2; + var lambda_dt = em_inv == 0 ? 0 : -correction / em_inv; + // Apply correction impulses + var impulse_dt = new Phaser.Vec2(); + Phaser.Vec2Utils.scale(n, lambda_dt, impulse_dt); + //var impulse_dt = vec2.scale(n, lambda_dt); + body1.position.multiplyAddByScalar(impulse_dt, -m1_inv); + //body1.p.mad(impulse_dt, -m1_inv); + body1.angle -= sn1 * lambda_dt * i1_inv; + body2.position.multiplyAddByScalar(impulse_dt, m2_inv); + //body2.p.mad(impulse_dt, m2_inv); + body2.angle += sn2 * lambda_dt * i2_inv; + } + return max_penetration <= Advanced.Manager.CONTACT_SOLVER_COLLISION_SLOP * 3; + }; + ContactSolver.prototype.clamp = function (v, min, max) { + return v < min ? min : (v > max ? max : v); + }; + return ContactSolver; + })(); + Advanced.ContactSolver = ContactSolver; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + (function (Advanced) { + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - Shape - Circle + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + (function (Shapes) { + var Circle = (function (_super) { + __extends(Circle, _super); + function Circle(radius, x, y) { + if (typeof x === "undefined") { x = 0; } + if (typeof y === "undefined") { y = 0; } + _super.call(this, Advanced.Manager.SHAPE_TYPE_CIRCLE); + this.center = new Phaser.Vec2(x, y); + this.radius = radius; + this.tc = new Phaser.Vec2(); + this.finishVerts(); + } + Circle.prototype.finishVerts = function () { + this.radius = Math.abs(this.radius); + }; + Circle.prototype.duplicate = function () { + return new Circle(this.center.x, this.center.y, this.radius); + }; + Circle.prototype.recenter = function (c) { + this.center.subtract(c); + }; + Circle.prototype.transform = function (xf) { + Phaser.TransformUtils.transform(xf, this.center, this.center); + //this.center = xf.transform(this.center); + }; + Circle.prototype.untransform = function (xf) { + Phaser.TransformUtils.untransform(xf, this.center, this.center); + //this.center = xf.untransform(this.center); + }; + Circle.prototype.area = function () { + return Advanced.Manager.areaForCircle(this.radius, 0); + }; + Circle.prototype.centroid = function () { + return Phaser.Vec2Utils.clone(this.center); + }; + Circle.prototype.inertia = function (mass) { + return Advanced.Manager.inertiaForCircle(mass, this.center, this.radius, 0); + }; + Circle.prototype.cacheData = function (xf) { + Phaser.TransformUtils.transform(xf, this.center, this.tc); + //this.tc = xf.transform(this.center); + this.bounds.mins.setTo(this.tc.x - this.radius, this.tc.y - this.radius); + this.bounds.maxs.setTo(this.tc.x + this.radius, this.tc.y + this.radius); + }; + Circle.prototype.pointQuery = function (p) { + //return vec2.distsq(this.tc, p) < (this.r * this.r); + return Phaser.Vec2Utils.distanceSq(this.tc, p) < (this.radius * this.radius); + }; + Circle.prototype.findVertexByPoint = function (p, minDist) { + var dsq = minDist * minDist; + if(Phaser.Vec2Utils.distanceSq(this.tc, p) < dsq) { + return 0; + } + return -1; + }; + Circle.prototype.distanceOnPlane = function (n, d) { + Phaser.Vec2Utils.dot(n, this.tc) - this.radius - d; + }; + return Circle; + })(Phaser.Physics.Advanced.Shape); + Shapes.Circle = Circle; + })(Advanced.Shapes || (Advanced.Shapes = {})); + var Shapes = Advanced.Shapes; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + (function (Advanced) { + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - Shapes - Convex Polygon + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + (function (Shapes) { + var Poly = (function (_super) { + __extends(Poly, _super); + function Poly(verts) { + _super.call(this, Advanced.Manager.SHAPE_TYPE_POLY); + this.verts = []; + this.planes = []; + this.tverts = []; + this.tplanes = []; + if(verts) { + for(var i = 0; i < verts.length; i++) { + this.verts[i] = Phaser.Vec2Utils.clone(verts[i]); + this.tverts[i] = this.verts[i]; + this.tplanes[i] = { + }; + this.tplanes[i].n = new Phaser.Vec2(); + this.tplanes[i].d = 0; + } + } + this.finishVerts(); + } + Poly.prototype.finishVerts = function () { + if(this.verts.length < 2) { + this.convexity = false; + this.planes = []; + return; + } + this.convexity = true; + this.tverts = []; + this.tplanes = []; + // Must be counter-clockwise verts + for(var i = 0; i < this.verts.length; i++) { + var a = this.verts[i]; + var b = this.verts[(i + 1) % this.verts.length]; + var n = Phaser.Vec2Utils.normalize(Phaser.Vec2Utils.perp(Phaser.Vec2Utils.subtract(a, b))); + this.planes[i] = { + }; + this.planes[i].n = n; + this.planes[i].d = Phaser.Vec2Utils.dot(n, a); + this.tverts[i] = this.verts[i]; + this.tplanes[i] = { + }; + this.tplanes[i].n = new Phaser.Vec2(); + this.tplanes[i].d = 0; + } + for(var i = 0; i < this.verts.length; i++) { + var b = this.verts[(i + 2) % this.verts.length]; + var n = this.planes[i].n; + var d = this.planes[i].d; + if(Phaser.Vec2Utils.dot(n, b) - d > 0) { + this.convexity = false; + } + } + }; + Poly.prototype.duplicate = function () { + return new Phaser.Physics.Advanced.Shapes.Poly(this.verts); + }; + Poly.prototype.recenter = function (c) { + for(var i = 0; i < this.verts.length; i++) { + this.verts[i].subtract(c); + } + }; + Poly.prototype.transform = function (xf) { + for(var i = 0; i < this.verts.length; i++) { + this.verts[i] = xf.transform(this.verts[i]); + } + }; + Poly.prototype.untransform = function (xf) { + for(var i = 0; i < this.verts.length; i++) { + this.verts[i] = xf.untransform(this.verts[i]); + } + }; + Poly.prototype.area = function () { + return Advanced.Manager.areaForPoly(this.verts); + }; + Poly.prototype.centroid = function () { + return Advanced.Manager.centroidForPoly(this.verts); + }; + Poly.prototype.inertia = function (mass) { + return Advanced.Manager.inertiaForPoly(mass, this.verts, new Phaser.Vec2()); + }; + Poly.prototype.cacheData = function (xf) { + this.bounds.clear(); + var numVerts = this.verts.length; + //console.log('shapePoly cacheData', numVerts); + if(numVerts == 0) { + return; + } + for(var i = 0; i < numVerts; i++) { + Phaser.TransformUtils.transform(xf, this.tverts[i], this.tverts[i]); + //this.tverts[i] = xf.transform(this.verts[i]); + } + if(numVerts < 2) { + this.bounds.addPoint(this.tverts[0]); + return; + } + for(var i = 0; i < numVerts; i++) { + var a = this.tverts[i]; + var b = this.tverts[(i + 1) % numVerts]; + var n = Phaser.Vec2Utils.normalize(Phaser.Vec2Utils.perp(Phaser.Vec2Utils.subtract(a, b))); + this.tplanes[i].n = n; + this.tplanes[i].d = Phaser.Vec2Utils.dot(n, a); + this.bounds.addPoint(a); + } + }; + Poly.prototype.pointQuery = function (p) { + if(!this.bounds.containPoint(p)) { + return false; + } + return this.containPoint(p); + }; + Poly.prototype.findVertexByPoint = function (p, minDist) { + var dsq = minDist * minDist; + for(var i = 0; i < this.tverts.length; i++) { + if(Phaser.Vec2Utils.distanceSq(this.tverts[i], p) < dsq) { + return i; + } + } + return -1; + }; + Poly.prototype.findEdgeByPoint = function (p, minDist) { + var dsq = minDist * minDist; + var numVerts = this.tverts.length; + for(var i = 0; i < this.tverts.length; i++) { + var v1 = this.tverts[i]; + var v2 = this.tverts[(i + 1) % numVerts]; + var n = this.tplanes[i].n; + var dtv1 = Phaser.Vec2Utils.cross(v1, n); + var dtv2 = Phaser.Vec2Utils.cross(v2, n); + var dt = Phaser.Vec2Utils.cross(p, n); + if(dt > dtv1) { + if(Phaser.Vec2Utils.distanceSq(v1, p) < dsq) { + return i; + } + } else if(dt < dtv2) { + if(Phaser.Vec2Utils.distanceSq(v2, p) < dsq) { + return i; + } + } else { + var dist = Phaser.Vec2Utils.dot(n, p) - Phaser.Vec2Utils.dot(n, v1); + if(dist * dist < dsq) { + return i; + } + } + } + return -1; + }; + Poly.prototype.distanceOnPlane = function (n, d) { + var min = 999999; + for(var i = 0; i < this.verts.length; i++) { + min = Math.min(min, Phaser.Vec2Utils.dot(n, this.tverts[i])); + } + return min - d; + }; + Poly.prototype.containPoint = function (p) { + for(var i = 0; i < this.verts.length; i++) { + var plane = this.tplanes[i]; + if(Phaser.Vec2Utils.dot(plane.n, p) - plane.d > 0) { + return false; + } + } + return true; + }; + Poly.prototype.containPointPartial = function (p, n) { + for(var i = 0; i < this.verts.length; i++) { + var plane = this.tplanes[i]; + if(Phaser.Vec2Utils.dot(plane.n, n) < 0.0001) { + continue; + } + if(Phaser.Vec2Utils.dot(plane.n, p) - plane.d > 0) { + return false; + } + } + return true; + }; + return Poly; + })(Phaser.Physics.Advanced.Shape); + Shapes.Poly = Poly; + })(Advanced.Shapes || (Advanced.Shapes = {})); + var Shapes = Advanced.Shapes; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + (function (Advanced) { + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - Shapes - Segment + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + (function (Shapes) { + var Segment = (function (_super) { + __extends(Segment, _super); + function Segment(a, b, radius) { + _super.call(this, Advanced.Manager.SHAPE_TYPE_SEGMENT); + this.a = a.duplicate(); + this.b = b.duplicate(); + this.radius = radius; + this.normal = Phaser.Vec2Utils.perp(Phaser.Vec2Utils.subtract(b, a)); + this.normal.normalize(); + this.ta = new Phaser.Vec2(); + this.tb = new Phaser.Vec2(); + this.tn = new Phaser.Vec2(); + this.finishVerts(); + } + Segment.prototype.finishVerts = function () { + this.normal = Phaser.Vec2Utils.perp(Phaser.Vec2Utils.subtract(this.b, this.a)); + this.normal.normalize(); + this.radius = Math.abs(this.radius); + }; + Segment.prototype.duplicate = function () { + return new Phaser.Physics.Advanced.Shapes.Segment(this.a, this.b, this.radius); + }; + Segment.prototype.recenter = function (c) { + this.a.subtract(c); + this.b.subtract(c); + }; + Segment.prototype.transform = function (xf) { + Phaser.TransformUtils.transform(xf, this.a, this.a); + Phaser.TransformUtils.transform(xf, this.b, this.b); + //this.a = xf.transform(this.a); + //this.b = xf.transform(this.b); + }; + Segment.prototype.untransform = function (xf) { + Phaser.TransformUtils.untransform(xf, this.a, this.a); + Phaser.TransformUtils.untransform(xf, this.b, this.b); + //this.a = xf.untransform(this.a); + //this.b = xf.untransform(this.b); + }; + Segment.prototype.area = function () { + return Advanced.Manager.areaForSegment(this.a, this.b, this.radius); + }; + Segment.prototype.centroid = function () { + return Advanced.Manager.centroidForSegment(this.a, this.b); + }; + Segment.prototype.inertia = function (mass) { + return Advanced.Manager.inertiaForSegment(mass, this.a, this.b); + }; + Segment.prototype.cacheData = function (xf) { + Phaser.TransformUtils.transform(xf, this.a, this.ta); + Phaser.TransformUtils.transform(xf, this.b, this.tb); + //this.ta = xf.transform(this.a); + //this.tb = xf.transform(this.b); + this.tn = Phaser.Vec2Utils.perp(Phaser.Vec2Utils.subtract(this.tb, this.ta)).normalize(); + var l; + var r; + var t; + var b; + if(this.ta.x < this.tb.x) { + l = this.ta.x; + r = this.tb.x; + } else { + l = this.tb.x; + r = this.ta.x; + } + if(this.ta.y < this.tb.y) { + b = this.ta.y; + t = this.tb.y; + } else { + b = this.tb.y; + t = this.ta.y; + } + this.bounds.mins.setTo(l - this.radius, b - this.radius); + this.bounds.maxs.setTo(r + this.radius, t + this.radius); + }; + Segment.prototype.pointQuery = function (p) { + if(!this.bounds.containPoint(p)) { + return false; + } + var dn = Phaser.Vec2Utils.dot(this.tn, p) - Phaser.Vec2Utils.dot(this.ta, this.tn); + var dist = Math.abs(dn); + if(dist > this.radius) { + return false; + } + var dt = Phaser.Vec2Utils.cross(p, this.tn); + var dta = Phaser.Vec2Utils.cross(this.ta, this.tn); + var dtb = Phaser.Vec2Utils.cross(this.tb, this.tn); + if(dt <= dta) { + if(dt < dta - this.radius) { + return false; + } + return Phaser.Vec2Utils.distanceSq(this.ta, p) < (this.radius * this.radius); + } else if(dt > dtb) { + if(dt > dtb + this.radius) { + return false; + } + return Phaser.Vec2Utils.distanceSq(this.tb, p) < (this.radius * this.radius); + } + return true; + }; + Segment.prototype.findVertexByPoint = function (p, minDist) { + var dsq = minDist * minDist; + if(Phaser.Vec2Utils.distanceSq(this.ta, p) < dsq) { + return 0; + } + if(Phaser.Vec2Utils.distanceSq(this.tb, p) < dsq) { + return 1; + } + return -1; + }; + Segment.prototype.distanceOnPlane = function (n, d) { + var a = Phaser.Vec2Utils.dot(n, this.ta) - this.radius; + var b = Phaser.Vec2Utils.dot(n, this.tb) - this.radius; + return Math.min(a, b) - d; + }; + return Segment; + })(Phaser.Physics.Advanced.Shape); + Shapes.Segment = Segment; + })(Advanced.Shapes || (Advanced.Shapes = {})); + var Shapes = Advanced.Shapes; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - Collision Handlers + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + (function (Advanced) { + var Collision = (function () { + function Collision() { + } + Collision.prototype.collide = function (a, b, contacts) { + // Circle (a is the circle) + if(a.type == Advanced.Manager.SHAPE_TYPE_CIRCLE) { + if(b.type == Advanced.Manager.SHAPE_TYPE_CIRCLE) { + return this.circle2Circle(a, b, contacts); + } else if(b.type == Advanced.Manager.SHAPE_TYPE_SEGMENT) { + return this.circle2Segment(a, b, contacts); + } else if(b.type == Advanced.Manager.SHAPE_TYPE_POLY) { + return this.circle2Poly(a, b, contacts); + } + } + // Segment (a is the segment) + if(a.type == Advanced.Manager.SHAPE_TYPE_SEGMENT) { + if(b.type == Advanced.Manager.SHAPE_TYPE_CIRCLE) { + return this.circle2Segment(b, a, contacts); + } else if(b.type == Advanced.Manager.SHAPE_TYPE_SEGMENT) { + return this.segment2Segment(a, b, contacts); + } else if(b.type == Advanced.Manager.SHAPE_TYPE_POLY) { + return this.segment2Poly(a, b, contacts); + } + } + // Poly (a is the poly) + if(a.type == Advanced.Manager.SHAPE_TYPE_POLY) { + if(b.type == Advanced.Manager.SHAPE_TYPE_CIRCLE) { + return this.circle2Poly(b, a, contacts); + } else if(b.type == Advanced.Manager.SHAPE_TYPE_SEGMENT) { + return this.segment2Poly(b, a, contacts); + } else if(b.type == Advanced.Manager.SHAPE_TYPE_POLY) { + return this.poly2Poly(a, b, contacts); + } + } + }; + Collision.prototype._circle2Circle = function (c1, r1, c2, r2, contactArr) { + var rmax = r1 + r2; + var t = new Phaser.Vec2(); + //var t = vec2.sub(c2, c1); + Phaser.Vec2Utils.subtract(c2, c1, t); + var distsq = t.lengthSq(); + if(distsq > rmax * rmax) { + return 0; + } + var dist = Math.sqrt(distsq); + var p = new Phaser.Vec2(); + Phaser.Vec2Utils.multiplyAdd(c1, t, 0.5 + (r1 - r2) * 0.5 / dist, p); + //var p = vec2.mad(c1, t, 0.5 + (r1 - r2) * 0.5 / dist); + var n = new Phaser.Vec2(); + //var n = (dist != 0) ? vec2.scale(t, 1 / dist) : vec2.zero; + if(dist != 0) { + Phaser.Vec2Utils.scale(t, 1 / dist, n); + } + var d = dist - rmax; + contactArr.push(new Advanced.Contact(p, n, d, 0)); + return 1; + }; + Collision.prototype.circle2Circle = function (circ1, circ2, contactArr) { + return this._circle2Circle(circ1.tc, circ1.radius, circ2.tc, circ2.radius, contactArr); + }; + Collision.prototype.circle2Segment = function (circ, seg, contactArr) { + var rsum = circ.radius + seg.radius; + // Normal distance from segment + var dn = Phaser.Vec2Utils.dot(circ.tc, seg.tn) - Phaser.Vec2Utils.dot(seg.ta, seg.tn); + var dist = (dn < 0 ? dn * -1 : dn) - rsum; + if(dist > 0) { + return 0; + } + // Tangential distance along segment + var dt = Phaser.Vec2Utils.cross(circ.tc, seg.tn); + var dtMin = Phaser.Vec2Utils.cross(seg.ta, seg.tn); + var dtMax = Phaser.Vec2Utils.cross(seg.tb, seg.tn); + if(dt < dtMin) { + if(dt < dtMin - rsum) { + return 0; + } + return this._circle2Circle(circ.tc, circ.radius, seg.ta, seg.radius, contactArr); + } else if(dt > dtMax) { + if(dt > dtMax + rsum) { + return 0; + } + return this._circle2Circle(circ.tc, circ.radius, seg.tb, seg.radius, contactArr); + } + var n = new Phaser.Vec2(); + if(dn > 0) { + n.copyFrom(seg.tn); + } else { + Phaser.Vec2Utils.negative(seg.tn, n); + } + //var n = (dn > 0) ? seg.tn : vec2.neg(seg.tn); + var c1 = new Phaser.Vec2(); + Phaser.Vec2Utils.multiplyAdd(circ.tc, n, -(circ.radius + dist * 0.5), c1); + var c2 = new Phaser.Vec2(); + Phaser.Vec2Utils.negative(n, c2); + contactArr.push(new Advanced.Contact(c1, c2, dist, 0)); + //contactArr.push(new Contact(vec2.mad(circ.tc, n, -(circ.r + dist * 0.5)), vec2.neg(n), dist, 0)); + return 1; + }; + Collision.prototype.circle2Poly = function (circ, poly, contactArr) { + var minDist = -999999; + var minIdx = -1; + for(var i = 0; i < poly.verts.length; i++) { + var plane = poly.tplanes[i]; + var dist = Phaser.Vec2Utils.dot(circ.tc, plane.n) - plane.d - circ.radius; + if(dist > 0) { + return 0; + } else if(dist > minDist) { + minDist = dist; + minIdx = i; + } + } + var n = poly.tplanes[minIdx].n; + var a = poly.tverts[minIdx]; + var b = poly.tverts[(minIdx + 1) % poly.verts.length]; + var dta = Phaser.Vec2Utils.cross(a, n); + var dtb = Phaser.Vec2Utils.cross(b, n); + var dt = Phaser.Vec2Utils.cross(circ.tc, n); + if(dt > dta) { + return this._circle2Circle(circ.tc, circ.radius, a, 0, contactArr); + } else if(dt < dtb) { + return this._circle2Circle(circ.tc, circ.radius, b, 0, contactArr); + } + var c1 = new Phaser.Vec2(); + Phaser.Vec2Utils.multiplyAdd(circ.tc, n, -(circ.radius + minDist * 0.5), c1); + var c2 = new Phaser.Vec2(); + Phaser.Vec2Utils.negative(n, c2); + contactArr.push(new Advanced.Contact(c1, c2, minDist, 0)); + //contactArr.push(new Contact(vec2.mad(circ.tc, n, -(circ.r + minDist * 0.5)), vec2.neg(n), minDist, 0)); + return 1; + }; + Collision.prototype.segmentPointDistanceSq = function (seg, p) { + var w = new Phaser.Vec2(); + var d = new Phaser.Vec2(); + Phaser.Vec2Utils.subtract(p, seg.ta, w); + Phaser.Vec2Utils.subtract(seg.tb, seg.ta, d); + //var w = vec2.sub(p, seg.ta); + //var d = vec2.sub(seg.tb, seg.ta); + var proj = w.dot(d); + if(proj <= 0) { + return w.dot(w); + } + var vsq = d.dot(d); + if(proj >= vsq) { + return w.dot(w) - 2 * proj + vsq; + } + return w.dot(w) - proj * proj / vsq; + }; + Collision.prototype.segment2Segment = // FIXME and optimise me lots!!! + function (seg1, seg2, contactArr) { + var d = []; + d[0] = this.segmentPointDistanceSq(seg1, seg2.ta); + d[1] = this.segmentPointDistanceSq(seg1, seg2.tb); + d[2] = this.segmentPointDistanceSq(seg2, seg1.ta); + d[3] = this.segmentPointDistanceSq(seg2, seg1.tb); + var idx1 = d[0] < d[1] ? 0 : 1; + var idx2 = d[2] < d[3] ? 2 : 3; + var idxm = d[idx1] < d[idx2] ? idx1 : idx2; + var s, t; + var u = Phaser.Vec2Utils.subtract(seg1.tb, seg1.ta); + var v = Phaser.Vec2Utils.subtract(seg2.tb, seg2.ta); + switch(idxm) { + case 0: + s = Phaser.Vec2Utils.dot(Phaser.Vec2Utils.subtract(seg2.ta, seg1.ta), u) / Phaser.Vec2Utils.dot(u, u); + s = s < 0 ? 0 : (s > 1 ? 1 : s); + t = 0; + break; + case 1: + s = Phaser.Vec2Utils.dot(Phaser.Vec2Utils.subtract(seg2.tb, seg1.ta), u) / Phaser.Vec2Utils.dot(u, u); + s = s < 0 ? 0 : (s > 1 ? 1 : s); + t = 1; + break; + case 2: + s = 0; + t = Phaser.Vec2Utils.dot(Phaser.Vec2Utils.subtract(seg1.ta, seg2.ta), v) / Phaser.Vec2Utils.dot(v, v); + t = t < 0 ? 0 : (t > 1 ? 1 : t); + break; + case 3: + s = 1; + t = Phaser.Vec2Utils.dot(Phaser.Vec2Utils.subtract(seg1.tb, seg2.ta), v) / Phaser.Vec2Utils.dot(v, v); + t = t < 0 ? 0 : (t > 1 ? 1 : t); + break; + } + var minp1 = Phaser.Vec2Utils.multiplyAdd(seg1.ta, u, s); + var minp2 = Phaser.Vec2Utils.multiplyAdd(seg2.ta, v, t); + return this._circle2Circle(minp1, seg1.radius, minp2, seg2.radius, contactArr); + }; + Collision.prototype.findPointsBehindSeg = // Identify vertexes that have penetrated the segment. + function (contactArr, seg, poly, dist, coef) { + var dta = Phaser.Vec2Utils.cross(seg.tn, seg.ta); + var dtb = Phaser.Vec2Utils.cross(seg.tn, seg.tb); + var n = new Phaser.Vec2(); + Phaser.Vec2Utils.scale(seg.tn, coef, n); + //var n = vec2.scale(seg.tn, coef); + for(var i = 0; i < poly.verts.length; i++) { + var v = poly.tverts[i]; + if(Phaser.Vec2Utils.dot(v, n) < Phaser.Vec2Utils.dot(seg.tn, seg.ta) * coef + seg.radius) { + var dt = Phaser.Vec2Utils.cross(seg.tn, v); + if(dta >= dt && dt >= dtb) { + contactArr.push(new Advanced.Contact(v, n, dist, (poly.id << 16) | i)); + } + } + } + }; + Collision.prototype.segment2Poly = function (seg, poly, contactArr) { + var seg_td = Phaser.Vec2Utils.dot(seg.tn, seg.ta); + var seg_d1 = poly.distanceOnPlane(seg.tn, seg_td) - seg.radius; + if(seg_d1 > 0) { + return 0; + } + var n = new Phaser.Vec2(); + Phaser.Vec2Utils.negative(seg.tn, n); + var seg_d2 = poly.distanceOnPlane(n, -seg_td) - seg.radius; + //var seg_d2 = poly.distanceOnPlane(vec2.neg(seg.tn), -seg_td) - seg.r; + if(seg_d2 > 0) { + return 0; + } + var poly_d = -999999; + var poly_i = -1; + for(var i = 0; i < poly.verts.length; i++) { + var plane = poly.tplanes[i]; + var dist = seg.distanceOnPlane(plane.n, plane.d); + if(dist > 0) { + return 0; + } + if(dist > poly_d) { + poly_d = dist; + poly_i = i; + } + } + var poly_n = new Phaser.Vec2(); + Phaser.Vec2Utils.negative(poly.tplanes[poly_i].n, poly_n); + //var poly_n = vec2.neg(poly.tplanes[poly_i].n); + var va = new Phaser.Vec2(); + Phaser.Vec2Utils.multiplyAdd(seg.ta, poly_n, seg.radius, va); + //var va = vec2.mad(seg.ta, poly_n, seg.r); + var vb = new Phaser.Vec2(); + Phaser.Vec2Utils.multiplyAdd(seg.tb, poly_n, seg.radius, vb); + //var vb = vec2.mad(seg.tb, poly_n, seg.r); + if(poly.containPoint(va)) { + contactArr.push(new Advanced.Contact(va, poly_n, poly_d, (seg.id << 16) | 0)); + } + if(poly.containPoint(vb)) { + contactArr.push(new Advanced.Contact(vb, poly_n, poly_d, (seg.id << 16) | 1)); + } + // Floating point precision problems here. + // This will have to do for now. + poly_d -= 0.1; + if(seg_d1 >= poly_d || seg_d2 >= poly_d) { + if(seg_d1 > seg_d2) { + this.findPointsBehindSeg(contactArr, seg, poly, seg_d1, 1); + } else { + this.findPointsBehindSeg(contactArr, seg, poly, seg_d2, -1); + } + } + // If no other collision points are found, try colliding endpoints. + if(contactArr.length == 0) { + var poly_a = poly.tverts[poly_i]; + var poly_b = poly.tverts[(poly_i + 1) % poly.verts.length]; + if(this._circle2Circle(seg.ta, seg.radius, poly_a, 0, contactArr)) { + return 1; + } + if(this._circle2Circle(seg.tb, seg.radius, poly_a, 0, contactArr)) { + return 1; + } + if(this._circle2Circle(seg.ta, seg.radius, poly_b, 0, contactArr)) { + return 1; + } + if(this._circle2Circle(seg.tb, seg.radius, poly_b, 0, contactArr)) { + return 1; + } + } + return contactArr.length; + }; + Collision.prototype.findMSA = // Find the minimum separating axis for the given poly and plane list. + function (poly, planes, num) { + var min_dist = -999999; + var min_index = -1; + for(var i = 0; i < num; i++) { + var dist = poly.distanceOnPlane(planes[i].n, planes[i].d); + if(dist > 0) { + // no collision + return { + dist: 0, + index: -1 + }; + } else if(dist > min_dist) { + min_dist = dist; + min_index = i; + } + } + // new object - see what we can do here + return { + dist: min_dist, + index: min_index + }; + }; + Collision.prototype.findVertsFallback = function (contactArr, poly1, poly2, n, dist) { + var num = 0; + for(var i = 0; i < poly1.verts.length; i++) { + var v = poly1.tverts[i]; + if(poly2.containPointPartial(v, n)) { + contactArr.push(new Advanced.Contact(v, n, dist, (poly1.id << 16) | i)); + num++; + } + } + for(var i = 0; i < poly2.verts.length; i++) { + var v = poly2.tverts[i]; + if(poly1.containPointPartial(v, n)) { + contactArr.push(new Advanced.Contact(v, n, dist, (poly2.id << 16) | i)); + num++; + } + } + return num; + }; + Collision.prototype.findVerts = // Find the overlapped vertices. + function (contactArr, poly1, poly2, n, dist) { + var num = 0; + for(var i = 0; i < poly1.verts.length; i++) { + var v = poly1.tverts[i]; + if(poly2.containPoint(v)) { + contactArr.push(new Advanced.Contact(v, n, dist, (poly1.id << 16) | i)); + num++; + } + } + for(var i = 0; i < poly2.verts.length; i++) { + var v = poly2.tverts[i]; + if(poly1.containPoint(v)) { + contactArr.push(new Advanced.Contact(v, n, dist, (poly2.id << 16) | i)); + num++; + } + } + return num > 0 ? num : this.findVertsFallback(contactArr, poly1, poly2, n, dist); + }; + Collision.prototype.poly2Poly = function (poly1, poly2, contactArr) { + var msa1 = this.findMSA(poly2, poly1.tplanes, poly1.verts.length); + if(msa1.index == -1) { + return 0; + } + var msa2 = this.findMSA(poly1, poly2.tplanes, poly2.verts.length); + if(msa2.index == -1) { + return 0; + } + // Penetration normal direction shoud be from poly1 to poly2 + if(msa1.dist > msa2.dist) { + return this.findVerts(contactArr, poly1, poly2, poly1.tplanes[msa1.index].n, msa1.dist); + } + return this.findVerts(contactArr, poly1, poly2, Phaser.Vec2Utils.negative(poly2.tplanes[msa2.index].n), msa2.dist); + }; + return Collision; + })(); + Advanced.Collision = Collision; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + /// + /// + /// + /// + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - Space + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + (function (Advanced) { + var Space = (function () { + function Space() { + this.stepCount = 0; + this.bodyArr = []; + this.bodyHash = { + }; + this.jointArr = []; + this.jointHash = { + }; + this.numContacts = 0; + this.contactSolvers = []; + //this.postSolve(arb) { }; + this.gravity = new Phaser.Vec2(0, 10); + this.damping = 0; + } + Space.TIME_TO_SLEEP = 0.5; + Space.SLEEP_LINEAR_TOLERANCE = 0.5; + Space.SLEEP_ANGULAR_TOLERANCE = 2 * Phaser.GameMath.DEG_TO_RAD; + Space.prototype.clear = function () { + Advanced.Manager.shapeCounter = 0; + Advanced.Manager.bodyCounter = 0; + Advanced.Manager.jointCounter = 0; + for(var i = 0; i < this.bodyArr.length; i++) { + if(this.bodyArr[i]) { + this.removeBody(this.bodyArr[i]); + } + } + this.bodyArr = []; + this.bodyHash = { + }; + this.jointArr = []; + this.jointHash = { + }; + this.contactSolvers = []; + this.stepCount = 0; + }; + Space.prototype.addBody = function (body) { + if(this.bodyHash[body.id] != undefined) { + return; + } + //console.log('Body added to space', body.name); + var index = this.bodyArr.push(body) - 1; + this.bodyHash[body.id] = index; + body.awake(true); + body.space = this; + body.cacheData(); + }; + Space.prototype.removeBody = function (body) { + if(this.bodyHash[body.id] == undefined) { + return; + } + // Remove linked joint + for(var i = 0; i < body.joints.length; i++) { + if(body.joints[i]) { + this.removeJoint(body.joints[i]); + } + } + body.space = null; + var index = this.bodyHash[body.id]; + delete this.bodyHash[body.id]; + delete this.bodyArr[index]; + }; + Space.prototype.addJoint = function (joint) { + if(this.jointHash[joint.id] != undefined) { + return; + } + joint.body1.awake(true); + joint.body2.awake(true); + var index = this.jointArr.push(joint) - 1; + this.jointHash[joint.id] = index; + var index = joint.body1.joints.push(joint) - 1; + joint.body1.jointHash[joint.id] = index; + var index = joint.body2.joints.push(joint) - 1; + joint.body2.jointHash[joint.id] = index; + }; + Space.prototype.removeJoint = function (joint) { + if(this.jointHash[joint.id] == undefined) { + return; + } + joint.body1.awake(true); + joint.body2.awake(true); + var index = joint.body1.jointHash[joint.id]; + delete joint.body1.jointHash[joint.id]; + delete joint.body1.joints[index]; + var index = joint.body2.jointHash[joint.id]; + delete joint.body2.jointHash[joint.id]; + delete joint.body2.joints[index]; + var index = this.jointHash[joint.id]; + delete this.jointHash[joint.id]; + delete this.jointArr[index]; + }; + Space.prototype.findShapeByPoint = function (p, refShape) { + var firstShape; + for(var i = 0; i < this.bodyArr.length; i++) { + var body = this.bodyArr[i]; + if(!body) { + continue; + } + for(var j = 0; j < body.shapes.length; j++) { + var shape = body.shapes[j]; + if(shape.pointQuery(p)) { + if(!refShape) { + return shape; + } + if(!firstShape) { + firstShape = shape; + } + if(shape == refShape) { + refShape = null; + } + } + } + } + return firstShape; + }; + Space.prototype.findBodyByPoint = function (p, refBody) { + var firstBody; + for(var i = 0; i < this.bodyArr.length; i++) { + var body = this.bodyArr[i]; + if(!body) { + continue; + } + for(var j = 0; j < body.shapes.length; j++) { + var shape = body.shapes[j]; + if(shape.pointQuery(p)) { + if(!refBody) { + return shape.body; + } + if(!firstBody) { + firstBody = shape.body; + } + if(shape.body == refBody) { + refBody = null; + } + break; + } + } + } + return firstBody; + }; + Space.prototype.shapeById = // TODO: Replace this function to shape hashing + function (id) { + var shape; + for(var i = 0; i < this.bodyArr.length; i++) { + var body = this.bodyArr[i]; + if(!body) { + continue; + } + for(var j = 0; j < body.shapes.length; j++) { + if(body.shapes[j].id == id) { + return body.shapes[j]; + } + } + } + return null; + }; + Space.prototype.jointById = function (id) { + var index = this.jointHash[id]; + if(index != undefined) { + return this.jointArr[index]; + } + return null; + }; + Space.prototype.findVertexByPoint = function (p, minDist, refVertexId) { + var firstVertexId = -1; + refVertexId = refVertexId || -1; + for(var i = 0; i < this.bodyArr.length; i++) { + var body = this.bodyArr[i]; + if(!body) { + continue; + } + for(var j = 0; j < body.shapes.length; j++) { + var shape = body.shapes[j]; + var index = shape.findVertexByPoint(p, minDist); + if(index != -1) { + var vertex = (shape.id << 16) | index; + if(refVertexId == -1) { + return vertex; + } + if(firstVertexId == -1) { + firstVertexId = vertex; + } + if(vertex == refVertexId) { + refVertexId = -1; + } + } + } + } + return firstVertexId; + }; + Space.prototype.findEdgeByPoint = function (p, minDist, refEdgeId) { + var firstEdgeId = -1; + refEdgeId = refEdgeId || -1; + for(var i = 0; i < this.bodyArr.length; i++) { + var body = this.bodyArr[i]; + if(!body) { + continue; + } + for(var j = 0; j < body.shapes.length; j++) { + var shape = body.shapes[j]; + if(shape.type != Advanced.Manager.SHAPE_TYPE_POLY) { + continue; + } + var index = shape.findEdgeByPoint(p, minDist); + if(index != -1) { + var edge = (shape.id << 16) | index; + if(refEdgeId == -1) { + return edge; + } + if(firstEdgeId == -1) { + firstEdgeId = edge; + } + if(edge == refEdgeId) { + refEdgeId = -1; + } + } + } + } + return firstEdgeId; + }; + Space.prototype.findJointByPoint = function (p, minDist, refJointId) { + var firstJointId = -1; + var dsq = minDist * minDist; + refJointId = refJointId || -1; + for(var i = 0; i < this.jointArr.length; i++) { + var joint = this.jointArr[i]; + if(!joint) { + continue; + } + var jointId = -1; + if(Phaser.Vec2Utils.distanceSq(p, joint.getWorldAnchor1()) < dsq) { + jointId = (joint.id << 16 | 0); + } else if(Phaser.Vec2Utils.distanceSq(p, joint.getWorldAnchor2()) < dsq) { + jointId = (joint.id << 16 | 1); + } + if(jointId != -1) { + if(refJointId == -1) { + return jointId; + } + if(firstJointId == -1) { + firstJointId = jointId; + } + if(jointId == refJointId) { + refJointId = -1; + } + } + } + return firstJointId; + }; + Space.prototype.findContactSolver = function (shape1, shape2) { + for(var i = 0; i < this.contactSolvers.length; i++) { + var contactSolver = this.contactSolvers[i]; + if(shape1 == contactSolver.shape1 && shape2 == contactSolver.shape2) { + return contactSolver; + } + } + return null; + }; + Space.prototype.genTemporalContactSolvers = function () { + //console.log('genTemporalContactSolvers'); + //var t0 = Date.now(); + var newContactSolverArr = []; + this.numContacts = 0; + for(var body1_index = 0; body1_index < this.bodyArr.length; body1_index++) { + var body1 = this.bodyArr[body1_index]; + //console.log('body1', body1_index, body1.type); + if(!body1) { + continue; + } + body1.stepCount = this.stepCount; + for(var body2_index = 0; body2_index < this.bodyArr.length; body2_index++) { + var body2 = this.bodyArr[body2_index]; + //console.log('body2', body2_index, body2.type); + if(!body2) { + continue; + } + if(body1.stepCount == body2.stepCount) { + continue; + } + //console.log('step'); + var active1 = body1.isAwake && !body1.isStatic; + var active2 = body2.isAwake && !body2.isStatic; + if(!active1 && !active2) { + continue; + } + //console.log('active'); + if(!body1.isCollidable(body2)) { + continue; + } + //console.log('collideable'); + if(!body1.bounds.intersectsBounds(body2.bounds)) { + continue; + } + //console.log('>>>>>>>>>> intersects'); + for(var i = 0; i < body1.shapes.length; i++) { + for(var j = 0; j < body2.shapes.length; j++) { + var shape1 = body1.shapes[i]; + var shape2 = body2.shapes[j]; + var contactArr = []; + if(!Advanced.Manager.collision.collide(shape1, shape2, contactArr)) { + continue; + } + if(shape1.type > shape2.type) { + var temp = shape1; + shape1 = shape2; + shape2 = temp; + } + this.numContacts += contactArr.length; + var contactSolver = this.findContactSolver(shape1, shape2); + if(contactSolver) { + contactSolver.update(contactArr); + newContactSolverArr.push(contactSolver); + } else { + body1.awake(true); + body2.awake(true); + var newContactSolver = new Advanced.ContactSolver(shape1, shape2); + newContactSolver.contacts = contactArr; + newContactSolver.elasticity = Math.max(shape1.elasticity, shape2.elasticity); + newContactSolver.friction = Math.sqrt(shape1.friction * shape2.friction); + newContactSolverArr.push(newContactSolver); + } + } + } + } + } + //stats.timeCollision = Date.now() - t0; + return newContactSolverArr; + }; + Space.prototype.initSolver = function (dt, dt_inv, warmStarting) { + //var t0 = Date.now(); + // Initialize contact solvers + for(var i = 0; i < this.contactSolvers.length; i++) { + this.contactSolvers[i].initSolver(dt_inv); + } + // Initialize joint solver + for(var i = 0; i < this.jointArr.length; i++) { + if(this.jointArr[i]) { + this.jointArr[i].initSolver(dt, warmStarting); + } + } + // Warm starting (apply cached impulse) + if(warmStarting) { + for(var i = 0; i < this.contactSolvers.length; i++) { + this.contactSolvers[i].warmStart(); + } + } + //stats.timeInitSolver = Date.now() - t0; + }; + Space.prototype.velocitySolver = function (iteration) { + //var t0 = Date.now(); + for(var i = 0; i < iteration; i++) { + for(var j = 0; j < this.jointArr.length; j++) { + if(this.jointArr[j]) { + this.jointArr[j].solveVelocityConstraints(); + } + } + for(var j = 0; j < this.contactSolvers.length; j++) { + this.contactSolvers[j].solveVelocityConstraints(); + } + } + //stats.timeVelocitySolver = Date.now() - t0; + }; + Space.prototype.positionSolver = function (iteration) { + //var t0 = Date.now(); + var positionSolved = false; + //stats.positionIterations = 0; + for(var i = 0; i < iteration; i++) { + var contactsOk = true; + var jointsOk = true; + for(var j = 0; j < this.contactSolvers.length; j++) { + var contactOk = this.contactSolvers[j].solvePositionConstraints(); + contactsOk = contactOk && contactsOk; + } + for(var j = 0; j < this.jointArr.length; j++) { + if(this.jointArr[j]) { + var jointOk = this.jointArr[j].solvePositionConstraints(); + jointsOk = jointOk && jointsOk; + } + } + if(contactsOk && jointsOk) { + // exit early if the position errors are small + positionSolved = true; + break; + } + //stats.positionIterations++; + } + //stats.timePositionSolver = Date.now() - t0; + return positionSolved; + }; + Space.prototype.step = function (dt, vel_iteration, pos_iteration, warmStarting, allowSleep) { + var dt_inv = 1 / dt; + this.stepCount++; + // Generate contact & contactSolver + this.contactSolvers = this.genTemporalContactSolvers(); + // Initialize contacts & joints solver + this.initSolver(dt, dt_inv, warmStarting); + // Intergrate velocity + for(var i = 0; i < this.bodyArr.length; i++) { + var body = this.bodyArr[i]; + if(!body) { + continue; + } + if(body.isDynamic && body.isAwake) { + body.updateVelocity(this.gravity, dt, this.damping); + } + } + for(var i = 0; i < this.jointArr.length; i++) { + var joint = this.jointArr[i]; + if(!joint) { + continue; + } + var body1 = joint.body1; + var body2 = joint.body2; + var awake1 = body1.isAwake && !body1.isStatic; + var awake2 = body2.isAwake && !body2.isStatic; + if(awake1 ^ awake2) { + if(!awake1) { + body1.awake(true); + } + if(!awake2) { + body2.awake(true); + } + } + } + // Iterative velocity constraints solver + this.velocitySolver(vel_iteration); + // Intergrate position + for(var i = 0; i < this.bodyArr.length; i++) { + var body = this.bodyArr[i]; + if(!body) { + continue; + } + if(body.isDynamic && body.isAwake) { + body.updatePosition(dt); + } + } + // Process breakable joint + for(var i = 0; i < this.jointArr.length; i++) { + var joint = this.jointArr[i]; + if(!joint) { + continue; + } + if(joint.breakable) { + if(joint.getReactionForce(dt_inv).lengthsq() >= joint.maxForce * joint.maxForce) { + this.removeJoint(joint); + } + } + } + // Iterative position constraints solver + var positionSolved = this.positionSolver(pos_iteration); + for(var i = 0; i < this.bodyArr.length; i++) { + var body = this.bodyArr[i]; + if(!body) { + continue; + } + body.syncTransform(); + } + // Post solve collision callback + for(var i = 0; i < this.contactSolvers.length; i++) { + var arb = this.contactSolvers[i]; + // Re-enable this + //this.postSolve(arb); + } + for(var i = 0; i < this.bodyArr.length; i++) { + var body = this.bodyArr[i]; + if(!body) { + continue; + } + if(body.isDynamic && body.isAwake) { + body.cacheData(); + } + } + // Process sleeping + if(allowSleep) { + var minSleepTime = 999999; + var linTolSqr = Space.SLEEP_LINEAR_TOLERANCE * Space.SLEEP_LINEAR_TOLERANCE; + var angTolSqr = Space.SLEEP_ANGULAR_TOLERANCE * Space.SLEEP_ANGULAR_TOLERANCE; + for(var i = 0; i < this.bodyArr.length; i++) { + var body = this.bodyArr[i]; + if(!body) { + continue; + } + if(!body.isDynamic) { + continue; + } + if(body.angularVelocity * body.angularVelocity > angTolSqr || body.velocity.dot(body.velocity) > linTolSqr) { + body.sleepTime = 0; + minSleepTime = 0; + } else { + body.sleepTime += dt; + minSleepTime = Math.min(minSleepTime, body.sleepTime); + } + } + if(positionSolved && minSleepTime >= Space.TIME_TO_SLEEP) { + for(var i = 0; i < this.bodyArr.length; i++) { + var body = this.bodyArr[i]; + if(!body) { + continue; + } + body.awake(false); + } + } + } + }; + return Space; + })(); + Advanced.Space = Space; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - Body + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + (function (Advanced) { + var Body = (function () { + function Body(sprite, type, x, y) { + if (typeof x === "undefined") { x = 0; } + if (typeof y === "undefined") { y = 0; } + // Shapes + this.shapes = []; + // Joints + this.joints = []; + this.jointHash = { + }; + this.fixedRotation = false; + this.categoryBits = 0x0001; + this.maskBits = 0xFFFF; + this.stepCount = 0; + this._tempVec2 = new Phaser.Vec2(); + this.id = Phaser.Physics.Advanced.Manager.bodyCounter++; + this.name = 'body' + this.id; + this.type = type; + if(sprite) { + this.sprite = sprite; + this.game = sprite.game; + this.position = new Phaser.Vec2(sprite.x, sprite.y); + this.angle = sprite.rotation; + } else { + this.position = new Phaser.Vec2(x, y); + this.angle = 0; + } + this.transform = new Phaser.Transform(this.position, this.angle); + this.centroid = new Phaser.Vec2(); + this.velocity = new Phaser.Vec2(); + this.force = new Phaser.Vec2(); + this.angularVelocity = 0; + this.torque = 0; + this.linearDamping = 0; + this.angularDamping = 0; + this.sleepTime = 0; + this.awaked = false; + this.shapes = []; + this.joints = []; + this.jointHash = { + }; + this.bounds = new Advanced.Bounds(); + this.fixedRotation = false; + this.categoryBits = 0x0001; + this.maskBits = 0xFFFF; + this.stepCount = 0; + } + Body.prototype.duplicate = function () { + //console.log('body duplicate called'); + //var body = new Body(this.type, this.transform.t, this.angle); + //for (var i = 0; i < this.shapes.length; i++) + //{ + // body.addShape(this.shapes[i].duplicate()); + //} + //body.resetMassData(); + //return body; + }; + Object.defineProperty(Body.prototype, "isDisabled", { + get: function () { + return this.type == Phaser.Types.BODY_DISABLED ? true : false; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Body.prototype, "isStatic", { + get: function () { + return this.type == Phaser.Types.BODY_STATIC ? true : false; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Body.prototype, "isKinetic", { + get: function () { + return this.type == Phaser.Types.BODY_KINETIC ? true : false; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Body.prototype, "isDynamic", { + get: function () { + return this.type == Phaser.Types.BODY_DYNAMIC ? true : false; + }, + enumerable: true, + configurable: true + }); + Body.prototype.setType = function (type) { + if(type == this.type) { + return; + } + this.force.setTo(0, 0); + this.velocity.setTo(0, 0); + this.torque = 0; + this.angularVelocity = 0; + this.type = type; + this.awake(true); + }; + Body.prototype.addShape = function (shape) { + // Check not already part of this body + shape.body = this; + this.shapes.push(shape); + return shape; + }; + Body.prototype.removeShape = function (shape) { + var index = this.shapes.indexOf(shape); + if(index != -1) { + this.shapes.splice(index, 1); + shape.body = undefined; + } + }; + Body.prototype.setMass = function (mass) { + this.mass = mass; + this.massInverted = mass > 0 ? 1 / mass : 0; + }; + Body.prototype.setInertia = function (inertia) { + this.inertia = inertia; + this.inertiaInverted = inertia > 0 ? 1 / inertia : 0; + }; + Body.prototype.setTransform = function (pos, angle) { + this.transform.setTo(pos, angle); + // inject the transform into this.position + Phaser.TransformUtils.transform(this.transform, this.centroid, this.position); + //this.position.copyFrom(this.transform.transform(this.centroid)); + this.angle = angle; + }; + Body.prototype.syncTransform = function () { + this.transform.setRotation(this.angle); + //var rotc: Phaser.Vec2 = this.transform.rotate(this.centroid); + //var sub: Phaser.Vec2 = Phaser.Vec2Utils.subtract(this.position, rotc); + //this.transform.setPosition(sub); + // this.transform.setPosition(vec2.sub(this.position, this.transform.rotate(this.centroid))); + //Phaser.Vec2Utils.subtract(this.position, this.transform.rotate(this.centroid), this.transform.t); + // OPTIMISE: Creating new vector + Phaser.Vec2Utils.subtract(this.position, Phaser.TransformUtils.rotate(this.transform, this.centroid), this.transform.t); + }; + Body.prototype.getWorldPoint = function (p) { + // OPTIMISE: Creating new vector + return Phaser.TransformUtils.transform(this.transform, p); + }; + Body.prototype.getWorldVector = function (v) { + // OPTIMISE: Creating new vector + return Phaser.TransformUtils.rotate(this.transform, v); + }; + Body.prototype.getLocalPoint = function (p) { + // OPTIMISE: Creating new vector + return Phaser.TransformUtils.untransform(this.transform, p); + }; + Body.prototype.getLocalVector = function (v) { + // OPTIMISE: Creating new vector + return Phaser.TransformUtils.unrotate(this.transform, v); + }; + Body.prototype.setFixedRotation = function (flag) { + this.fixedRotation = flag; + this.resetMassData(); + }; + Body.prototype.resetMassData = function () { + this.centroid.setTo(0, 0); + this.mass = 0; + this.massInverted = 0; + this.inertia = 0; + this.inertiaInverted = 0; + if(this.isDynamic == false) { + Phaser.TransformUtils.transform(this.transform, this.centroid, this.position); + //this.position.copyFrom(this.transform.transform(this.centroid)); + return; + } + var totalMassCentroid = new Phaser.Vec2(0, 0); + var totalMass = 0; + var totalInertia = 0; + for(var i = 0; i < this.shapes.length; i++) { + var shape = this.shapes[i]; + var centroid = shape.centroid(); + var mass = shape.area() * shape.density; + var inertia = shape.inertia(mass); + //console.log('rmd', centroid, shape); + totalMassCentroid.multiplyAddByScalar(centroid, mass); + totalMass += mass; + totalInertia += inertia; + } + //this.centroid.copy(vec2.scale(totalMassCentroid, 1 / totalMass)); + Phaser.Vec2Utils.scale(totalMassCentroid, 1 / totalMass, this.centroid); + this.setMass(totalMass); + if(!this.fixedRotation) { + //this.setInertia(totalInertia - totalMass * vec2.dot(this.centroid, this.centroid)); + this.setInertia(totalInertia - totalMass * Phaser.Vec2Utils.dot(this.centroid, this.centroid)); + } + //console.log("mass = " + this.m + " inertia = " + this.i); + // Move center of mass + var oldPosition = Phaser.Vec2Utils.clone(this.position); + //this.position.copyFrom(this.transform.transform(this.centroid)); + Phaser.TransformUtils.transform(this.transform, this.centroid, this.position); + // Update center of mass velocity + //this.velocity.mad(vec2.perp(vec2.sub(this.position, old_p)), this.angularVelocity); + oldPosition.subtract(this.position); + this.velocity.multiplyAddByScalar(Phaser.Vec2Utils.perp(oldPosition, oldPosition), this.angularVelocity); + }; + Body.prototype.resetJointAnchors = function () { + for(var i = 0; i < this.joints.length; i++) { + var joint = this.joints[i]; + if(!joint) { + continue; + } + var anchor1 = joint.getWorldAnchor1(); + var anchor2 = joint.getWorldAnchor2(); + joint.setWorldAnchor1(anchor1); + joint.setWorldAnchor2(anchor2); + } + }; + Body.prototype.cacheData = function () { + //console.log('Body cacheData', this.name, 'len', this.shapes.length); + this.bounds.clear(); + for(var i = 0; i < this.shapes.length; i++) { + var shape = this.shapes[i]; + shape.cacheData(this.transform); + this.bounds.addBounds(shape.bounds); + } + }; + Body.prototype.updateVelocity = function (gravity, dt, damping) { + // this.velocity = vec2.mad(this.velocity, vec2.mad(gravity, this.force, this.massInverted), dt); + Phaser.Vec2Utils.multiplyAdd(gravity, this.force, this.massInverted, this._tempVec2); + Phaser.Vec2Utils.multiplyAdd(this.velocity, this._tempVec2, dt, this.velocity); + this.angularVelocity = this.angularVelocity + this.torque * this.inertiaInverted * dt; + // Apply damping. + // ODE: dv/dt + c * v = 0 + // Solution: v(t) = v0 * exp(-c * t) + // Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v * exp(-c * dt) + // v2 = exp(-c * dt) * v1 + // Taylor expansion: + // v2 = (1.0f - c * dt) * v1 + this.velocity.scale(this.game.math.clamp(1 - dt * (damping + this.linearDamping), 0, 1)); + this.angularVelocity *= this.game.math.clamp(1 - dt * (damping + this.angularDamping), 0, 1); + this.force.setTo(0, 0); + this.torque = 0; + }; + Body.prototype.updatePosition = function (dt) { + //console.log('body update pos', this.position.y); + //console.log('pre add temp', this._tempVec2.y); + //this.position.addself(vec2.scale(this.velocity, dt)); + this.position.add(Phaser.Vec2Utils.scale(this.velocity, dt, this._tempVec2)); + //console.log('post add temp', this._tempVec2.y); + //console.log('post add', this.position.y); + this.angle += this.angularVelocity * dt; + }; + Body.prototype.resetForce = function () { + this.force.setTo(0, 0); + this.torque = 0; + }; + Body.prototype.applyForce = function (force, p) { + if(this.isDynamic == false) { + return; + } + if(this.isAwake == false) { + this.awake(true); + } + this.force.add(force); + // this.f.addself(force); + // this.torque += vec2.cross(vec2.sub(p, this.p), force); + Phaser.Vec2Utils.subtract(p, this.position, this._tempVec2); + this.torque += Phaser.Vec2Utils.cross(this._tempVec2, force); + }; + Body.prototype.applyForceToCenter = function (force) { + if(this.isDynamic == false) { + return; + } + if(this.isAwake == false) { + this.awake(true); + } + this.force.add(force); + }; + Body.prototype.applyTorque = function (torque) { + if(this.isDynamic == false) { + return; + } + if(this.isAwake == false) { + this.awake(true); + } + this.torque += torque; + }; + Body.prototype.applyLinearImpulse = function (impulse, p) { + if(this.isDynamic == false) { + return; + } + if(this.isAwake == false) { + this.awake(true); + } + this.velocity.multiplyAddByScalar(impulse, this.massInverted); + // this.angularVelocity += vec2.cross(vec2.sub(p, this.position), impulse) * this.inertiaInverted; + Phaser.Vec2Utils.subtract(p, this.position, this._tempVec2); + this.angularVelocity += Phaser.Vec2Utils.cross(this._tempVec2, impulse) * this.inertiaInverted; + }; + Body.prototype.applyAngularImpulse = function (impulse) { + if(this.isDynamic == false) { + return; + } + if(this.isAwake == false) { + this.awake(true); + } + this.angularVelocity += impulse * this.inertiaInverted; + }; + Body.prototype.kineticEnergy = function () { + var vsq = this.velocity.dot(this.velocity); + var wsq = this.angularVelocity * this.angularVelocity; + return 0.5 * (this.mass * vsq + this.inertia * wsq); + }; + Object.defineProperty(Body.prototype, "isAwake", { + get: function () { + return this.awaked; + }, + enumerable: true, + configurable: true + }); + Body.prototype.awake = function (flag) { + this.awaked = flag; + if(flag) { + this.sleepTime = 0; + } else { + this.velocity.setTo(0, 0); + this.angularVelocity = 0; + this.force.setTo(0, 0); + this.torque = 0; + } + }; + Body.prototype.isCollidable = function (other) { + if(this == other) { + return false; + } + if(this.isDynamic == false && other.isDynamic == false) { + return false; + } + if(!(this.maskBits & other.categoryBits) || !(other.maskBits & this.categoryBits)) { + return false; + } + for(var i = 0; i < this.joints.length; i++) { + var joint = this.joints[i]; + if(!joint) { + continue; + } + if(!joint.collideConnected && other.jointHash[joint.id] != undefined) { + return false; + } + } + return true; + }; + return Body; + })(); + Advanced.Body = Body; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + (function (Advanced) { + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - Shapes - Box + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + (function (Shapes) { + var Box = (function (_super) { + __extends(Box, _super); + // Give in pixels + function Box(x, y, width, height) { + x = Advanced.Manager.pixelsToMeters(x); + y = Advanced.Manager.pixelsToMeters(y); + width = Advanced.Manager.pixelsToMeters(width); + height = Advanced.Manager.pixelsToMeters(height); + var hw = width * 0.5; + var hh = height * 0.5; + _super.call(this, [ + new Phaser.Vec2(-hw + x, +hh + y), + new Phaser.Vec2(-hw + x, -hh + y), + new Phaser.Vec2(+hw + x, -hh + y), + new Phaser.Vec2(+hw + x, +hh + y) + ]); + } + return Box; + })(Phaser.Physics.Advanced.Shapes.Poly); + Shapes.Box = Box; + })(Advanced.Shapes || (Advanced.Shapes = {})); + var Shapes = Advanced.Shapes; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + (function (Advanced) { + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - Shapes - Triangle + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + (function (Shapes) { + var Triangle = (function (_super) { + __extends(Triangle, _super); + function Triangle(p1, p2, p3) { + _super.call(this, [ + new Phaser.Vec2(p1.x, p1.y), + new Phaser.Vec2(p2.x, p2.y), + new Phaser.Vec2(p3.x, p3.y) + ]); + } + return Triangle; + })(Phaser.Physics.Advanced.Shapes.Poly); + Shapes.Triangle = Triangle; + })(Advanced.Shapes || (Advanced.Shapes = {})); + var Shapes = Advanced.Shapes; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +/// /// /// /// diff --git a/Tests/physics/body1.js b/Tests/physics/body1.js new file mode 100644 index 00000000..f8f99706 --- /dev/null +++ b/Tests/physics/body1.js @@ -0,0 +1,104 @@ +/// +/// +/// +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + function init() { + game.load.image('xatari', 'assets/sprites/atari800xl.png'); + game.load.image('card', 'assets/sprites/mana_card.png'); + game.load.image('atari', 'assets/sprites/shinyball.png'); + game.load.start(); + } + var atari; + var card; + var physics; + var circle; + var walls; + var ground; + function create() { + atari = game.add.sprite(200, 100, 'atari'); + atari.transform.origin.setTo(0.5, 0.5); + //card = game.add.sprite(500, 300, 'card'); + physics = new Phaser.Physics.Advanced.Manager(game); + walls = new Phaser.Physics.Advanced.Body(null, Phaser.Types.BODY_STATIC); + walls.game = game; + // position is in relation to the containing body! don't forget this + ground = walls.addShape(new Phaser.Physics.Advanced.Shapes.Box(400, 500, 500, 20)); + //walls.addShape(new Phaser.Physics.Advanced.ShapeBox(0, 0.2, 20.48, 0.4)); + //walls.addShape(new Phaser.Physics.Advanced.ShapeBox(0, 15.16, 20.48, 0.4)); + //walls.addShape(new Phaser.Physics.Advanced.ShapeBox(-10.04, 7.68, 0.4, 14.56)); + //walls.addShape(new Phaser.Physics.Advanced.ShapeBox(10.04, 7.68, 0.4, 14.56)); + walls.resetMassData(); + physics.space.addBody(walls); + // Add a circle + circle = new Phaser.Physics.Advanced.Body(null, Phaser.Types.BODY_DYNAMIC, physics.pixelsToMeters(300), physics.pixelsToMeters(200)); + circle.game = game; + var shape = new Phaser.Physics.Advanced.Shapes.Circle(0.4, 0, 0); + shape.elasticity = 0.8; + shape.friction = 1; + shape.density = 1; + circle.addShape(shape); + circle.resetMassData(); + physics.space.addBody(circle); + } + function update() { + physics.update(); + atari.x = physics.metersToPixels(circle.position.x); + atari.y = physics.metersToPixels(circle.position.y); + atari.rotation = physics.metersToPixels(circle.angle); + // force moves without rotating + if(game.input.keyboard.isDown(Phaser.Keyboard.LEFT)) { + circle.applyAngularImpulse(-0.02); + } else if(game.input.keyboard.isDown(Phaser.Keyboard.RIGHT)) { + circle.applyAngularImpulse(0.02); + } + /* + if (game.input.keyboard.isDown(Phaser.Keyboard.LEFT)) + { + circle.applyForceToCenter(new Phaser.Vec2(-8, 0)); + } + else if (game.input.keyboard.isDown(Phaser.Keyboard.RIGHT)) + { + circle.applyForceToCenter(new Phaser.Vec2(8, 0)); + } + */ + if(game.input.keyboard.isDown(Phaser.Keyboard.UP)) { + circle.applyForceToCenter(new Phaser.Vec2(0, -10)); + } else if(game.input.keyboard.isDown(Phaser.Keyboard.DOWN)) { + circle.applyForceToCenter(new Phaser.Vec2(0, 5)); + } + //console.log(circle.velocity.x, circle.velocity.y); + //console.log('p', circle.position.x, circle.position.y); + } + function renderCircle(shape) { + game.stage.context.beginPath(); + game.stage.context.arc(shape.tc.x * 50, shape.tc.y * 50, shape.radius * 50, 0, Math.PI * 2, false); + if(shape.body.isAwake) { + game.stage.context.fillStyle = 'rgba(0,255,0, 0.3)'; + } else { + game.stage.context.fillStyle = 'rgba(100,100,100, 0.1)'; + } + game.stage.context.fill(); + game.stage.context.closePath(); + } + function drawPolygon(ctx, shape, lineWidth, fillStyle) { + var verts = shape.verts; + ctx.beginPath(); + ctx.moveTo(verts[0].x * 50, verts[0].y * 50); + for(var i = 0; i < verts.length; i++) { + ctx.lineTo(verts[i].x * 50, verts[i].y * 50); + } + ctx.lineTo(verts[verts.length - 1].x * 50, verts[verts.length - 1].y * 50); + ctx.closePath(); + ctx.fillStyle = fillStyle; + ctx.fill(); + } + function render() { + game.stage.context.fillStyle = 'rgb(255,255,0)'; + game.stage.context.fillText('x: ' + circle.position.x + ' y: ' + circle.position.y, 32, 32); + game.stage.context.fillText('vx: ' + circle.velocity.x + ' vy: ' + circle.velocity.y, 32, 64); + renderCircle(circle.shapes[0]); + drawPolygon(game.stage.context, walls.shapes[0], 1, 'rgb(0,255,255)'); + } +})(); diff --git a/Tests/physics/body1.ts b/Tests/physics/body1.ts new file mode 100644 index 00000000..2ab49637 --- /dev/null +++ b/Tests/physics/body1.ts @@ -0,0 +1,160 @@ +/// +/// +/// +/// + +(function () { + + var game = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + + function init() { + + game.load.image('xatari', 'assets/sprites/atari800xl.png'); + game.load.image('card', 'assets/sprites/mana_card.png'); + game.load.image('atari', 'assets/sprites/shinyball.png'); + game.load.start(); + + } + + var atari: Phaser.Sprite; + var card: Phaser.Sprite; + var physics: Phaser.Physics.Advanced.Manager; + var circle: Phaser.Physics.Advanced.Body; + var walls: Phaser.Physics.Advanced.Body; + + var ground: Phaser.Physics.Advanced.Shapes.Box; + + function create() { + + atari = game.add.sprite(200, 100, 'atari'); + atari.transform.origin.setTo(0.5, 0.5); + //card = game.add.sprite(500, 300, 'card'); + + physics = new Phaser.Physics.Advanced.Manager(game); + + walls = new Phaser.Physics.Advanced.Body(null, Phaser.Types.BODY_STATIC); + walls.game = game; + + // position is in relation to the containing body! don't forget this + ground = walls.addShape(new Phaser.Physics.Advanced.Shapes.Box(400, 500, 500, 20)); + + //walls.addShape(new Phaser.Physics.Advanced.ShapeBox(0, 0.2, 20.48, 0.4)); + //walls.addShape(new Phaser.Physics.Advanced.ShapeBox(0, 15.16, 20.48, 0.4)); + //walls.addShape(new Phaser.Physics.Advanced.ShapeBox(-10.04, 7.68, 0.4, 14.56)); + //walls.addShape(new Phaser.Physics.Advanced.ShapeBox(10.04, 7.68, 0.4, 14.56)); + walls.resetMassData(); + + physics.space.addBody(walls); + + // Add a circle + + circle = new Phaser.Physics.Advanced.Body(null, Phaser.Types.BODY_DYNAMIC, physics.pixelsToMeters(300), physics.pixelsToMeters(200)); + circle.game = game; + + var shape = new Phaser.Physics.Advanced.Shapes.Circle(0.4, 0, 0); + shape.elasticity = 0.8; + shape.friction = 1; + shape.density = 1; + circle.addShape(shape); + circle.resetMassData(); + + physics.space.addBody(circle); + + } + + function update() { + + physics.update(); + + atari.x = physics.metersToPixels(circle.position.x); + atari.y = physics.metersToPixels(circle.position.y); + atari.rotation = physics.metersToPixels(circle.angle); + + // force moves without rotating + + if (game.input.keyboard.isDown(Phaser.Keyboard.LEFT)) + { + circle.applyAngularImpulse(-0.02); + } + else if (game.input.keyboard.isDown(Phaser.Keyboard.RIGHT)) + { + circle.applyAngularImpulse(0.02); + } + + + /* + if (game.input.keyboard.isDown(Phaser.Keyboard.LEFT)) + { + circle.applyForceToCenter(new Phaser.Vec2(-8, 0)); + } + else if (game.input.keyboard.isDown(Phaser.Keyboard.RIGHT)) + { + circle.applyForceToCenter(new Phaser.Vec2(8, 0)); + } + */ + + if (game.input.keyboard.isDown(Phaser.Keyboard.UP)) + { + circle.applyForceToCenter(new Phaser.Vec2(0, -10)); + } + else if (game.input.keyboard.isDown(Phaser.Keyboard.DOWN)) + { + circle.applyForceToCenter(new Phaser.Vec2(0, 5)); + } + + //console.log(circle.velocity.x, circle.velocity.y); + //console.log('p', circle.position.x, circle.position.y); + } + + function renderCircle(shape) { + + game.stage.context.beginPath(); + game.stage.context.arc(shape.tc.x * 50, shape.tc.y * 50, shape.radius * 50, 0, Math.PI * 2, false); + + if (shape.body.isAwake) + { + game.stage.context.fillStyle = 'rgba(0,255,0, 0.3)'; + } + else + { + game.stage.context.fillStyle = 'rgba(100,100,100, 0.1)'; + } + + game.stage.context.fill(); + game.stage.context.closePath(); + + } + + function drawPolygon(ctx, shape, lineWidth, fillStyle) { + + var verts = shape.verts; + + ctx.beginPath(); + ctx.moveTo(verts[0].x * 50, verts[0].y * 50); + + for (var i = 0; i < verts.length; i++) { + ctx.lineTo(verts[i].x * 50, verts[i].y * 50); + } + + ctx.lineTo(verts[verts.length - 1].x * 50, verts[verts.length - 1].y * 50); + + ctx.closePath(); + + ctx.fillStyle = fillStyle; + ctx.fill(); + + } + + function render() { + + game.stage.context.fillStyle = 'rgb(255,255,0)'; + game.stage.context.fillText('x: ' + circle.position.x + ' y: ' + circle.position.y, 32, 32); + game.stage.context.fillText('vx: ' + circle.velocity.x + ' vy: ' + circle.velocity.y, 32, 64); + + renderCircle(circle.shapes[0]); + + drawPolygon(game.stage.context, walls.shapes[0], 1, 'rgb(0,255,255)'); + + } + +})(); diff --git a/Tests/physics/obb vs obb.js b/Tests/physics/obb vs obb.js new file mode 100644 index 00000000..17483f81 --- /dev/null +++ b/Tests/physics/obb vs obb.js @@ -0,0 +1,33 @@ +/// +(function () { + var game = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + function init() { + // Using Phasers asset loader we load up a PNG from the assets folder + game.load.image('atari', 'assets/sprites/atari800xl.png'); + game.load.image('card', 'assets/sprites/mana_card.png'); + game.load.start(); + } + var atari; + var card; + function create() { + atari = game.add.sprite(200, 310, 'atari'); + card = game.add.sprite(500, 300, 'card'); + atari.input.start(0); + atari.input.enableDrag(); + card.input.start(0); + card.events.onInputDown.add(rotateIt, this); + } + function rotateIt() { + card.rotation += 10; + } + function update() { + } + function render() { + game.stage.context.save(); + game.stage.context.strokeStyle = 'rgb(255,255,0)'; + game.stage.context.strokeRect(atari.cameraView.x, atari.cameraView.y, atari.cameraView.width, atari.cameraView.height); + game.stage.context.strokeStyle = 'rgb(255,0,255)'; + game.stage.context.strokeRect(card.cameraView.x, card.cameraView.y, card.cameraView.width, card.cameraView.height); + game.stage.context.restore(); + } +})(); diff --git a/Tests/physics/obb vs obb.ts b/Tests/physics/obb vs obb.ts new file mode 100644 index 00000000..5b22bebf --- /dev/null +++ b/Tests/physics/obb vs obb.ts @@ -0,0 +1,54 @@ +/// + +(function () { + + var game = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + + function init() { + + // Using Phasers asset loader we load up a PNG from the assets folder + game.load.image('atari', 'assets/sprites/atari800xl.png'); + game.load.image('card', 'assets/sprites/mana_card.png'); + game.load.start(); + + } + + var atari: Phaser.Sprite; + var card: Phaser.Sprite; + + function create() { + + atari = game.add.sprite(200, 310, 'atari'); + + card = game.add.sprite(500, 300, 'card'); + + atari.input.start(0); + atari.input.enableDrag(); + + card.input.start(0); + card.events.onInputDown.add(rotateIt, this); + + } + + function rotateIt() { + card.rotation += 10; + } + + function update() { + } + + function render() { + + game.stage.context.save(); + + game.stage.context.strokeStyle = 'rgb(255,255,0)'; + game.stage.context.strokeRect(atari.cameraView.x, atari.cameraView.y, atari.cameraView.width, atari.cameraView.height); + + game.stage.context.strokeStyle = 'rgb(255,0,255)'; + game.stage.context.strokeRect(card.cameraView.x, card.cameraView.y, card.cameraView.width, card.cameraView.height); + + game.stage.context.restore(); + + } + +})(); diff --git a/build/phaser.d.ts b/build/phaser.d.ts index 8584ca5c..7a30e48a 100644 --- a/build/phaser.d.ts +++ b/build/phaser.d.ts @@ -572,6 +572,12 @@ module Phaser { */ public lengthSq(): number; /** + * Normalize this vector. + * + * @return {Vec2} This for chaining. + */ + public normalize(): Vec2; + /** * The dot product of two 2D vectors. * * @param {Vec2} a Reference to a source Vec2 object. @@ -615,6 +621,14 @@ module Phaser { */ public multiplyByScalar(scalar: number): Vec2; /** + * Adds the given vector to this vector then multiplies by the given scalar. + * + * @param {Vec2} a Reference to a source Vec2 object. + * @param {number} scalar + * @return {Vec2} This for chaining. + */ + public multiplyAddByScalar(a: Vec2, scalar: number): Vec2; + /** * Divide this vector by the given scalar. * * @param {number} scalar @@ -822,9 +836,9 @@ module Phaser { static GEOM_LINE: number; static GEOM_POLYGON: number; static BODY_DISABLED: number; - static BODY_DYNAMIC: number; static BODY_STATIC: number; - static BODY_KINEMATIC: number; + static BODY_KINETIC: number; + static BODY_DYNAMIC: number; /** * Flag used to allow GameObjects to collide on their left side * @type {number} @@ -2378,7 +2392,25 @@ module Phaser { */ static scale(a: Vec2, s: number, out?: Vec2): Vec2; /** - * Rotate a 2D vector by 90 degrees. + * Adds two 2D vectors together and multiplies the result by the given scalar. + * + * @param {Vec2} a Reference to a source Vec2 object. + * @param {Vec2} b Reference to a source Vec2 object. + * @param {number} s Scaling value. + * @param {Vec2} out The output Vec2 that is the result of the operation. + * @return {Vec2} A Vec2 that is the sum of the two vectors added and multiplied. + */ + static multiplyAdd(a: Vec2, b: Vec2, s: number, out?: Vec2): Vec2; + /** + * Return a negative vector. + * + * @param {Vec2} a Reference to a source Vec2 object. + * @param {Vec2} out The output Vec2 that is the result of the operation. + * @return {Vec2} A Vec2 that is the negative vector. + */ + static negative(a: Vec2, out?: Vec2): Vec2; + /** + * Return a perpendicular vector (90 degrees rotation) * * @param {Vec2} a Reference to a source Vec2 object. * @param {Vec2} out The output Vec2 that is the result of the operation. @@ -2386,6 +2418,14 @@ module Phaser { */ static perp(a: Vec2, out?: Vec2): Vec2; /** + * Return a perpendicular vector (-90 degrees rotation) + * + * @param {Vec2} a Reference to a source Vec2 object. + * @param {Vec2} out The output Vec2 that is the result of the operation. + * @return {Vec2} A Vec2 that is the scaled vector. + */ + static rperp(a: Vec2, out?: Vec2): Vec2; + /** * Checks if two 2D vectors are equal. * * @param {Vec2} a Reference to a source Vec2 object. @@ -2493,7 +2533,17 @@ module Phaser { * @param {Vec2} out The output Vec2 that is the result of the operation. * @return {Vec2} A Vec2. */ - static rotate(a: Vec2, b: Vec2, theta: number, out?: Vec2): Vec2; + static rotateAroundOrigin(a: Vec2, b: Vec2, theta: number, out?: Vec2): Vec2; + /** + * Rotate a 2D vector to the given angle (theta). + * + * @param {Vec2} a Reference to a source Vec2 object. + * @param {Vec2} b Reference to a source Vec2 object. + * @param {Number} theta The angle of rotation in radians. + * @param {Vec2} out The output Vec2 that is the result of the operation. + * @return {Vec2} A Vec2. + */ + static rotate(a: Vec2, theta: number, out?: Vec2): Vec2; /** * Clone a 2D vector. * @@ -3832,8 +3882,22 @@ module Phaser { * @return {object} The text data you want. */ public getText(key: string); + /** + * Returns an array containing all of the keys of Images in the Cache. + * @return {Array} The string based keys in the Cache. + */ public getImageKeys(): any[]; /** + * Returns an array containing all of the keys of Sounds in the Cache. + * @return {Array} The string based keys in the Cache. + */ + public getSoundKeys(): any[]; + /** + * Returns an array containing all of the keys of Text Files in the Cache. + * @return {Array} The string based keys in the Cache. + */ + public getTextKeys(): any[]; + /** * Clean up cache memory. */ public destroy(): void; @@ -7215,7 +7279,7 @@ module Phaser { private _groupCounter; public getNextGroupID(): number; /** - * Called one by Game during the boot process. + * Called once by Game during the boot process. */ public boot(): void; /** @@ -9349,6 +9413,644 @@ module Phaser { } } /** +* Phaser - 2D Transform +* +* A 2D Transform +*/ +module Phaser { + class Transform { + /** + * Creates a new 2D Transform object. + * @class Transform + * @constructor + * @return {Transform} This object + **/ + constructor(pos: Vec2, angle: number); + public t: Vec2; + public c: number; + public s: number; + public setTo(pos: Vec2, angle: number): Transform; + public setRotation(angle: number): Transform; + public setPosition(p: Vec2): Transform; + public identity(): Transform; + } +} +/** +* Phaser - TransformUtils +* +* A collection of methods useful for manipulating and performing operations on 2D Transforms. +* +*/ +module Phaser { + class TransformUtils { + static rotate(t: Transform, v: Vec2, out?: Vec2): Vec2; + static unrotate(t: Transform, v: Vec2, out?: Vec2): Vec2; + static transform(t: Transform, v: Vec2, out?: Vec2): Vec2; + static untransform(t: Transform, v: Vec2, out?: Vec2): Vec2; + } +} +/** +* Phaser - PhysicsManager +* +* Your game only has one PhysicsManager instance and it's responsible for looking after, creating and colliding +* all of the physics objects in the world. +*/ +module Phaser.Physics { + class ArcadePhysics { + constructor(game: Game, width: number, height: number); + /** + * Local private reference to Game. + */ + public game: Game; + /** + * Physics object pool + */ + public members: Group; + private _drag; + private _delta; + private _velocityDelta; + private _length; + private _distance; + private _tangent; + private _separatedX; + private _separatedY; + private _overlap; + private _maxOverlap; + private _obj1Velocity; + private _obj2Velocity; + private _obj1NewVelocity; + private _obj2NewVelocity; + private _average; + private _quadTree; + private _quadTreeResult; + public bounds: Rectangle; + public gravity: Vec2; + public drag: Vec2; + public bounce: Vec2; + public angularDrag: number; + /** + * The overlap bias is used when calculating hull overlap before separation - change it if you have especially small or large GameObjects + * @type {number} + */ + static OVERLAP_BIAS: number; + /** + * The overlap bias is used when calculating hull overlap before separation - change it if you have especially small or large GameObjects + * @type {number} + */ + static TILE_OVERLAP: bool; + /** + * @type {number} + */ + public worldDivisions: number; + public updateMotion(body: Body): void; + /** + * A tween-like function that takes a starting velocity and some other factors and returns an altered velocity. + * + * @param {number} Velocity Any component of velocity (e.g. 20). + * @param {number} Acceleration Rate at which the velocity is changing. + * @param {number} Drag Really kind of a deceleration, this is how much the velocity changes if Acceleration is not set. + * @param {number} Max An absolute value cap for the velocity. + * + * @return {number} The altered Velocity value. + */ + public computeVelocity(velocity: number, gravity?: number, acceleration?: number, drag?: number, max?: number): number; + /** + * The core Collision separation method. + * @param body1 The first Physics.Body to separate + * @param body2 The second Physics.Body to separate + * @returns {boolean} Returns true if the bodies were separated, otherwise false. + */ + public separate(body1: Body, body2: Body): bool; + public checkHullIntersection(body1: Body, body2: Body): 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. + */ + public separateBodyX(body1: Body, body2: Body): 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. + */ + public separateBodyY(body1: Body, body2: Body): bool; + /** + * Checks for overlaps between two objects using the world QuadTree. Can be Sprite vs. Sprite, Sprite 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 Sprite or Group to check. If null the world.group is used. + * @param object2 The second Sprite or Group to check. + * @param notifyCallback A callback function that is called if the objects overlap. The two objects will be passed to this function in the same order in which you passed them to Collision.overlap. + * @param processCallback A callback function that lets you perform additional checks against the two objects if they overlap. If this is set then notifyCallback will only be called if processCallback returns true. + * @param context The context in which the callbacks will be called + * @returns {boolean} true if the objects overlap, otherwise false. + */ + public overlap(object1?, object2?, notifyCallback?, processCallback?, context?): bool; + /** + * Collision resolution specifically for GameObjects vs. Tiles. + * @param object The GameObject to separate + * @param tile The Tile to separate + * @returns {boolean} Whether the objects in fact touched and were separated + */ + public separateTile(object: Sprite, x: number, y: number, width: number, height: number, mass: number, collideLeft: bool, collideRight: bool, collideUp: bool, collideDown: bool, separateX: bool, separateY: bool): bool; + } +} +/** +* Phaser - Advanced Physics - Joint +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ +module Phaser.Physics.Advanced { + class Joint { + constructor(type: number, body1: Body, body2: Body, collideConnected); + public id: number; + public type: number; + public body1: Body; + public body2: Body; + public collideConnected; + public maxForce: number; + public breakable: bool; + public anchor1: Vec2; + public anchor2: Vec2; + public getWorldAnchor1(): Vec2; + public getWorldAnchor2(): Vec2; + public setWorldAnchor1(anchor1): void; + public setWorldAnchor2(anchor2): void; + } +} +/** +* Phaser - Advanced Physics Manager +* +* Your game only has one PhysicsManager instance and it's responsible for looking after, creating and colliding +* all of the physics objects in the world. +*/ +module Phaser.Physics.Advanced { + class Manager { + constructor(game: Game); + /** + * Local reference to Game. + */ + public game: Game; + static collision: Collision; + static SHAPE_TYPE_CIRCLE: number; + static SHAPE_TYPE_SEGMENT: number; + static SHAPE_TYPE_POLY: number; + static SHAPE_NUM_TYPES: number; + static JOINT_TYPE_ANGLE: number; + static JOINT_TYPE_REVOLUTE: number; + static JOINT_TYPE_WELD: number; + static JOINT_TYPE_WHEEL: number; + static JOINT_TYPE_PRISMATIC: number; + static JOINT_TYPE_DISTANCE: number; + static JOINT_TYPE_ROPE: number; + static JOINT_TYPE_MOUSE: number; + static JOINT_LINEAR_SLOP: number; + static JOINT_ANGULAR_SLOP: number; + static JOINT_MAX_LINEAR_CORRECTION: number; + static JOINT_MAX_ANGULAR_CORRECTION: number; + static JOINT_LIMIT_STATE_INACTIVE: number; + static JOINT_LIMIT_STATE_AT_LOWER: number; + static JOINT_LIMIT_STATE_AT_UPPER: number; + static JOINT_LIMIT_STATE_EQUAL_LIMITS: number; + static CONTACT_SOLVER_COLLISION_SLOP: number; + static CONTACT_SOLVER_BAUMGARTE: number; + static CONTACT_SOLVER_MAX_LINEAR_CORRECTION: number; + static bodyCounter: number; + static jointCounter: number; + static shapeCounter: number; + public space: Space; + public lastTime: number; + public frameRateHz: number; + public timeDelta: number; + public paused: bool; + public step: bool; + public velocityIterations: number; + public positionIterations: number; + public allowSleep: bool; + public warmStarting: bool; + public update(): void; + public pixelsToMeters(value: number): number; + public metersToPixels(value: number): number; + static pixelsToMeters(value: number): number; + static metersToPixels(value: number): number; + static p2m(value: number): number; + static m2p(value: number): number; + static areaForCircle(radius_outer, radius_inner): number; + static inertiaForCircle(mass, center, radius_outer, radius_inner): number; + static areaForSegment(a, b, radius): number; + static centroidForSegment(a, b): Vec2; + static inertiaForSegment(mass, a, b): number; + static areaForPoly(verts): number; + static centroidForPoly(verts): Vec2; + static inertiaForPoly(mass, verts, offset): number; + static inertiaForBox(mass, w, h): number; + static createConvexHull(points): any[]; + } +} +/** +* Phaser - 2D AABB +* +* A 2D AABB object +*/ +module Phaser.Physics.Advanced { + class Bounds { + /** + * Creates a new 2D AABB object. + * @class Bounds + * @constructor + * @return {Bounds} This object + **/ + constructor(mins?: Vec2, maxs?: Vec2); + public mins: Vec2; + public maxs: Vec2; + public toString(): string; + public setTo(mins: Vec2, maxs: Vec2): void; + public copy(b: Bounds): Bounds; + public clear(): Bounds; + public isEmpty(): bool; + public getPerimeter(): number; + public addPoint(p: Vec2): Bounds; + public addBounds(b: Bounds): Bounds; + public addBounds2(mins, maxs): Bounds; + public addExtents(center: Vec2, extent_x: number, extent_y: number): Bounds; + public expand(ax: number, ay: number): Bounds; + public containPoint(p: Vec2): bool; + public intersectsBounds(b: Bounds): bool; + static expand(b: Bounds, ax, ay); + } +} +/** +* Phaser - Advanced Physics - IShape +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ +module Phaser.Physics.Advanced { + interface IShape { + id: number; + type: number; + elasticity: number; + friction: number; + density: number; + body: Body; + bounds: Bounds; + area(): number; + centroid(): Vec2; + inertia(mass: number): number; + cacheData(xf: Transform); + pointQuery(p: Vec2): bool; + findEdgeByPoint(p: Vec2, minDist: number): number; + findVertexByPoint(p: Vec2, minDist: number): number; + } +} +/** +* Phaser - Advanced Physics - Shape +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ +module Phaser.Physics.Advanced { + class Shape { + constructor(type: number); + public id: number; + public type: number; + public body: Body; + public elasticity: number; + public friction: number; + public density: number; + public bounds: Bounds; + public findEdgeByPoint(p: Vec2, minDist: number): number; + } +} +/** +* Phaser - Advanced Physics - Contact +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ +module Phaser.Physics.Advanced { + class Contact { + constructor(p, n, d, hash); + public hash; + public r1: Vec2; + public r2: Vec2; + public r1_local: Vec2; + public r2_local: Vec2; + public bounce: number; + public emn: number; + public emt: number; + public point; + public normal: Vec2; + public depth; + public lambdaNormal; + public lambdaTangential; + } +} +module Phaser.Physics.Advanced { + class ContactSolver { + constructor(shape1, shape2); + public shape1; + public shape2; + public contacts: Contact[]; + public elasticity: number; + public friction: number; + public update(newContactArr: Contact[]): void; + public initSolver(dt_inv): void; + public warmStart(): void; + public solveVelocityConstraints(): void; + public solvePositionConstraints(): bool; + public clamp(v, min, max); + } +} +/** +* Phaser - Advanced Physics - Shape - Circle +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ +module Phaser.Physics.Advanced.Shapes { + class Circle extends Shape implements IShape { + constructor(radius: number, x?: number, y?: number); + public radius: number; + public center: Vec2; + public tc: Vec2; + public finishVerts(): void; + public duplicate(): Circle; + public recenter(c: Vec2): void; + public transform(xf: Transform): void; + public untransform(xf: Transform): void; + public area(): number; + public centroid(): Vec2; + public inertia(mass: number): number; + public cacheData(xf: Transform): void; + public pointQuery(p: Vec2): bool; + public findVertexByPoint(p: Vec2, minDist: number): number; + public distanceOnPlane(n, d): void; + } +} +/** +* Phaser - Advanced Physics - Shapes - Convex Polygon +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ +module Phaser.Physics.Advanced.Shapes { + class Poly extends Shape implements IShape { + constructor(verts?: Vec2[]); + public verts: Vec2[]; + public planes; + public tverts; + public tplanes; + public convexity: bool; + public finishVerts(): void; + public duplicate(): Poly; + public recenter(c): void; + public transform(xf): void; + public untransform(xf): void; + public area(): number; + public centroid(): Vec2; + public inertia(mass: number): number; + public cacheData(xf: Transform): void; + public pointQuery(p: Vec2): bool; + public findVertexByPoint(p: Vec2, minDist: number): number; + public findEdgeByPoint(p: Vec2, minDist: number): number; + public distanceOnPlane(n, d): number; + public containPoint(p): bool; + public containPointPartial(p, n): bool; + } +} +/** +* Phaser - Advanced Physics - Shapes - Segment +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ +module Phaser.Physics.Advanced.Shapes { + class Segment extends Shape implements IShape { + constructor(a, b, radius: number); + public a: Vec2; + public b: Vec2; + public radius: number; + public normal: Vec2; + public ta: Vec2; + public tb: Vec2; + public tn: Vec2; + public finishVerts(): void; + public duplicate(): Segment; + public recenter(c): void; + public transform(xf: Transform): void; + public untransform(xf: Transform): void; + public area(): number; + public centroid(): Vec2; + public inertia(mass: number): number; + public cacheData(xf: Transform): void; + public pointQuery(p: Vec2): bool; + public findVertexByPoint(p: Vec2, minDist: number): number; + public distanceOnPlane(n, d): number; + } +} +/** +* Phaser - Advanced Physics - Collision Handlers +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ +module Phaser.Physics.Advanced { + class Collision { + constructor(); + public collide(a, b, contacts: Contact[]): number; + private _circle2Circle(c1, r1, c2, r2, contactArr); + public circle2Circle(circ1: Shapes.Circle, circ2: Shapes.Circle, contactArr: Contact[]): number; + public circle2Segment(circ: Shapes.Circle, seg: Shapes.Segment, contactArr: Contact[]): number; + public circle2Poly(circ: Shapes.Circle, poly: Shapes.Poly, contactArr: Contact[]): number; + public segmentPointDistanceSq(seg: Shapes.Segment, p): number; + public segment2Segment(seg1: Shapes.Segment, seg2: Shapes.Segment, contactArr: Contact[]): number; + public findPointsBehindSeg(contactArr: Contact[], seg: Shapes.Segment, poly: Shapes.Poly, dist: number, coef: number): void; + public segment2Poly(seg: Shapes.Segment, poly: Shapes.Poly, contactArr: Contact[]): number; + public findMSA(poly: Shapes.Poly, planes, num: number): { + dist: number; + index: number; + }; + public findVertsFallback(contactArr: Contact[], poly1: Shapes.Poly, poly2: Shapes.Poly, n, dist: number): number; + public findVerts(contactArr: Contact[], poly1: Shapes.Poly, poly2: Shapes.Poly, n, dist: number): number; + public poly2Poly(poly1: Shapes.Poly, poly2: Shapes.Poly, contactArr: Contact[]): number; + } +} +/** +* Phaser - Advanced Physics - Joint +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ +module Phaser.Physics.Advanced { + interface IJoint { + id: number; + type: number; + body1: Body; + body2: Body; + collideConnected; + maxForce: number; + breakable: bool; + anchor1: Vec2; + anchor2: Vec2; + getWorldAnchor1(); + getWorldAnchor2(); + setWorldAnchor1(anchor1); + setWorldAnchor2(anchor2); + initSolver(dt, warmStarting); + solveVelocityConstraints(); + solvePositionConstraints(); + getReactionForce(dt_inv); + } +} +/** +* Phaser - Advanced Physics - Space +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ +module Phaser.Physics.Advanced { + class Space { + constructor(); + static TIME_TO_SLEEP: number; + static SLEEP_LINEAR_TOLERANCE: number; + static SLEEP_ANGULAR_TOLERANCE: number; + public bodyArr: Body[]; + public bodyHash; + public jointArr: IJoint[]; + public jointHash; + public numContacts: number; + public contactSolvers: ContactSolver[]; + public postSolve; + public gravity: Vec2; + public damping: number; + public stepCount: number; + public clear(): void; + public addBody(body: Body): void; + public removeBody(body: Body): void; + public addJoint(joint: IJoint): void; + public removeJoint(joint: IJoint): void; + public findShapeByPoint(p, refShape); + public findBodyByPoint(p, refBody: Body); + public shapeById(id): IShape; + public jointById(id): IJoint; + public findVertexByPoint(p, minDist, refVertexId): number; + public findEdgeByPoint(p, minDist, refEdgeId): number; + public findJointByPoint(p, minDist, refJointId): number; + public findContactSolver(shape1, shape2): ContactSolver; + public genTemporalContactSolvers(): any[]; + public initSolver(dt, dt_inv, warmStarting): void; + public velocitySolver(iteration): void; + public positionSolver(iteration): bool; + public step(dt, vel_iteration, pos_iteration, warmStarting, allowSleep): void; + } +} +/** +* Phaser - Advanced Physics - Body +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ +module Phaser.Physics.Advanced { + class Body { + constructor(sprite: Sprite, type: number, x?: number, y?: number); + /** + * Reference to Phaser.Game + */ + public game: Game; + /** + * Reference to the parent Sprite + */ + public sprite: Sprite; + /** + * The Body ID + */ + public id: number; + /** + * The Body name + */ + public name: string; + /** + * The type of Body (disabled, dynamic, static or kinematic) + * Disabled = skips all physics operations / tests (default) + * Dynamic = gives and receives impacts + * Static = gives but doesn't receive impacts, cannot be moved by physics + * Kinematic = gives impacts, but never receives, can be moved by physics + * @type {number} + */ + public type: number; + public angle: number; + public transform: Transform; + public centroid: Vec2; + public position: Vec2; + public velocity: Vec2; + public force: Vec2; + public angularVelocity: number; + public torque: number; + public linearDamping: number; + public angularDamping: number; + public sleepTime: number; + public awaked: bool; + public shapes: IShape[]; + public joints: IJoint[]; + public jointHash: {}; + public bounds: Bounds; + public fixedRotation: bool; + public categoryBits: number; + public maskBits: number; + public stepCount: number; + public space: Space; + public duplicate(): void; + public isDisabled : bool; + public isStatic : bool; + public isKinetic : bool; + public isDynamic : bool; + public setType(type: number): void; + public addShape(shape); + public removeShape(shape): void; + public mass: number; + public massInverted: number; + public inertia: number; + public inertiaInverted: number; + private setMass(mass); + private setInertia(inertia); + public setTransform(pos, angle): void; + public syncTransform(): void; + public getWorldPoint(p: Vec2): Vec2; + public getWorldVector(v): Vec2; + public getLocalPoint(p): Vec2; + public getLocalVector(v): Vec2; + public setFixedRotation(flag): void; + public resetMassData(): void; + public resetJointAnchors(): void; + public cacheData(): void; + private _tempVec2; + public updateVelocity(gravity, dt, damping): void; + public updatePosition(dt): void; + public resetForce(): void; + public applyForce(force, p): void; + public applyForceToCenter(force): void; + public applyTorque(torque): void; + public applyLinearImpulse(impulse, p): void; + public applyAngularImpulse(impulse: number): void; + public kineticEnergy(): number; + public isAwake : bool; + public awake(flag): void; + public isCollidable(other): bool; + } +} +/** +* Phaser - Advanced Physics - Shapes - Box +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ +module Phaser.Physics.Advanced.Shapes { + class Box extends Poly { + constructor(x, y, width, height); + } +} +/** +* Phaser - Advanced Physics - Shapes - Triangle +* +* Based on the work Ju Hyung Lee started in JS PhyRus. +*/ +module Phaser.Physics.Advanced.Shapes { + class Triangle extends Poly { + constructor(p1, p2, p3); + } +} +/** * Phaser - PixelUtils * * A collection of methods useful for manipulating pixels. diff --git a/build/phaser.js b/build/phaser.js index 6826f466..0f9b2dab 100644 --- a/build/phaser.js +++ b/build/phaser.js @@ -833,6 +833,17 @@ var Phaser; function () { return (this.x * this.x) + (this.y * this.y); }; + Vec2.prototype.normalize = /** + * Normalize this vector. + * + * @return {Vec2} This for chaining. + */ + function () { + var inv = (this.x != 0 || this.y != 0) ? 1 / Math.sqrt(this.x * this.x + this.y * this.y) : 0; + this.x *= inv; + this.y *= inv; + return this; + }; Vec2.prototype.dot = /** * The dot product of two 2D vectors. * @@ -897,6 +908,18 @@ var Phaser; this.y *= scalar; return this; }; + Vec2.prototype.multiplyAddByScalar = /** + * Adds the given vector to this vector then multiplies by the given scalar. + * + * @param {Vec2} a Reference to a source Vec2 object. + * @param {number} scalar + * @return {Vec2} This for chaining. + */ + function (a, scalar) { + this.x += a.x * scalar; + this.y += a.y * scalar; + return this; + }; Vec2.prototype.divideByScalar = /** * Divide this vector by the given scalar. * @@ -1247,9 +1270,9 @@ var Phaser; Types.GEOM_LINE = 3; Types.GEOM_POLYGON = 4; Types.BODY_DISABLED = 0; - Types.BODY_DYNAMIC = 1; - Types.BODY_STATIC = 2; - Types.BODY_KINEMATIC = 3; + Types.BODY_STATIC = 1; + Types.BODY_KINETIC = 2; + Types.BODY_DYNAMIC = 3; Types.LEFT = 0x0001; Types.RIGHT = 0x0010; Types.UP = 0x0100; @@ -3890,7 +3913,7 @@ var Phaser; var Components = Phaser.Components; })(Phaser || (Phaser = {})); /// -/// +/// /** * Phaser - Vec2Utils * @@ -3961,14 +3984,49 @@ var Phaser; if (typeof out === "undefined") { out = new Phaser.Vec2(); } return out.setTo(a.x * s, a.y * s); }; + Vec2Utils.multiplyAdd = /** + * Adds two 2D vectors together and multiplies the result by the given scalar. + * + * @param {Vec2} a Reference to a source Vec2 object. + * @param {Vec2} b Reference to a source Vec2 object. + * @param {number} s Scaling value. + * @param {Vec2} out The output Vec2 that is the result of the operation. + * @return {Vec2} A Vec2 that is the sum of the two vectors added and multiplied. + */ + function multiplyAdd(a, b, s, out) { + if (typeof out === "undefined") { out = new Phaser.Vec2(); } + return out.setTo(a.x + b.x * s, a.y + b.y * s); + }; + Vec2Utils.negative = /** + * Return a negative vector. + * + * @param {Vec2} a Reference to a source Vec2 object. + * @param {Vec2} out The output Vec2 that is the result of the operation. + * @return {Vec2} A Vec2 that is the negative vector. + */ + function negative(a, out) { + if (typeof out === "undefined") { out = new Phaser.Vec2(); } + return out.setTo(-a.x, -a.y); + }; Vec2Utils.perp = /** - * Rotate a 2D vector by 90 degrees. + * Return a perpendicular vector (90 degrees rotation) * * @param {Vec2} a Reference to a source Vec2 object. * @param {Vec2} out The output Vec2 that is the result of the operation. * @return {Vec2} A Vec2 that is the scaled vector. */ function perp(a, out) { + if (typeof out === "undefined") { out = new Phaser.Vec2(); } + return out.setTo(-a.y, a.x); + }; + Vec2Utils.rperp = /** + * Return a perpendicular vector (-90 degrees rotation) + * + * @param {Vec2} a Reference to a source Vec2 object. + * @param {Vec2} out The output Vec2 that is the result of the operation. + * @return {Vec2} A Vec2 that is the scaled vector. + */ + function rperp(a, out) { if (typeof out === "undefined") { out = new Phaser.Vec2(); } return out.setTo(a.y, -a.x); }; @@ -4111,7 +4169,7 @@ var Phaser; function angleSq(a, b) { return a.subtract(b).angle(b.subtract(a)); }; - Vec2Utils.rotate = /** + Vec2Utils.rotateAroundOrigin = /** * Rotate a 2D vector around the origin to the given angle (theta). * * @param {Vec2} a Reference to a source Vec2 object. @@ -4120,12 +4178,27 @@ var Phaser; * @param {Vec2} out The output Vec2 that is the result of the operation. * @return {Vec2} A Vec2. */ - function rotate(a, b, theta, out) { + function rotateAroundOrigin(a, b, theta, out) { if (typeof out === "undefined") { out = new Phaser.Vec2(); } var x = a.x - b.x; var y = a.y - b.y; return out.setTo(x * Math.cos(theta) - y * Math.sin(theta) + b.x, x * Math.sin(theta) + y * Math.cos(theta) + b.y); }; + Vec2Utils.rotate = /** + * Rotate a 2D vector to the given angle (theta). + * + * @param {Vec2} a Reference to a source Vec2 object. + * @param {Vec2} b Reference to a source Vec2 object. + * @param {Number} theta The angle of rotation in radians. + * @param {Vec2} out The output Vec2 that is the result of the operation. + * @return {Vec2} A Vec2. + */ + function rotate(a, theta, out) { + if (typeof out === "undefined") { out = new Phaser.Vec2(); } + var c = Math.cos(theta); + var s = Math.sin(theta); + return out.setTo(a.x * c - a.y * s, a.x * s + a.y * c); + }; Vec2Utils.clone = /** * Clone a 2D vector. * @@ -5506,18 +5579,20 @@ var Phaser; } this.sort(); // What's the z index of the top most child? - var tempZ = child.z; var childIndex = this._zCounter; this._i = 0; while(this._i < this.length) { this._member = this.members[this._i++]; - if(this._i > childIndex) { - this._member.z--; - } else if(this._member.z == child.z) { - childIndex = this._i; - this._member.z = this._zCounter; + if(this._member) { + if(this._i > childIndex) { + this._member.z--; + } else if(this._member.z == child.z) { + childIndex = this._i; + this._member.z = this._zCounter; + } } } + // Maybe redundant? this.sort(); return true; }; @@ -6805,13 +6880,39 @@ var Phaser; } return null; }; - Cache.prototype.getImageKeys = function () { + Cache.prototype.getImageKeys = /** + * Returns an array containing all of the keys of Images in the Cache. + * @return {Array} The string based keys in the Cache. + */ + function () { var output = []; for(var item in this._images) { output.push(item); } return output; }; + Cache.prototype.getSoundKeys = /** + * Returns an array containing all of the keys of Sounds in the Cache. + * @return {Array} The string based keys in the Cache. + */ + function () { + var output = []; + for(var item in this._sounds) { + output.push(item); + } + return output; + }; + Cache.prototype.getTextKeys = /** + * Returns an array containing all of the keys of Text Files in the Cache. + * @return {Array} The string based keys in the Cache. + */ + function () { + var output = []; + for(var item in this._text) { + output.push(item); + } + return output; + }; Cache.prototype.destroy = /** * Clean up cache memory. */ @@ -13503,7 +13604,7 @@ var Phaser; return this._groupCounter++; }; World.prototype.boot = /** - * Called one by Game during the boot process. + * Called once by Game during the boot process. */ function () { this.group = new Phaser.Group(this._game, 0); @@ -17940,6 +18041,3566 @@ var Phaser; Phaser.Mat3Utils = Mat3Utils; })(Phaser || (Phaser = {})); /// +/// +/** +* Phaser - 2D Transform +* +* A 2D Transform +*/ +var Phaser; +(function (Phaser) { + var Transform = (function () { + /** + * Creates a new 2D Transform object. + * @class Transform + * @constructor + * @return {Transform} This object + **/ + function Transform(pos, angle) { + this.t = Phaser.Vec2Utils.clone(pos); + this.c = Math.cos(angle); + this.s = Math.sin(angle); + } + Transform.prototype.setTo = function (pos, angle) { + this.t.copyFrom(pos); + this.c = Math.cos(angle); + this.s = Math.sin(angle); + return this; + }; + Transform.prototype.setRotation = function (angle) { + this.c = Math.cos(angle); + this.s = Math.sin(angle); + return this; + }; + Transform.prototype.setPosition = function (p) { + this.t.copyFrom(p); + return this; + }; + Transform.prototype.identity = function () { + this.t.setTo(0, 0); + this.c = 1; + this.s = 0; + return this; + }; + return Transform; + })(); + Phaser.Transform = Transform; +})(Phaser || (Phaser = {})); +/// +/// +/// +/** +* Phaser - TransformUtils +* +* A collection of methods useful for manipulating and performing operations on 2D Transforms. +* +*/ +var Phaser; +(function (Phaser) { + var TransformUtils = (function () { + function TransformUtils() { } + TransformUtils.rotate = function rotate(t, v, out) { + if (typeof out === "undefined") { out = new Phaser.Vec2(); } + return out.setTo(v.x * t.c - v.y * t.s, v.x * t.s + v.y * t.c); + }; + TransformUtils.unrotate = function unrotate(t, v, out) { + if (typeof out === "undefined") { out = new Phaser.Vec2(); } + return out.setTo(v.x * t.c + v.y * t.s, -v.x * t.s + v.y * t.c); + }; + TransformUtils.transform = function transform(t, v, out) { + if (typeof out === "undefined") { out = new Phaser.Vec2(); } + return out.setTo(v.x * t.c - v.y * t.s + t.t.x, v.x * t.s + v.y * t.c + t.t.y); + }; + TransformUtils.untransform = function untransform(t, v, out) { + if (typeof out === "undefined") { out = new Phaser.Vec2(); } + var px = v.x - t.t.x; + var py = v.y - t.t.y; + return out.setTo(px * t.c + py * t.s, -px * t.s + py * t.c); + }; + return TransformUtils; + })(); + Phaser.TransformUtils = TransformUtils; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + /// + /// + /// + /// + /// + /** + * Phaser - PhysicsManager + * + * Your game only has one PhysicsManager instance and it's responsible for looking after, creating and colliding + * all of the physics objects in the world. + */ + (function (Physics) { + var ArcadePhysics = (function () { + function ArcadePhysics(game, width, height) { + this._length = 0; + /** + * @type {number} + */ + this.worldDivisions = 6; + this.game = game; + this.gravity = new Phaser.Vec2(); + this.drag = new Phaser.Vec2(); + this.bounce = new Phaser.Vec2(); + this.angularDrag = 0; + this.bounds = new Phaser.Rectangle(0, 0, width, height); + this._distance = new Phaser.Vec2(); + this._tangent = new Phaser.Vec2(); + this.members = new Phaser.Group(game); + } + ArcadePhysics.OVERLAP_BIAS = 4; + ArcadePhysics.TILE_OVERLAP = false; + ArcadePhysics.prototype.updateMotion = /* + public update() { + + this._length = this._objects.length; + + for (var i = 0; i < this._length; i++) + { + if (this._objects[i]) + { + this._objects[i].preUpdate(); + this.updateMotion(this._objects[i]); + this.collideWorld(this._objects[i]); + + for (var x = 0; x < this._length; x++) + { + if (this._objects[x] && this._objects[x] !== this._objects[i]) + { + //this.collideShapes(this._objects[i], this._objects[x]); + var r = this.NEWseparate(this._objects[i], this._objects[x]); + //console.log('sep', r); + } + } + + } + } + + } + + public render() { + + // iterate through the objects here, updating and colliding + for (var i = 0; i < this._length; i++) + { + if (this._objects[i]) + { + this._objects[i].render(this.game.stage.context); + } + } + + } + */ + function (body) { + if(body.type == Phaser.Types.BODY_DISABLED) { + return; + } + this._velocityDelta = (this.computeVelocity(body.angularVelocity, body.gravity.x, body.angularAcceleration, body.angularDrag, body.maxAngular) - body.angularVelocity) / 2; + body.angularVelocity += this._velocityDelta; + body.sprite.transform.rotation += body.angularVelocity * this.game.time.elapsed; + body.angularVelocity += this._velocityDelta; + this._velocityDelta = (this.computeVelocity(body.velocity.x, body.gravity.x, body.acceleration.x, body.drag.x) - body.velocity.x) / 2; + body.velocity.x += this._velocityDelta; + this._delta = body.velocity.x * this.game.time.elapsed; + body.velocity.x += this._velocityDelta; + //body.position.x += this._delta; + body.sprite.x += this._delta; + this._velocityDelta = (this.computeVelocity(body.velocity.y, body.gravity.y, body.acceleration.y, body.drag.y) - body.velocity.y) / 2; + body.velocity.y += this._velocityDelta; + this._delta = body.velocity.y * this.game.time.elapsed; + body.velocity.y += this._velocityDelta; + //body.position.y += this._delta; + body.sprite.y += this._delta; + }; + ArcadePhysics.prototype.computeVelocity = /** + * A tween-like function that takes a starting velocity and some other factors and returns an altered velocity. + * + * @param {number} Velocity Any component of velocity (e.g. 20). + * @param {number} Acceleration Rate at which the velocity is changing. + * @param {number} Drag Really kind of a deceleration, this is how much the velocity changes if Acceleration is not set. + * @param {number} Max An absolute value cap for the velocity. + * + * @return {number} The altered Velocity value. + */ + function (velocity, gravity, acceleration, drag, max) { + if (typeof gravity === "undefined") { gravity = 0; } + if (typeof acceleration === "undefined") { acceleration = 0; } + if (typeof drag === "undefined") { drag = 0; } + if (typeof max === "undefined") { max = 10000; } + if(acceleration !== 0) { + velocity += (acceleration + gravity) * this.game.time.elapsed; + } else if(drag !== 0) { + this._drag = drag * this.game.time.elapsed; + if(velocity - this._drag > 0) { + velocity = velocity - this._drag; + } else if(velocity + this._drag < 0) { + velocity += this._drag; + } else { + velocity = 0; + } + velocity += gravity; + } + if((velocity != 0) && (max != 10000)) { + if(velocity > max) { + velocity = max; + } else if(velocity < -max) { + velocity = -max; + } + } + return velocity; + }; + ArcadePhysics.prototype.separate = /** + * The core Collision separation method. + * @param body1 The first Physics.Body to separate + * @param body2 The second Physics.Body to separate + * @returns {boolean} Returns true if the bodies were separated, otherwise false. + */ + function (body1, body2) { + this._separatedX = this.separateBodyX(body1, body2); + this._separatedY = this.separateBodyY(body1, body2); + return this._separatedX || this._separatedY; + }; + ArcadePhysics.prototype.checkHullIntersection = function (body1, body2) { + return ((body1.hullX + body1.hullWidth > body2.hullX) && (body1.hullX < body2.hullX + body2.hullWidth) && (body1.hullY + body1.hullHeight > body2.hullY) && (body1.hullY < body2.hullY + body2.hullHeight)); + }; + ArcadePhysics.prototype.separateBodyX = /** + * 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 (body1, body2) { + // Can't separate two disabled or static objects + if((body1.type == Phaser.Types.BODY_DISABLED || body1.type == Phaser.Types.BODY_STATIC) && (body2.type == Phaser.Types.BODY_DISABLED || body2.type == Phaser.Types.BODY_STATIC)) { + return false; + } + // First, get the two object deltas + this._overlap = 0; + if(body1.deltaX != body2.deltaX) { + if(Phaser.RectangleUtils.intersects(body1.bounds, body2.bounds)) { + this._maxOverlap = body1.deltaXAbs + body2.deltaXAbs + Physics.PhysicsManager.OVERLAP_BIAS; + // If they did overlap (and can), figure out by how much and flip the corresponding flags + if(body1.deltaX > body2.deltaX) { + this._overlap = body1.bounds.right - body2.bounds.x; + if((this._overlap > this._maxOverlap) || !(body1.allowCollisions & Phaser.Types.RIGHT) || !(body2.allowCollisions & Phaser.Types.LEFT)) { + this._overlap = 0; + } else { + body1.touching |= Phaser.Types.RIGHT; + body2.touching |= Phaser.Types.LEFT; + } + } else if(body1.deltaX < body2.deltaX) { + this._overlap = body1.bounds.x - body2.bounds.width - body2.bounds.x; + if((-this._overlap > this._maxOverlap) || !(body1.allowCollisions & Phaser.Types.LEFT) || !(body2.allowCollisions & Phaser.Types.RIGHT)) { + this._overlap = 0; + } else { + body1.touching |= Phaser.Types.LEFT; + body2.touching |= Phaser.Types.RIGHT; + } + } + } + } + // Then adjust their positions and velocities accordingly (if there was any overlap) + if(this._overlap != 0) { + this._obj1Velocity = body1.velocity.x; + this._obj2Velocity = body2.velocity.x; + /** + * Dynamic = gives and receives impacts + * Static = gives but doesn't receive impacts, cannot be moved by physics + * Kinematic = gives impacts, but never receives, can be moved by physics + */ + // 2 dynamic bodies will exchange velocities + if(body1.type == Phaser.Types.BODY_DYNAMIC && body2.type == Phaser.Types.BODY_DYNAMIC) { + this._overlap *= 0.5; + body1.position.x = body1.position.x - this._overlap; + body2.position.x += this._overlap; + this._obj1NewVelocity = Math.sqrt((this._obj2Velocity * this._obj2Velocity * body2.mass) / body1.mass) * ((this._obj2Velocity > 0) ? 1 : -1); + this._obj2NewVelocity = Math.sqrt((this._obj1Velocity * this._obj1Velocity * body1.mass) / body2.mass) * ((this._obj1Velocity > 0) ? 1 : -1); + this._average = (this._obj1NewVelocity + this._obj2NewVelocity) * 0.5; + this._obj1NewVelocity -= this._average; + this._obj2NewVelocity -= this._average; + body1.velocity.x = this._average + this._obj1NewVelocity * body1.bounce.x; + body2.velocity.x = this._average + this._obj2NewVelocity * body2.bounce.x; + } else if(body2.type != Phaser.Types.BODY_DYNAMIC) { + // Body 2 is Static or Kinematic + this._overlap *= 2; + body1.position.x -= this._overlap; + body1.velocity.x = this._obj2Velocity - this._obj1Velocity * body1.bounce.x; + } else if(body1.type != Phaser.Types.BODY_DYNAMIC) { + // Body 1 is Static or Kinematic + this._overlap *= 2; + body2.position.x += this._overlap; + body2.velocity.x = this._obj1Velocity - this._obj2Velocity * body2.bounce.x; + } + return true; + } else { + return false; + } + }; + ArcadePhysics.prototype.separateBodyY = /** + * 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 (body1, body2) { + // Can't separate two immovable objects + if((body1.type == Phaser.Types.BODY_DISABLED || body1.type == Phaser.Types.BODY_STATIC) && (body2.type == Phaser.Types.BODY_DISABLED || body2.type == Phaser.Types.BODY_STATIC)) { + return false; + } + // First, get the two object deltas + this._overlap = 0; + if(body1.deltaY != body2.deltaY) { + if(Phaser.RectangleUtils.intersects(body1.bounds, body2.bounds)) { + // This is the only place to use the DeltaAbs values + this._maxOverlap = body1.deltaYAbs + body2.deltaYAbs + Physics.PhysicsManager.OVERLAP_BIAS; + // If they did overlap (and can), figure out by how much and flip the corresponding flags + if(body1.deltaY > body2.deltaY) { + this._overlap = body1.bounds.bottom - body2.bounds.y; + if((this._overlap > this._maxOverlap) || !(body1.allowCollisions & Phaser.Types.DOWN) || !(body2.allowCollisions & Phaser.Types.UP)) { + this._overlap = 0; + } else { + body1.touching |= Phaser.Types.DOWN; + body2.touching |= Phaser.Types.UP; + } + } else if(body1.deltaY < body2.deltaY) { + this._overlap = body1.bounds.y - body2.bounds.height - body2.bounds.y; + if((-this._overlap > this._maxOverlap) || !(body1.allowCollisions & Phaser.Types.UP) || !(body2.allowCollisions & Phaser.Types.DOWN)) { + this._overlap = 0; + } else { + body1.touching |= Phaser.Types.UP; + body2.touching |= Phaser.Types.DOWN; + } + } + } + } + // Then adjust their positions and velocities accordingly (if there was any overlap) + if(this._overlap != 0) { + this._obj1Velocity = body1.velocity.y; + this._obj2Velocity = body2.velocity.y; + /** + * Dynamic = gives and receives impacts + * Static = gives but doesn't receive impacts, cannot be moved by physics + * Kinematic = gives impacts, but never receives, can be moved by physics + */ + if(body1.type == Phaser.Types.BODY_DYNAMIC && body2.type == Phaser.Types.BODY_DYNAMIC) { + this._overlap *= 0.5; + body1.position.y = body1.position.y - this._overlap; + body2.position.y += this._overlap; + this._obj1NewVelocity = Math.sqrt((this._obj2Velocity * this._obj2Velocity * body2.mass) / body1.mass) * ((this._obj2Velocity > 0) ? 1 : -1); + this._obj2NewVelocity = Math.sqrt((this._obj1Velocity * this._obj1Velocity * body1.mass) / body2.mass) * ((this._obj1Velocity > 0) ? 1 : -1); + var average = (this._obj1NewVelocity + this._obj2NewVelocity) * 0.5; + this._obj1NewVelocity -= average; + this._obj2NewVelocity -= average; + body1.velocity.y = average + this._obj1NewVelocity * body1.bounce.y; + body2.velocity.y = average + this._obj2NewVelocity * body2.bounce.y; + } else if(body2.type != Phaser.Types.BODY_DYNAMIC) { + this._overlap *= 2; + body1.position.y -= this._overlap; + body1.velocity.y = this._obj2Velocity - this._obj1Velocity * body1.bounce.y; + // This is special case code that handles things like horizontal moving platforms you can ride + //if (body2.parent.active && body2.moves && (body1.deltaY > body2.deltaY)) + if(body2.sprite.active && (body1.deltaY > body2.deltaY)) { + body1.position.x += body2.position.x - body2.oldPosition.x; + } + } else if(body1.type != Phaser.Types.BODY_DYNAMIC) { + this._overlap *= 2; + body2.position.y += this._overlap; + body2.velocity.y = this._obj1Velocity - this._obj2Velocity * body2.bounce.y; + // This is special case code that handles things like horizontal moving platforms you can ride + //if (object1.active && body1.moves && (body1.deltaY < body2.deltaY)) + if(body1.sprite.active && (body1.deltaY < body2.deltaY)) { + body2.position.x += body1.position.x - body1.oldPosition.x; + } + } + return true; + } else { + return false; + } + }; + ArcadePhysics.prototype.overlap = /* + private TILEseparate(shapeA: IPhysicsShape, shapeB: IPhysicsShape, distance: Vec2, tangent: Vec2) { + + if (tangent.x == 1) + { + console.log('1 The left side of ShapeA hit the right side of ShapeB', Math.floor(distance.x)); + shapeA.physics.touching |= Phaser.Types.LEFT; + shapeB.physics.touching |= Phaser.Types.RIGHT; + } + else if (tangent.x == -1) + { + console.log('2 The right side of ShapeA hit the left side of ShapeB', Math.floor(distance.x)); + shapeA.physics.touching |= Phaser.Types.RIGHT; + shapeB.physics.touching |= Phaser.Types.LEFT; + } + + if (tangent.y == 1) + { + console.log('3 The top of ShapeA hit the bottom of ShapeB', Math.floor(distance.y)); + shapeA.physics.touching |= Phaser.Types.UP; + shapeB.physics.touching |= Phaser.Types.DOWN; + } + else if (tangent.y == -1) + { + console.log('4 The bottom of ShapeA hit the top of ShapeB', Math.floor(distance.y)); + shapeA.physics.touching |= Phaser.Types.DOWN; + shapeB.physics.touching |= Phaser.Types.UP; + } + + + // only apply collision response forces if the object is travelling into, and not out of, the collision + var dot = Vec2Utils.dot(shapeA.physics.velocity, tangent); + + if (dot < 0) + { + console.log('in to', dot); + + // Apply horizontal bounce + if (shapeA.physics.bounce.x > 0) + { + shapeA.physics.velocity.x *= -(shapeA.physics.bounce.x); + } + else + { + shapeA.physics.velocity.x = 0; + } + // Apply horizontal bounce + if (shapeA.physics.bounce.y > 0) + { + shapeA.physics.velocity.y *= -(shapeA.physics.bounce.y); + } + else + { + shapeA.physics.velocity.y = 0; + } + } + else + { + console.log('out of', dot); + } + + shapeA.position.x += Math.floor(distance.x); + //shapeA.bounds.x += Math.floor(distance.x); + + shapeA.position.y += Math.floor(distance.y); + //shapeA.bounds.y += distance.y; + + console.log('------------------------------------------------'); + + } + + private collideWorld(shape:IPhysicsShape) { + + // Collide on the x-axis + this._distance.x = shape.world.bounds.x - (shape.position.x - shape.bounds.halfWidth); + + if (0 < this._distance.x) + { + // Hit Left + this._tangent.setTo(1, 0); + this.separateXWall(shape, this._distance, this._tangent); + } + else + { + this._distance.x = (shape.position.x + shape.bounds.halfWidth) - shape.world.bounds.right; + + if (0 < this._distance.x) + { + // Hit Right + this._tangent.setTo(-1, 0); + this._distance.reverse(); + this.separateXWall(shape, this._distance, this._tangent); + } + } + + // Collide on the y-axis + this._distance.y = shape.world.bounds.y - (shape.position.y - shape.bounds.halfHeight); + + if (0 < this._distance.y) + { + // Hit Top + this._tangent.setTo(0, 1); + this.separateYWall(shape, this._distance, this._tangent); + } + else + { + this._distance.y = (shape.position.y + shape.bounds.halfHeight) - shape.world.bounds.bottom; + + if (0 < this._distance.y) + { + // Hit Bottom + this._tangent.setTo(0, -1); + this._distance.reverse(); + this.separateYWall(shape, this._distance, this._tangent); + } + } + + } + + private separateX(shapeA: IPhysicsShape, shapeB: IPhysicsShape, distance: Vec2, tangent: Vec2) { + + if (tangent.x == 1) + { + console.log('The left side of ShapeA hit the right side of ShapeB', distance.x); + shapeA.physics.touching |= Phaser.Types.LEFT; + shapeB.physics.touching |= Phaser.Types.RIGHT; + } + else + { + console.log('The right side of ShapeA hit the left side of ShapeB', distance.x); + shapeA.physics.touching |= Phaser.Types.RIGHT; + shapeB.physics.touching |= Phaser.Types.LEFT; + } + + // collision edges + //shapeA.oH = tangent.x; + + // only apply collision response forces if the object is travelling into, and not out of, the collision + if (Vec2Utils.dot(shapeA.physics.velocity, tangent) < 0) + { + // Apply horizontal bounce + if (shapeA.physics.bounce.x > 0) + { + shapeA.physics.velocity.x *= -(shapeA.physics.bounce.x); + } + else + { + shapeA.physics.velocity.x = 0; + } + } + + shapeA.position.x += distance.x; + shapeA.bounds.x += distance.x; + + } + + private separateY(shapeA: IPhysicsShape, shapeB: IPhysicsShape, distance: Vec2, tangent: Vec2) { + + if (tangent.y == 1) + { + console.log('The top of ShapeA hit the bottom of ShapeB', distance.y); + shapeA.physics.touching |= Phaser.Types.UP; + shapeB.physics.touching |= Phaser.Types.DOWN; + } + else + { + console.log('The bottom of ShapeA hit the top of ShapeB', distance.y); + shapeA.physics.touching |= Phaser.Types.DOWN; + shapeB.physics.touching |= Phaser.Types.UP; + } + + // collision edges + //shapeA.oV = tangent.y; + + // only apply collision response forces if the object is travelling into, and not out of, the collision + if (Vec2Utils.dot(shapeA.physics.velocity, tangent) < 0) + { + // Apply horizontal bounce + if (shapeA.physics.bounce.y > 0) + { + shapeA.physics.velocity.y *= -(shapeA.physics.bounce.y); + } + else + { + shapeA.physics.velocity.y = 0; + } + } + + shapeA.position.y += distance.y; + shapeA.bounds.y += distance.y; + + } + + private separateXWall(shapeA: IPhysicsShape, distance: Vec2, tangent: Vec2) { + + if (tangent.x == 1) + { + console.log('The left side of ShapeA hit the right side of ShapeB', distance.x); + shapeA.physics.touching |= Phaser.Types.LEFT; + } + else + { + console.log('The right side of ShapeA hit the left side of ShapeB', distance.x); + shapeA.physics.touching |= Phaser.Types.RIGHT; + } + + // collision edges + //shapeA.oH = tangent.x; + + // only apply collision response forces if the object is travelling into, and not out of, the collision + if (Vec2Utils.dot(shapeA.physics.velocity, tangent) < 0) + { + // Apply horizontal bounce + if (shapeA.physics.bounce.x > 0) + { + shapeA.physics.velocity.x *= -(shapeA.physics.bounce.x); + } + else + { + shapeA.physics.velocity.x = 0; + } + } + + shapeA.position.x += distance.x; + + } + + private separateYWall(shapeA: IPhysicsShape, distance: Vec2, tangent: Vec2) { + + if (tangent.y == 1) + { + console.log('The top of ShapeA hit the bottom of ShapeB', distance.y); + shapeA.physics.touching |= Phaser.Types.UP; + } + else + { + console.log('The bottom of ShapeA hit the top of ShapeB', distance.y); + shapeA.physics.touching |= Phaser.Types.DOWN; + } + + // collision edges + //shapeA.oV = tangent.y; + + // only apply collision response forces if the object is travelling into, and not out of, the collision + if (Vec2Utils.dot(shapeA.physics.velocity, tangent) < 0) + { + // Apply horizontal bounce + if (shapeA.physics.bounce.y > 0) + { + shapeA.physics.velocity.y *= -(shapeA.physics.bounce.y); + } + else + { + shapeA.physics.velocity.y = 0; + } + } + + shapeA.position.y += distance.y; + + } + */ + /** + * Checks for overlaps between two objects using the world QuadTree. Can be Sprite vs. Sprite, Sprite 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 Sprite or Group to check. If null the world.group is used. + * @param object2 The second Sprite or Group to check. + * @param notifyCallback A callback function that is called if the objects overlap. The two objects will be passed to this function in the same order in which you passed them to Collision.overlap. + * @param processCallback A callback function that lets you perform additional checks against the two objects if they overlap. If this is set then notifyCallback will only be called if processCallback returns true. + * @param context The context in which the callbacks will be called + * @returns {boolean} true if the objects overlap, otherwise false. + */ + function (object1, object2, notifyCallback, processCallback, context) { + if (typeof object1 === "undefined") { object1 = null; } + if (typeof object2 === "undefined") { object2 = null; } + if (typeof notifyCallback === "undefined") { notifyCallback = null; } + if (typeof processCallback === "undefined") { processCallback = null; } + if (typeof context === "undefined") { context = null; } + /* + if (object1 == null) + { + object1 = this.game.world.group; + } + + if (object2 == object1) + { + object2 = null; + } + + QuadTree.divisions = this.worldDivisions; + + this._quadTree = new Phaser.QuadTree(this, this.bounds.x, this.bounds.y, this.bounds.width, this.bounds.height); + + this._quadTree.load(object1, object2, notifyCallback, processCallback, context); + + this._quadTreeResult = this._quadTree.execute(); + + console.log('over', this._quadTreeResult); + + this._quadTree.destroy(); + + this._quadTree = null; + + return this._quadTreeResult; + */ + return false; + }; + ArcadePhysics.prototype.separateTile = /** + * Collision resolution specifically for GameObjects vs. Tiles. + * @param object The GameObject to separate + * @param tile The Tile to separate + * @returns {boolean} Whether the objects in fact touched and were separated + */ + function (object, x, y, width, height, mass, collideLeft, collideRight, collideUp, collideDown, separateX, separateY) { + //var separatedX: bool = this.separateTileX(object, x, y, width, height, mass, collideLeft, collideRight, separateX); + //var separatedY: bool = this.separateTileY(object, x, y, width, height, mass, collideUp, collideDown, separateY); + //return separatedX || separatedY; + return false; + }; + return ArcadePhysics; + })(); + Physics.ArcadePhysics = ArcadePhysics; + /** + * 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 separateTileX(object:Sprite, 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; + 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: Rectangle = new Rectangle(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: number = objDeltaAbs + Collision.OVERLAP_BIAS; + + // If they did overlap (and can), figure out by how much and flip the corresponding flags + if (objDelta > 0) + { + overlap = object.x + object.width - x; + + if ((overlap > maxOverlap) || !(object.allowCollisions & Collision.RIGHT) || collideLeft == false) + { + overlap = 0; + } + else + { + object.touching |= Collision.RIGHT; + } + } + else if (objDelta < 0) + { + overlap = object.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) + { + //console.log(' + 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 separateTileY(object: Sprite, 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 (objDelta != 0) + { + // Check if the Y hulls actually overlap + var objDeltaAbs: number = (objDelta > 0) ? objDelta : -objDelta; + var objBounds: Rectangle = new Rectangle(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)) + { + var maxOverlap: number = objDeltaAbs + Collision.OVERLAP_BIAS; + + // If they did overlap (and can), figure out by how much and flip the corresponding flags + if (objDelta > 0) + { + overlap = object.y + object.height - y; + + if ((overlap > maxOverlap) || !(object.allowCollisions & Collision.DOWN) || collideUp == false) + { + overlap = 0; + } + else + { + object.touching |= Collision.DOWN; + } + } + else if (objDelta < 0) + { + overlap = object.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 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:Sprite, 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: Rectangle = new Rectangle(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 NEWseparateTileY(object: Sprite, 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: Rectangle = new Rectangle(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; + } + } + */ + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - Joint + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + (function (Advanced) { + var Joint = (function () { + function Joint(type, body1, body2, collideConnected) { + this.id = Phaser.Physics.Advanced.Manager.jointCounter++; + this.type = type; + this.body1 = body1; + this.body2 = body2; + this.collideConnected = collideConnected; + this.maxForce = 9999999999; + this.breakable = false; + } + Joint.prototype.getWorldAnchor1 = function () { + return this.body1.getWorldPoint(this.anchor1); + }; + Joint.prototype.getWorldAnchor2 = function () { + return this.body2.getWorldPoint(this.anchor2); + }; + Joint.prototype.setWorldAnchor1 = function (anchor1) { + this.anchor1 = this.body1.getLocalPoint(anchor1); + }; + Joint.prototype.setWorldAnchor2 = function (anchor2) { + this.anchor2 = this.body2.getLocalPoint(anchor2); + }; + return Joint; + })(); + Advanced.Joint = Joint; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + /// + /// + /// + /** + * Phaser - Advanced Physics Manager + * + * Your game only has one PhysicsManager instance and it's responsible for looking after, creating and colliding + * all of the physics objects in the world. + */ + (function (Advanced) { + var Manager = (function () { + function Manager(game) { + this.lastTime = Date.now(); + this.frameRateHz = 60; + this.timeDelta = 0; + this.paused = false; + this.step = false; + // step through the simulation (i.e. per click) + this.velocityIterations = 8; + this.positionIterations = 4; + this.allowSleep = true; + this.warmStarting = true; + this.game = game; + this.space = new Advanced.Space(); + Manager.collision = new Advanced.Collision(); + } + Manager.SHAPE_TYPE_CIRCLE = 0; + Manager.SHAPE_TYPE_SEGMENT = 1; + Manager.SHAPE_TYPE_POLY = 2; + Manager.SHAPE_NUM_TYPES = 3; + Manager.JOINT_TYPE_ANGLE = 0; + Manager.JOINT_TYPE_REVOLUTE = 1; + Manager.JOINT_TYPE_WELD = 2; + Manager.JOINT_TYPE_WHEEL = 3; + Manager.JOINT_TYPE_PRISMATIC = 4; + Manager.JOINT_TYPE_DISTANCE = 5; + Manager.JOINT_TYPE_ROPE = 6; + Manager.JOINT_TYPE_MOUSE = 7; + Manager.JOINT_LINEAR_SLOP = 0.0008; + Manager.JOINT_ANGULAR_SLOP = 2 * Phaser.GameMath.DEG_TO_RAD; + Manager.JOINT_MAX_LINEAR_CORRECTION = 0.5; + Manager.JOINT_MAX_ANGULAR_CORRECTION = 8 * Phaser.GameMath.DEG_TO_RAD; + Manager.JOINT_LIMIT_STATE_INACTIVE = 0; + Manager.JOINT_LIMIT_STATE_AT_LOWER = 1; + Manager.JOINT_LIMIT_STATE_AT_UPPER = 2; + Manager.JOINT_LIMIT_STATE_EQUAL_LIMITS = 3; + Manager.CONTACT_SOLVER_COLLISION_SLOP = 0.0008; + Manager.CONTACT_SOLVER_BAUMGARTE = 0.28; + Manager.CONTACT_SOLVER_MAX_LINEAR_CORRECTION = 1; + Manager.bodyCounter = 0; + Manager.jointCounter = 0; + Manager.shapeCounter = 0; + Manager.prototype.update = function () { + var time = Date.now(); + var frameTime = (time - this.lastTime) / 1000; + this.lastTime = time; + // if rAf - why? + frameTime = Math.floor(frameTime * 60 + 0.5) / 60; + //if (!mouseDown) + //{ + // var p = canvasToWorld(mousePosition); + // var body = space.findBodyByPoint(p); + // //domCanvas.style.cursor = body ? "pointer" : "default"; + //} + if(!this.paused || this.step) { + var h = 1 / this.frameRateHz; + this.timeDelta += frameTime; + if(this.step) { + this.step = false; + this.timeDelta = h; + } + for(var maxSteps = 4; maxSteps > 0 && this.timeDelta >= h; maxSteps--) { + this.space.step(h, this.velocityIterations, this.positionIterations, this.warmStarting, this.allowSleep); + this.timeDelta -= h; + } + if(this.timeDelta > h) { + this.timeDelta = 0; + } + //if (sceneIndex < demoArr.length) + //{ + // demo = demoArr[sceneIndex]; + // demo.runFrame(); + //} + } + //frameCount++; + }; + Manager.prototype.pixelsToMeters = function (value) { + return value * 0.02; + }; + Manager.prototype.metersToPixels = function (value) { + return value * 50; + }; + Manager.pixelsToMeters = function pixelsToMeters(value) { + return value * 0.02; + }; + Manager.metersToPixels = function metersToPixels(value) { + return value * 50; + }; + Manager.p2m = function p2m(value) { + return value * 0.02; + }; + Manager.m2p = function m2p(value) { + return value * 50; + }; + Manager.areaForCircle = function areaForCircle(radius_outer, radius_inner) { + return Math.PI * (radius_outer * radius_outer - radius_inner * radius_inner); + }; + Manager.inertiaForCircle = function inertiaForCircle(mass, center, radius_outer, radius_inner) { + return mass * ((radius_outer * radius_outer + radius_inner * radius_inner) * 0.5 + center.lengthSq()); + }; + Manager.areaForSegment = function areaForSegment(a, b, radius) { + return radius * (Math.PI * radius + 2 * Phaser.Vec2Utils.distance(a, b)); + }; + Manager.centroidForSegment = function centroidForSegment(a, b) { + return Phaser.Vec2Utils.scale(Phaser.Vec2Utils.add(a, b), 0.5); + }; + Manager.inertiaForSegment = function inertiaForSegment(mass, a, b) { + var distsq = Phaser.Vec2Utils.distanceSq(b, a); + var offset = Phaser.Vec2Utils.scale(Phaser.Vec2Utils.add(a, b), 0.5); + return mass * (distsq / 12 + offset.lengthSq()); + }; + Manager.areaForPoly = function areaForPoly(verts) { + var area = 0; + for(var i = 0; i < verts.length; i++) { + area += Phaser.Vec2Utils.cross(verts[i], verts[(i + 1) % verts.length]); + } + return area / 2; + }; + Manager.centroidForPoly = function centroidForPoly(verts) { + var area = 0; + var vsum = new Phaser.Vec2(); + for(var i = 0; i < verts.length; i++) { + var v1 = verts[i]; + var v2 = verts[(i + 1) % verts.length]; + var cross = Phaser.Vec2Utils.cross(v1, v2); + area += cross; + // SO many vecs created here - unroll these bad boys + vsum.add(Phaser.Vec2Utils.scale(Phaser.Vec2Utils.add(v1, v2), cross)); + } + return Phaser.Vec2Utils.scale(vsum, 1 / (3 * area)); + }; + Manager.inertiaForPoly = function inertiaForPoly(mass, verts, offset) { + var sum1 = 0; + var sum2 = 0; + for(var i = 0; i < verts.length; i++) { + var v1 = Phaser.Vec2Utils.add(verts[i], offset); + var v2 = Phaser.Vec2Utils.add(verts[(i + 1) % verts.length], offset); + var a = Phaser.Vec2Utils.cross(v2, v1); + var b = Phaser.Vec2Utils.dot(v1, v1) + Phaser.Vec2Utils.dot(v1, v2) + Phaser.Vec2Utils.dot(v2, v2); + sum1 += a * b; + sum2 += a; + } + return (mass * sum1) / (6 * sum2); + }; + Manager.inertiaForBox = function inertiaForBox(mass, w, h) { + return mass * (w * w + h * h) / 12; + }; + Manager.createConvexHull = // Create the convex hull using the Gift wrapping algorithm (http://en.wikipedia.org/wiki/Gift_wrapping_algorithm) + function createConvexHull(points) { + // Find the right most point on the hull + var i0 = 0; + var x0 = points[0].x; + for(var i = 1; i < points.length; i++) { + var x = points[i].x; + if(x > x0 || (x == x0 && points[i].y < points[i0].y)) { + i0 = i; + x0 = x; + } + } + var n = points.length; + var hull = []; + var m = 0; + var ih = i0; + while(1) { + hull[m] = ih; + var ie = 0; + for(var j = 1; j < n; j++) { + if(ie == ih) { + ie = j; + continue; + } + var r = Phaser.Vec2Utils.subtract(points[ie], points[hull[m]]); + var v = Phaser.Vec2Utils.subtract(points[j], points[hull[m]]); + var c = Phaser.Vec2Utils.cross(r, v); + if(c < 0) { + ie = j; + } + // Collinearity check + if(c == 0 && v.lengthSq() > r.lengthSq()) { + ie = j; + } + } + m++; + ih = ie; + if(ie == i0) { + break; + } + } + // Copy vertices + var newPoints = []; + for(var i = 0; i < m; ++i) { + newPoints.push(points[hull[i]]); + } + return newPoints; + }; + return Manager; + })(); + Advanced.Manager = Manager; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + /// + /// + /// + /** + * Phaser - 2D AABB + * + * A 2D AABB object + */ + (function (Advanced) { + var Bounds = (function () { + /** + * Creates a new 2D AABB object. + * @class Bounds + * @constructor + * @return {Bounds} This object + **/ + function Bounds(mins, maxs) { + if (typeof mins === "undefined") { mins = null; } + if (typeof maxs === "undefined") { maxs = null; } + if(mins) { + this.mins = Phaser.Vec2Utils.clone(mins); + } else { + this.mins = new Phaser.Vec2(999999, 999999); + } + if(maxs) { + this.maxs = Phaser.Vec2Utils.clone(maxs); + } else { + this.maxs = new Phaser.Vec2(999999, 999999); + } + } + Bounds.prototype.toString = function () { + return [ + "mins:", + this.mins.toString(), + "maxs:", + this.maxs.toString() + ].join(" "); + }; + Bounds.prototype.setTo = function (mins, maxs) { + this.mins.setTo(mins.x, mins.y); + this.maxs.setTo(maxs.x, maxs.y); + }; + Bounds.prototype.copy = function (b) { + this.mins.copyFrom(b.mins); + this.maxs.copyFrom(b.maxs); + return this; + }; + Bounds.prototype.clear = function () { + this.mins.setTo(999999, 999999); + this.maxs.setTo(-999999, -999999); + return this; + }; + Bounds.prototype.isEmpty = function () { + return (this.mins.x > this.maxs.x || this.mins.y > this.maxs.y); + }; + Bounds.prototype.getPerimeter = /* + public getCenter() { + return vec2.scale(vec2.add(this.mins, this.maxs), 0.5); + } + + public getExtent() { + return vec2.scale(vec2.sub(this.maxs, this.mins), 0.5); + } + */ + function () { + return (this.maxs.x - this.mins.x + this.maxs.y - this.mins.y) * 2; + }; + Bounds.prototype.addPoint = function (p) { + if(this.mins.x > p.x) { + this.mins.x = p.x; + } + if(this.maxs.x < p.x) { + this.maxs.x = p.x; + } + if(this.mins.y > p.y) { + this.mins.y = p.y; + } + if(this.maxs.y < p.y) { + this.maxs.y = p.y; + } + return this; + }; + Bounds.prototype.addBounds = function (b) { + if(this.mins.x > b.mins.x) { + this.mins.x = b.mins.x; + } + if(this.maxs.x < b.maxs.x) { + this.maxs.x = b.maxs.x; + } + if(this.mins.y > b.mins.y) { + this.mins.y = b.mins.y; + } + if(this.maxs.y < b.maxs.y) { + this.maxs.y = b.maxs.y; + } + return this; + }; + Bounds.prototype.addBounds2 = function (mins, maxs) { + if(this.mins.x > mins.x) { + this.mins.x = mins.x; + } + if(this.maxs.x < maxs.x) { + this.maxs.x = maxs.x; + } + if(this.mins.y > mins.y) { + this.mins.y = mins.y; + } + if(this.maxs.y < maxs.y) { + this.maxs.y = maxs.y; + } + return this; + }; + Bounds.prototype.addExtents = function (center, extent_x, extent_y) { + if(this.mins.x > center.x - extent_x) { + this.mins.x = center.x - extent_x; + } + if(this.maxs.x < center.x + extent_x) { + this.maxs.x = center.x + extent_x; + } + if(this.mins.y > center.y - extent_y) { + this.mins.y = center.y - extent_y; + } + if(this.maxs.y < center.y + extent_y) { + this.maxs.y = center.y + extent_y; + } + return this; + }; + Bounds.prototype.expand = function (ax, ay) { + this.mins.x -= ax; + this.mins.y -= ay; + this.maxs.x += ax; + this.maxs.y += ay; + return this; + }; + Bounds.prototype.containPoint = function (p) { + if(p.x < this.mins.x || p.x > this.maxs.x || p.y < this.mins.y || p.y > this.maxs.y) { + return false; + } + return true; + }; + Bounds.prototype.intersectsBounds = function (b) { + if(this.mins.x > b.maxs.x || this.maxs.x < b.mins.x || this.mins.y > b.maxs.y || this.maxs.y < b.mins.y) { + return false; + } + return true; + }; + Bounds.expand = function expand(b, ax, ay) { + var b = new Bounds(b.mins, b.maxs); + b.expand(ax, ay); + return b; + }; + return Bounds; + })(); + Advanced.Bounds = Bounds; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + /// + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - Shape + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + (function (Advanced) { + var Shape = (function () { + function Shape(type) { + this.id = Phaser.Physics.Advanced.Manager.shapeCounter++; + this.type = type; + this.elasticity = 0.0; + this.friction = 1.0; + this.density = 1; + this.bounds = new Advanced.Bounds(); + } + Shape.prototype.findEdgeByPoint = // Over-ridden by ShapePoly + function (p, minDist) { + return -1; + }; + return Shape; + })(); + Advanced.Shape = Shape; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - Contact + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + (function (Advanced) { + var Contact = (function () { + function Contact(p, n, d, hash) { + this.hash = hash; + this.point = p; + this.normal = n; + this.depth = d; + this.lambdaNormal = 0; + this.lambdaTangential = 0; + this.r1 = new Phaser.Vec2(); + this.r2 = new Phaser.Vec2(); + this.r1_local = new Phaser.Vec2(); + this.r2_local = new Phaser.Vec2(); + } + return Contact; + })(); + Advanced.Contact = Contact; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + /// + /// + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - ContactSolver + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + //------------------------------------------------------------------------------------------------- + // Contact Constraint + // + // Non-penetration constraint: + // C = dot(p2 - p1, n) + // Cdot = dot(v2 - v1, n) + // J = [ -n, -cross(r1, n), n, cross(r2, n) ] + // + // impulse = JT * lambda = [ -n * lambda, -cross(r1, n) * lambda, n * lambda, cross(r1, n) * lambda ] + // + // Friction constraint: + // C = dot(p2 - p1, t) + // Cdot = dot(v2 - v1, t) + // J = [ -t, -cross(r1, t), t, cross(r2, t) ] + // + // impulse = JT * lambda = [ -t * lambda, -cross(r1, t) * lambda, t * lambda, cross(r1, t) * lambda ] + // + // NOTE: lambda is an impulse in constraint space. + //------------------------------------------------------------------------------------------------- + (function (Advanced) { + var ContactSolver = (function () { + function ContactSolver(shape1, shape2) { + //console.log('ContactSolver super'); + this.shape1 = shape1; + this.shape2 = shape2; + this.contacts = []; + this.elasticity = 1; + this.friction = 1; + } + ContactSolver.prototype.update = function (newContactArr) { + for(var i = 0; i < newContactArr.length; i++) { + var newContact = newContactArr[i]; + var k = -1; + for(var j = 0; j < this.contacts.length; j++) { + if(newContact.hash == this.contacts[j].hash) { + k = j; + break; + } + } + if(k > -1) { + newContact.lambdaNormal = this.contacts[k].lambdaNormal; + newContact.lambdaTangential = this.contacts[k].lambdaTangential; + } + } + this.contacts = newContactArr; + }; + ContactSolver.prototype.initSolver = function (dt_inv) { + var body1 = this.shape1.body; + var body2 = this.shape2.body; + var sum_m_inv = body1.massInverted + body2.massInverted; + for(var i = 0; i < this.contacts.length; i++) { + var con = this.contacts[i]; + //console.log('initSolver con'); + //console.log(con); + // Transformed r1, r2 + Phaser.Vec2Utils.subtract(con.point, body1.position, con.r1); + Phaser.Vec2Utils.subtract(con.point, body2.position, con.r2); + //con.r1 = vec2.sub(con.point, body1.p); + //con.r2 = vec2.sub(con.point, body2.p); + // Local r1, r2 + Phaser.TransformUtils.unrotate(body1.transform, con.r1, con.r1_local); + Phaser.TransformUtils.unrotate(body2.transform, con.r2, con.r2_local); + //con.r1_local = body1.transform.unrotate(con.r1); + //con.r2_local = body2.transform.unrotate(con.r2); + var n = con.normal; + var t = Phaser.Vec2Utils.perp(con.normal); + // invEMn = J * invM * JT + // J = [ -n, -cross(r1, n), n, cross(r2, n) ] + var sn1 = Phaser.Vec2Utils.cross(con.r1, n); + var sn2 = Phaser.Vec2Utils.cross(con.r2, n); + var emn_inv = sum_m_inv + body1.inertiaInverted * sn1 * sn1 + body2.inertiaInverted * sn2 * sn2; + con.emn = emn_inv == 0 ? 0 : 1 / emn_inv; + // invEMt = J * invM * JT + // J = [ -t, -cross(r1, t), t, cross(r2, t) ] + var st1 = Phaser.Vec2Utils.cross(con.r1, t); + var st2 = Phaser.Vec2Utils.cross(con.r2, t); + var emt_inv = sum_m_inv + body1.inertiaInverted * st1 * st1 + body2.inertiaInverted * st2 * st2; + con.emt = emt_inv == 0 ? 0 : 1 / emt_inv; + // Linear velocities at contact point + // in 2D: cross(w, r) = perp(r) * w + var v1 = new Phaser.Vec2(); + var v2 = new Phaser.Vec2(); + Phaser.Vec2Utils.multiplyAdd(body1.velocity, Phaser.Vec2Utils.perp(con.r1), body1.angularVelocity, v1); + Phaser.Vec2Utils.multiplyAdd(body2.velocity, Phaser.Vec2Utils.perp(con.r2), body2.angularVelocity, v2); + //var v1 = vec2.mad(body1.v, vec2.perp(con.r1), body1.w); + //var v2 = vec2.mad(body2.v, vec2.perp(con.r2), body2.w); + // relative velocity at contact point + var rv = new Phaser.Vec2(); + Phaser.Vec2Utils.subtract(v2, v1, rv); + //var rv = vec2.sub(v2, v1); + // bounce velocity dot n + con.bounce = Phaser.Vec2Utils.dot(rv, con.normal) * this.elasticity; + } + }; + ContactSolver.prototype.warmStart = function () { + var body1 = this.shape1.body; + var body2 = this.shape2.body; + for(var i = 0; i < this.contacts.length; i++) { + var con = this.contacts[i]; + var n = con.normal; + var lambda_n = con.lambdaNormal; + var lambda_t = con.lambdaTangential; + // Apply accumulated impulses + //var impulse = vec2.rotate_vec(new vec2(lambda_n, lambda_t), n); + //var impulse = new vec2(lambda_n * n.x - lambda_t * n.y, lambda_t * n.x + lambda_n * n.y); + var impulse = new Phaser.Vec2(lambda_n * n.x - lambda_t * n.y, lambda_t * n.x + lambda_n * n.y); + body1.velocity.multiplyAddByScalar(impulse, -body1.massInverted); + //body1.v.mad(impulse, -body1.m_inv); + body1.angularVelocity -= Phaser.Vec2Utils.cross(con.r1, impulse) * body1.inertiaInverted; + //body1.w -= vec2.cross(con.r1, impulse) * body1.i_inv; + body2.velocity.multiplyAddByScalar(impulse, -body2.massInverted); + //body2.v.mad(impulse, body2.m_inv); + body2.angularVelocity -= Phaser.Vec2Utils.cross(con.r2, impulse) * body2.inertiaInverted; + //body2.w += vec2.cross(con.r2, impulse) * body2.i_inv; + } + }; + ContactSolver.prototype.solveVelocityConstraints = function () { + var body1 = this.shape1.body; + var body2 = this.shape2.body; + var m1_inv = body1.massInverted; + var i1_inv = body1.inertiaInverted; + var m2_inv = body2.massInverted; + var i2_inv = body2.inertiaInverted; + for(var i = 0; i < this.contacts.length; i++) { + var con = this.contacts[i]; + var n = con.normal; + var t = Phaser.Vec2Utils.perp(n); + var r1 = con.r1; + var r2 = con.r2; + // Linear velocities at contact point + // in 2D: cross(w, r) = perp(r) * w + var v1 = new Phaser.Vec2(); + var v2 = new Phaser.Vec2(); + Phaser.Vec2Utils.multiplyAdd(body1.velocity, Phaser.Vec2Utils.perp(r1), body1.angularVelocity, v1); + //var v1 = vec2.mad(body1.v, vec2.perp(r1), body1.w); + Phaser.Vec2Utils.multiplyAdd(body2.velocity, Phaser.Vec2Utils.perp(r2), body2.angularVelocity, v2); + //var v2 = vec2.mad(body2.v, vec2.perp(r2), body2.w); + // Relative velocity at contact point + var rv = new Phaser.Vec2(); + Phaser.Vec2Utils.subtract(v2, v1, rv); + //var rv = vec2.sub(v2, v1); + // Compute normal constraint impulse + adding bounce as a velocity bias + // lambda_n = -EMn * J * V + var lambda_n = -con.emn * (Phaser.Vec2Utils.dot(n, rv) + con.bounce); + // Accumulate and clamp + var lambda_n_old = con.lambdaNormal; + con.lambdaNormal = Math.max(lambda_n_old + lambda_n, 0); + lambda_n = con.lambdaNormal - lambda_n_old; + // Compute frictional constraint impulse + // lambda_t = -EMt * J * V + var lambda_t = -con.emt * Phaser.Vec2Utils.dot(t, rv); + // Max friction constraint impulse (Coulomb's Law) + var lambda_t_max = con.lambdaNormal * this.friction; + // Accumulate and clamp + var lambda_t_old = con.lambdaTangential; + con.lambdaTangential = this.clamp(lambda_t_old + lambda_t, -lambda_t_max, lambda_t_max); + lambda_t = con.lambdaTangential - lambda_t_old; + // Apply the final impulses + //var impulse = vec2.rotate_vec(new vec2(lambda_n, lambda_t), n); + var impulse = new Phaser.Vec2(lambda_n * n.x - lambda_t * n.y, lambda_t * n.x + lambda_n * n.y); + body1.velocity.multiplyAddByScalar(impulse, -m1_inv); + //body1.v.mad(impulse, -m1_inv); + body1.angularVelocity -= Phaser.Vec2Utils.cross(r1, impulse) * i1_inv; + //body1.w -= vec2.cross(r1, impulse) * i1_inv; + body2.velocity.multiplyAddByScalar(impulse, m2_inv); + //body2.v.mad(impulse, m2_inv); + body1.angularVelocity += Phaser.Vec2Utils.cross(r2, impulse) * i2_inv; + //body2.w += vec2.cross(r2, impulse) * i2_inv; + } + }; + ContactSolver.prototype.solvePositionConstraints = function () { + var body1 = this.shape1.body; + var body2 = this.shape2.body; + var m1_inv = body1.massInverted; + var i1_inv = body1.inertiaInverted; + var m2_inv = body2.massInverted; + var i2_inv = body2.inertiaInverted; + var sum_m_inv = m1_inv + m2_inv; + var max_penetration = 0; + for(var i = 0; i < this.contacts.length; i++) { + var con = this.contacts[i]; + var n = con.normal; + var r1 = new Phaser.Vec2(); + var r2 = new Phaser.Vec2(); + // Transformed r1, r2 + Phaser.Vec2Utils.rotate(con.r1_local, body1.angle, r1); + //var r1 = vec2.rotate(con.r1_local, body1.a); + Phaser.Vec2Utils.rotate(con.r2_local, body2.angle, r2); + //var r2 = vec2.rotate(con.r2_local, body2.a); + // Contact points (corrected) + var p1 = new Phaser.Vec2(); + var p2 = new Phaser.Vec2(); + Phaser.Vec2Utils.add(body1.position, r1, p1); + //var p1 = vec2.add(body1.p, r1); + Phaser.Vec2Utils.add(body2.position, r2, p2); + //var p2 = vec2.add(body2.p, r2); + // Corrected delta vector + var dp = new Phaser.Vec2(); + Phaser.Vec2Utils.subtract(p2, p1); + //var dp = vec2.sub(p2, p1); + // Position constraint + var c = Phaser.Vec2Utils.dot(dp, n) + con.depth; + var correction = this.clamp(Advanced.Manager.CONTACT_SOLVER_BAUMGARTE * (c + Advanced.Manager.CONTACT_SOLVER_COLLISION_SLOP), -Advanced.Manager.CONTACT_SOLVER_MAX_LINEAR_CORRECTION, 0); + if(correction == 0) { + continue; + } + // We don't need max_penetration less than or equal slop + max_penetration = Math.max(max_penetration, -c); + // Compute lambda for position constraint + // Solve (J * invM * JT) * lambda = -C / dt + var sn1 = Phaser.Vec2Utils.cross(r1, n); + var sn2 = Phaser.Vec2Utils.cross(r2, n); + var em_inv = sum_m_inv + body1.inertiaInverted * sn1 * sn1 + body2.inertiaInverted * sn2 * sn2; + var lambda_dt = em_inv == 0 ? 0 : -correction / em_inv; + // Apply correction impulses + var impulse_dt = new Phaser.Vec2(); + Phaser.Vec2Utils.scale(n, lambda_dt, impulse_dt); + //var impulse_dt = vec2.scale(n, lambda_dt); + body1.position.multiplyAddByScalar(impulse_dt, -m1_inv); + //body1.p.mad(impulse_dt, -m1_inv); + body1.angle -= sn1 * lambda_dt * i1_inv; + body2.position.multiplyAddByScalar(impulse_dt, m2_inv); + //body2.p.mad(impulse_dt, m2_inv); + body2.angle += sn2 * lambda_dt * i2_inv; + } + return max_penetration <= Advanced.Manager.CONTACT_SOLVER_COLLISION_SLOP * 3; + }; + ContactSolver.prototype.clamp = function (v, min, max) { + return v < min ? min : (v > max ? max : v); + }; + return ContactSolver; + })(); + Advanced.ContactSolver = ContactSolver; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + (function (Advanced) { + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - Shape - Circle + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + (function (Shapes) { + var Circle = (function (_super) { + __extends(Circle, _super); + function Circle(radius, x, y) { + if (typeof x === "undefined") { x = 0; } + if (typeof y === "undefined") { y = 0; } + _super.call(this, Advanced.Manager.SHAPE_TYPE_CIRCLE); + this.center = new Phaser.Vec2(x, y); + this.radius = radius; + this.tc = new Phaser.Vec2(); + this.finishVerts(); + } + Circle.prototype.finishVerts = function () { + this.radius = Math.abs(this.radius); + }; + Circle.prototype.duplicate = function () { + return new Circle(this.center.x, this.center.y, this.radius); + }; + Circle.prototype.recenter = function (c) { + this.center.subtract(c); + }; + Circle.prototype.transform = function (xf) { + Phaser.TransformUtils.transform(xf, this.center, this.center); + //this.center = xf.transform(this.center); + }; + Circle.prototype.untransform = function (xf) { + Phaser.TransformUtils.untransform(xf, this.center, this.center); + //this.center = xf.untransform(this.center); + }; + Circle.prototype.area = function () { + return Advanced.Manager.areaForCircle(this.radius, 0); + }; + Circle.prototype.centroid = function () { + return Phaser.Vec2Utils.clone(this.center); + }; + Circle.prototype.inertia = function (mass) { + return Advanced.Manager.inertiaForCircle(mass, this.center, this.radius, 0); + }; + Circle.prototype.cacheData = function (xf) { + Phaser.TransformUtils.transform(xf, this.center, this.tc); + //this.tc = xf.transform(this.center); + this.bounds.mins.setTo(this.tc.x - this.radius, this.tc.y - this.radius); + this.bounds.maxs.setTo(this.tc.x + this.radius, this.tc.y + this.radius); + }; + Circle.prototype.pointQuery = function (p) { + //return vec2.distsq(this.tc, p) < (this.r * this.r); + return Phaser.Vec2Utils.distanceSq(this.tc, p) < (this.radius * this.radius); + }; + Circle.prototype.findVertexByPoint = function (p, minDist) { + var dsq = minDist * minDist; + if(Phaser.Vec2Utils.distanceSq(this.tc, p) < dsq) { + return 0; + } + return -1; + }; + Circle.prototype.distanceOnPlane = function (n, d) { + Phaser.Vec2Utils.dot(n, this.tc) - this.radius - d; + }; + return Circle; + })(Phaser.Physics.Advanced.Shape); + Shapes.Circle = Circle; + })(Advanced.Shapes || (Advanced.Shapes = {})); + var Shapes = Advanced.Shapes; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + (function (Advanced) { + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - Shapes - Convex Polygon + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + (function (Shapes) { + var Poly = (function (_super) { + __extends(Poly, _super); + function Poly(verts) { + _super.call(this, Advanced.Manager.SHAPE_TYPE_POLY); + this.verts = []; + this.planes = []; + this.tverts = []; + this.tplanes = []; + if(verts) { + for(var i = 0; i < verts.length; i++) { + this.verts[i] = Phaser.Vec2Utils.clone(verts[i]); + this.tverts[i] = this.verts[i]; + this.tplanes[i] = { + }; + this.tplanes[i].n = new Phaser.Vec2(); + this.tplanes[i].d = 0; + } + } + this.finishVerts(); + } + Poly.prototype.finishVerts = function () { + if(this.verts.length < 2) { + this.convexity = false; + this.planes = []; + return; + } + this.convexity = true; + this.tverts = []; + this.tplanes = []; + // Must be counter-clockwise verts + for(var i = 0; i < this.verts.length; i++) { + var a = this.verts[i]; + var b = this.verts[(i + 1) % this.verts.length]; + var n = Phaser.Vec2Utils.normalize(Phaser.Vec2Utils.perp(Phaser.Vec2Utils.subtract(a, b))); + this.planes[i] = { + }; + this.planes[i].n = n; + this.planes[i].d = Phaser.Vec2Utils.dot(n, a); + this.tverts[i] = this.verts[i]; + this.tplanes[i] = { + }; + this.tplanes[i].n = new Phaser.Vec2(); + this.tplanes[i].d = 0; + } + for(var i = 0; i < this.verts.length; i++) { + var b = this.verts[(i + 2) % this.verts.length]; + var n = this.planes[i].n; + var d = this.planes[i].d; + if(Phaser.Vec2Utils.dot(n, b) - d > 0) { + this.convexity = false; + } + } + }; + Poly.prototype.duplicate = function () { + return new Phaser.Physics.Advanced.Shapes.Poly(this.verts); + }; + Poly.prototype.recenter = function (c) { + for(var i = 0; i < this.verts.length; i++) { + this.verts[i].subtract(c); + } + }; + Poly.prototype.transform = function (xf) { + for(var i = 0; i < this.verts.length; i++) { + this.verts[i] = xf.transform(this.verts[i]); + } + }; + Poly.prototype.untransform = function (xf) { + for(var i = 0; i < this.verts.length; i++) { + this.verts[i] = xf.untransform(this.verts[i]); + } + }; + Poly.prototype.area = function () { + return Advanced.Manager.areaForPoly(this.verts); + }; + Poly.prototype.centroid = function () { + return Advanced.Manager.centroidForPoly(this.verts); + }; + Poly.prototype.inertia = function (mass) { + return Advanced.Manager.inertiaForPoly(mass, this.verts, new Phaser.Vec2()); + }; + Poly.prototype.cacheData = function (xf) { + this.bounds.clear(); + var numVerts = this.verts.length; + //console.log('shapePoly cacheData', numVerts); + if(numVerts == 0) { + return; + } + for(var i = 0; i < numVerts; i++) { + Phaser.TransformUtils.transform(xf, this.tverts[i], this.tverts[i]); + //this.tverts[i] = xf.transform(this.verts[i]); + } + if(numVerts < 2) { + this.bounds.addPoint(this.tverts[0]); + return; + } + for(var i = 0; i < numVerts; i++) { + var a = this.tverts[i]; + var b = this.tverts[(i + 1) % numVerts]; + var n = Phaser.Vec2Utils.normalize(Phaser.Vec2Utils.perp(Phaser.Vec2Utils.subtract(a, b))); + this.tplanes[i].n = n; + this.tplanes[i].d = Phaser.Vec2Utils.dot(n, a); + this.bounds.addPoint(a); + } + }; + Poly.prototype.pointQuery = function (p) { + if(!this.bounds.containPoint(p)) { + return false; + } + return this.containPoint(p); + }; + Poly.prototype.findVertexByPoint = function (p, minDist) { + var dsq = minDist * minDist; + for(var i = 0; i < this.tverts.length; i++) { + if(Phaser.Vec2Utils.distanceSq(this.tverts[i], p) < dsq) { + return i; + } + } + return -1; + }; + Poly.prototype.findEdgeByPoint = function (p, minDist) { + var dsq = minDist * minDist; + var numVerts = this.tverts.length; + for(var i = 0; i < this.tverts.length; i++) { + var v1 = this.tverts[i]; + var v2 = this.tverts[(i + 1) % numVerts]; + var n = this.tplanes[i].n; + var dtv1 = Phaser.Vec2Utils.cross(v1, n); + var dtv2 = Phaser.Vec2Utils.cross(v2, n); + var dt = Phaser.Vec2Utils.cross(p, n); + if(dt > dtv1) { + if(Phaser.Vec2Utils.distanceSq(v1, p) < dsq) { + return i; + } + } else if(dt < dtv2) { + if(Phaser.Vec2Utils.distanceSq(v2, p) < dsq) { + return i; + } + } else { + var dist = Phaser.Vec2Utils.dot(n, p) - Phaser.Vec2Utils.dot(n, v1); + if(dist * dist < dsq) { + return i; + } + } + } + return -1; + }; + Poly.prototype.distanceOnPlane = function (n, d) { + var min = 999999; + for(var i = 0; i < this.verts.length; i++) { + min = Math.min(min, Phaser.Vec2Utils.dot(n, this.tverts[i])); + } + return min - d; + }; + Poly.prototype.containPoint = function (p) { + for(var i = 0; i < this.verts.length; i++) { + var plane = this.tplanes[i]; + if(Phaser.Vec2Utils.dot(plane.n, p) - plane.d > 0) { + return false; + } + } + return true; + }; + Poly.prototype.containPointPartial = function (p, n) { + for(var i = 0; i < this.verts.length; i++) { + var plane = this.tplanes[i]; + if(Phaser.Vec2Utils.dot(plane.n, n) < 0.0001) { + continue; + } + if(Phaser.Vec2Utils.dot(plane.n, p) - plane.d > 0) { + return false; + } + } + return true; + }; + return Poly; + })(Phaser.Physics.Advanced.Shape); + Shapes.Poly = Poly; + })(Advanced.Shapes || (Advanced.Shapes = {})); + var Shapes = Advanced.Shapes; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + (function (Advanced) { + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - Shapes - Segment + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + (function (Shapes) { + var Segment = (function (_super) { + __extends(Segment, _super); + function Segment(a, b, radius) { + _super.call(this, Advanced.Manager.SHAPE_TYPE_SEGMENT); + this.a = a.duplicate(); + this.b = b.duplicate(); + this.radius = radius; + this.normal = Phaser.Vec2Utils.perp(Phaser.Vec2Utils.subtract(b, a)); + this.normal.normalize(); + this.ta = new Phaser.Vec2(); + this.tb = new Phaser.Vec2(); + this.tn = new Phaser.Vec2(); + this.finishVerts(); + } + Segment.prototype.finishVerts = function () { + this.normal = Phaser.Vec2Utils.perp(Phaser.Vec2Utils.subtract(this.b, this.a)); + this.normal.normalize(); + this.radius = Math.abs(this.radius); + }; + Segment.prototype.duplicate = function () { + return new Phaser.Physics.Advanced.Shapes.Segment(this.a, this.b, this.radius); + }; + Segment.prototype.recenter = function (c) { + this.a.subtract(c); + this.b.subtract(c); + }; + Segment.prototype.transform = function (xf) { + Phaser.TransformUtils.transform(xf, this.a, this.a); + Phaser.TransformUtils.transform(xf, this.b, this.b); + //this.a = xf.transform(this.a); + //this.b = xf.transform(this.b); + }; + Segment.prototype.untransform = function (xf) { + Phaser.TransformUtils.untransform(xf, this.a, this.a); + Phaser.TransformUtils.untransform(xf, this.b, this.b); + //this.a = xf.untransform(this.a); + //this.b = xf.untransform(this.b); + }; + Segment.prototype.area = function () { + return Advanced.Manager.areaForSegment(this.a, this.b, this.radius); + }; + Segment.prototype.centroid = function () { + return Advanced.Manager.centroidForSegment(this.a, this.b); + }; + Segment.prototype.inertia = function (mass) { + return Advanced.Manager.inertiaForSegment(mass, this.a, this.b); + }; + Segment.prototype.cacheData = function (xf) { + Phaser.TransformUtils.transform(xf, this.a, this.ta); + Phaser.TransformUtils.transform(xf, this.b, this.tb); + //this.ta = xf.transform(this.a); + //this.tb = xf.transform(this.b); + this.tn = Phaser.Vec2Utils.perp(Phaser.Vec2Utils.subtract(this.tb, this.ta)).normalize(); + var l; + var r; + var t; + var b; + if(this.ta.x < this.tb.x) { + l = this.ta.x; + r = this.tb.x; + } else { + l = this.tb.x; + r = this.ta.x; + } + if(this.ta.y < this.tb.y) { + b = this.ta.y; + t = this.tb.y; + } else { + b = this.tb.y; + t = this.ta.y; + } + this.bounds.mins.setTo(l - this.radius, b - this.radius); + this.bounds.maxs.setTo(r + this.radius, t + this.radius); + }; + Segment.prototype.pointQuery = function (p) { + if(!this.bounds.containPoint(p)) { + return false; + } + var dn = Phaser.Vec2Utils.dot(this.tn, p) - Phaser.Vec2Utils.dot(this.ta, this.tn); + var dist = Math.abs(dn); + if(dist > this.radius) { + return false; + } + var dt = Phaser.Vec2Utils.cross(p, this.tn); + var dta = Phaser.Vec2Utils.cross(this.ta, this.tn); + var dtb = Phaser.Vec2Utils.cross(this.tb, this.tn); + if(dt <= dta) { + if(dt < dta - this.radius) { + return false; + } + return Phaser.Vec2Utils.distanceSq(this.ta, p) < (this.radius * this.radius); + } else if(dt > dtb) { + if(dt > dtb + this.radius) { + return false; + } + return Phaser.Vec2Utils.distanceSq(this.tb, p) < (this.radius * this.radius); + } + return true; + }; + Segment.prototype.findVertexByPoint = function (p, minDist) { + var dsq = minDist * minDist; + if(Phaser.Vec2Utils.distanceSq(this.ta, p) < dsq) { + return 0; + } + if(Phaser.Vec2Utils.distanceSq(this.tb, p) < dsq) { + return 1; + } + return -1; + }; + Segment.prototype.distanceOnPlane = function (n, d) { + var a = Phaser.Vec2Utils.dot(n, this.ta) - this.radius; + var b = Phaser.Vec2Utils.dot(n, this.tb) - this.radius; + return Math.min(a, b) - d; + }; + return Segment; + })(Phaser.Physics.Advanced.Shape); + Shapes.Segment = Segment; + })(Advanced.Shapes || (Advanced.Shapes = {})); + var Shapes = Advanced.Shapes; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - Collision Handlers + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + (function (Advanced) { + var Collision = (function () { + function Collision() { + } + Collision.prototype.collide = function (a, b, contacts) { + // Circle (a is the circle) + if(a.type == Advanced.Manager.SHAPE_TYPE_CIRCLE) { + if(b.type == Advanced.Manager.SHAPE_TYPE_CIRCLE) { + return this.circle2Circle(a, b, contacts); + } else if(b.type == Advanced.Manager.SHAPE_TYPE_SEGMENT) { + return this.circle2Segment(a, b, contacts); + } else if(b.type == Advanced.Manager.SHAPE_TYPE_POLY) { + return this.circle2Poly(a, b, contacts); + } + } + // Segment (a is the segment) + if(a.type == Advanced.Manager.SHAPE_TYPE_SEGMENT) { + if(b.type == Advanced.Manager.SHAPE_TYPE_CIRCLE) { + return this.circle2Segment(b, a, contacts); + } else if(b.type == Advanced.Manager.SHAPE_TYPE_SEGMENT) { + return this.segment2Segment(a, b, contacts); + } else if(b.type == Advanced.Manager.SHAPE_TYPE_POLY) { + return this.segment2Poly(a, b, contacts); + } + } + // Poly (a is the poly) + if(a.type == Advanced.Manager.SHAPE_TYPE_POLY) { + if(b.type == Advanced.Manager.SHAPE_TYPE_CIRCLE) { + return this.circle2Poly(b, a, contacts); + } else if(b.type == Advanced.Manager.SHAPE_TYPE_SEGMENT) { + return this.segment2Poly(b, a, contacts); + } else if(b.type == Advanced.Manager.SHAPE_TYPE_POLY) { + return this.poly2Poly(a, b, contacts); + } + } + }; + Collision.prototype._circle2Circle = function (c1, r1, c2, r2, contactArr) { + var rmax = r1 + r2; + var t = new Phaser.Vec2(); + //var t = vec2.sub(c2, c1); + Phaser.Vec2Utils.subtract(c2, c1, t); + var distsq = t.lengthSq(); + if(distsq > rmax * rmax) { + return 0; + } + var dist = Math.sqrt(distsq); + var p = new Phaser.Vec2(); + Phaser.Vec2Utils.multiplyAdd(c1, t, 0.5 + (r1 - r2) * 0.5 / dist, p); + //var p = vec2.mad(c1, t, 0.5 + (r1 - r2) * 0.5 / dist); + var n = new Phaser.Vec2(); + //var n = (dist != 0) ? vec2.scale(t, 1 / dist) : vec2.zero; + if(dist != 0) { + Phaser.Vec2Utils.scale(t, 1 / dist, n); + } + var d = dist - rmax; + contactArr.push(new Advanced.Contact(p, n, d, 0)); + return 1; + }; + Collision.prototype.circle2Circle = function (circ1, circ2, contactArr) { + return this._circle2Circle(circ1.tc, circ1.radius, circ2.tc, circ2.radius, contactArr); + }; + Collision.prototype.circle2Segment = function (circ, seg, contactArr) { + var rsum = circ.radius + seg.radius; + // Normal distance from segment + var dn = Phaser.Vec2Utils.dot(circ.tc, seg.tn) - Phaser.Vec2Utils.dot(seg.ta, seg.tn); + var dist = (dn < 0 ? dn * -1 : dn) - rsum; + if(dist > 0) { + return 0; + } + // Tangential distance along segment + var dt = Phaser.Vec2Utils.cross(circ.tc, seg.tn); + var dtMin = Phaser.Vec2Utils.cross(seg.ta, seg.tn); + var dtMax = Phaser.Vec2Utils.cross(seg.tb, seg.tn); + if(dt < dtMin) { + if(dt < dtMin - rsum) { + return 0; + } + return this._circle2Circle(circ.tc, circ.radius, seg.ta, seg.radius, contactArr); + } else if(dt > dtMax) { + if(dt > dtMax + rsum) { + return 0; + } + return this._circle2Circle(circ.tc, circ.radius, seg.tb, seg.radius, contactArr); + } + var n = new Phaser.Vec2(); + if(dn > 0) { + n.copyFrom(seg.tn); + } else { + Phaser.Vec2Utils.negative(seg.tn, n); + } + //var n = (dn > 0) ? seg.tn : vec2.neg(seg.tn); + var c1 = new Phaser.Vec2(); + Phaser.Vec2Utils.multiplyAdd(circ.tc, n, -(circ.radius + dist * 0.5), c1); + var c2 = new Phaser.Vec2(); + Phaser.Vec2Utils.negative(n, c2); + contactArr.push(new Advanced.Contact(c1, c2, dist, 0)); + //contactArr.push(new Contact(vec2.mad(circ.tc, n, -(circ.r + dist * 0.5)), vec2.neg(n), dist, 0)); + return 1; + }; + Collision.prototype.circle2Poly = function (circ, poly, contactArr) { + var minDist = -999999; + var minIdx = -1; + for(var i = 0; i < poly.verts.length; i++) { + var plane = poly.tplanes[i]; + var dist = Phaser.Vec2Utils.dot(circ.tc, plane.n) - plane.d - circ.radius; + if(dist > 0) { + return 0; + } else if(dist > minDist) { + minDist = dist; + minIdx = i; + } + } + var n = poly.tplanes[minIdx].n; + var a = poly.tverts[minIdx]; + var b = poly.tverts[(minIdx + 1) % poly.verts.length]; + var dta = Phaser.Vec2Utils.cross(a, n); + var dtb = Phaser.Vec2Utils.cross(b, n); + var dt = Phaser.Vec2Utils.cross(circ.tc, n); + if(dt > dta) { + return this._circle2Circle(circ.tc, circ.radius, a, 0, contactArr); + } else if(dt < dtb) { + return this._circle2Circle(circ.tc, circ.radius, b, 0, contactArr); + } + var c1 = new Phaser.Vec2(); + Phaser.Vec2Utils.multiplyAdd(circ.tc, n, -(circ.radius + minDist * 0.5), c1); + var c2 = new Phaser.Vec2(); + Phaser.Vec2Utils.negative(n, c2); + contactArr.push(new Advanced.Contact(c1, c2, minDist, 0)); + //contactArr.push(new Contact(vec2.mad(circ.tc, n, -(circ.r + minDist * 0.5)), vec2.neg(n), minDist, 0)); + return 1; + }; + Collision.prototype.segmentPointDistanceSq = function (seg, p) { + var w = new Phaser.Vec2(); + var d = new Phaser.Vec2(); + Phaser.Vec2Utils.subtract(p, seg.ta, w); + Phaser.Vec2Utils.subtract(seg.tb, seg.ta, d); + //var w = vec2.sub(p, seg.ta); + //var d = vec2.sub(seg.tb, seg.ta); + var proj = w.dot(d); + if(proj <= 0) { + return w.dot(w); + } + var vsq = d.dot(d); + if(proj >= vsq) { + return w.dot(w) - 2 * proj + vsq; + } + return w.dot(w) - proj * proj / vsq; + }; + Collision.prototype.segment2Segment = // FIXME and optimise me lots!!! + function (seg1, seg2, contactArr) { + var d = []; + d[0] = this.segmentPointDistanceSq(seg1, seg2.ta); + d[1] = this.segmentPointDistanceSq(seg1, seg2.tb); + d[2] = this.segmentPointDistanceSq(seg2, seg1.ta); + d[3] = this.segmentPointDistanceSq(seg2, seg1.tb); + var idx1 = d[0] < d[1] ? 0 : 1; + var idx2 = d[2] < d[3] ? 2 : 3; + var idxm = d[idx1] < d[idx2] ? idx1 : idx2; + var s, t; + var u = Phaser.Vec2Utils.subtract(seg1.tb, seg1.ta); + var v = Phaser.Vec2Utils.subtract(seg2.tb, seg2.ta); + switch(idxm) { + case 0: + s = Phaser.Vec2Utils.dot(Phaser.Vec2Utils.subtract(seg2.ta, seg1.ta), u) / Phaser.Vec2Utils.dot(u, u); + s = s < 0 ? 0 : (s > 1 ? 1 : s); + t = 0; + break; + case 1: + s = Phaser.Vec2Utils.dot(Phaser.Vec2Utils.subtract(seg2.tb, seg1.ta), u) / Phaser.Vec2Utils.dot(u, u); + s = s < 0 ? 0 : (s > 1 ? 1 : s); + t = 1; + break; + case 2: + s = 0; + t = Phaser.Vec2Utils.dot(Phaser.Vec2Utils.subtract(seg1.ta, seg2.ta), v) / Phaser.Vec2Utils.dot(v, v); + t = t < 0 ? 0 : (t > 1 ? 1 : t); + break; + case 3: + s = 1; + t = Phaser.Vec2Utils.dot(Phaser.Vec2Utils.subtract(seg1.tb, seg2.ta), v) / Phaser.Vec2Utils.dot(v, v); + t = t < 0 ? 0 : (t > 1 ? 1 : t); + break; + } + var minp1 = Phaser.Vec2Utils.multiplyAdd(seg1.ta, u, s); + var minp2 = Phaser.Vec2Utils.multiplyAdd(seg2.ta, v, t); + return this._circle2Circle(minp1, seg1.radius, minp2, seg2.radius, contactArr); + }; + Collision.prototype.findPointsBehindSeg = // Identify vertexes that have penetrated the segment. + function (contactArr, seg, poly, dist, coef) { + var dta = Phaser.Vec2Utils.cross(seg.tn, seg.ta); + var dtb = Phaser.Vec2Utils.cross(seg.tn, seg.tb); + var n = new Phaser.Vec2(); + Phaser.Vec2Utils.scale(seg.tn, coef, n); + //var n = vec2.scale(seg.tn, coef); + for(var i = 0; i < poly.verts.length; i++) { + var v = poly.tverts[i]; + if(Phaser.Vec2Utils.dot(v, n) < Phaser.Vec2Utils.dot(seg.tn, seg.ta) * coef + seg.radius) { + var dt = Phaser.Vec2Utils.cross(seg.tn, v); + if(dta >= dt && dt >= dtb) { + contactArr.push(new Advanced.Contact(v, n, dist, (poly.id << 16) | i)); + } + } + } + }; + Collision.prototype.segment2Poly = function (seg, poly, contactArr) { + var seg_td = Phaser.Vec2Utils.dot(seg.tn, seg.ta); + var seg_d1 = poly.distanceOnPlane(seg.tn, seg_td) - seg.radius; + if(seg_d1 > 0) { + return 0; + } + var n = new Phaser.Vec2(); + Phaser.Vec2Utils.negative(seg.tn, n); + var seg_d2 = poly.distanceOnPlane(n, -seg_td) - seg.radius; + //var seg_d2 = poly.distanceOnPlane(vec2.neg(seg.tn), -seg_td) - seg.r; + if(seg_d2 > 0) { + return 0; + } + var poly_d = -999999; + var poly_i = -1; + for(var i = 0; i < poly.verts.length; i++) { + var plane = poly.tplanes[i]; + var dist = seg.distanceOnPlane(plane.n, plane.d); + if(dist > 0) { + return 0; + } + if(dist > poly_d) { + poly_d = dist; + poly_i = i; + } + } + var poly_n = new Phaser.Vec2(); + Phaser.Vec2Utils.negative(poly.tplanes[poly_i].n, poly_n); + //var poly_n = vec2.neg(poly.tplanes[poly_i].n); + var va = new Phaser.Vec2(); + Phaser.Vec2Utils.multiplyAdd(seg.ta, poly_n, seg.radius, va); + //var va = vec2.mad(seg.ta, poly_n, seg.r); + var vb = new Phaser.Vec2(); + Phaser.Vec2Utils.multiplyAdd(seg.tb, poly_n, seg.radius, vb); + //var vb = vec2.mad(seg.tb, poly_n, seg.r); + if(poly.containPoint(va)) { + contactArr.push(new Advanced.Contact(va, poly_n, poly_d, (seg.id << 16) | 0)); + } + if(poly.containPoint(vb)) { + contactArr.push(new Advanced.Contact(vb, poly_n, poly_d, (seg.id << 16) | 1)); + } + // Floating point precision problems here. + // This will have to do for now. + poly_d -= 0.1; + if(seg_d1 >= poly_d || seg_d2 >= poly_d) { + if(seg_d1 > seg_d2) { + this.findPointsBehindSeg(contactArr, seg, poly, seg_d1, 1); + } else { + this.findPointsBehindSeg(contactArr, seg, poly, seg_d2, -1); + } + } + // If no other collision points are found, try colliding endpoints. + if(contactArr.length == 0) { + var poly_a = poly.tverts[poly_i]; + var poly_b = poly.tverts[(poly_i + 1) % poly.verts.length]; + if(this._circle2Circle(seg.ta, seg.radius, poly_a, 0, contactArr)) { + return 1; + } + if(this._circle2Circle(seg.tb, seg.radius, poly_a, 0, contactArr)) { + return 1; + } + if(this._circle2Circle(seg.ta, seg.radius, poly_b, 0, contactArr)) { + return 1; + } + if(this._circle2Circle(seg.tb, seg.radius, poly_b, 0, contactArr)) { + return 1; + } + } + return contactArr.length; + }; + Collision.prototype.findMSA = // Find the minimum separating axis for the given poly and plane list. + function (poly, planes, num) { + var min_dist = -999999; + var min_index = -1; + for(var i = 0; i < num; i++) { + var dist = poly.distanceOnPlane(planes[i].n, planes[i].d); + if(dist > 0) { + // no collision + return { + dist: 0, + index: -1 + }; + } else if(dist > min_dist) { + min_dist = dist; + min_index = i; + } + } + // new object - see what we can do here + return { + dist: min_dist, + index: min_index + }; + }; + Collision.prototype.findVertsFallback = function (contactArr, poly1, poly2, n, dist) { + var num = 0; + for(var i = 0; i < poly1.verts.length; i++) { + var v = poly1.tverts[i]; + if(poly2.containPointPartial(v, n)) { + contactArr.push(new Advanced.Contact(v, n, dist, (poly1.id << 16) | i)); + num++; + } + } + for(var i = 0; i < poly2.verts.length; i++) { + var v = poly2.tverts[i]; + if(poly1.containPointPartial(v, n)) { + contactArr.push(new Advanced.Contact(v, n, dist, (poly2.id << 16) | i)); + num++; + } + } + return num; + }; + Collision.prototype.findVerts = // Find the overlapped vertices. + function (contactArr, poly1, poly2, n, dist) { + var num = 0; + for(var i = 0; i < poly1.verts.length; i++) { + var v = poly1.tverts[i]; + if(poly2.containPoint(v)) { + contactArr.push(new Advanced.Contact(v, n, dist, (poly1.id << 16) | i)); + num++; + } + } + for(var i = 0; i < poly2.verts.length; i++) { + var v = poly2.tverts[i]; + if(poly1.containPoint(v)) { + contactArr.push(new Advanced.Contact(v, n, dist, (poly2.id << 16) | i)); + num++; + } + } + return num > 0 ? num : this.findVertsFallback(contactArr, poly1, poly2, n, dist); + }; + Collision.prototype.poly2Poly = function (poly1, poly2, contactArr) { + var msa1 = this.findMSA(poly2, poly1.tplanes, poly1.verts.length); + if(msa1.index == -1) { + return 0; + } + var msa2 = this.findMSA(poly1, poly2.tplanes, poly2.verts.length); + if(msa2.index == -1) { + return 0; + } + // Penetration normal direction shoud be from poly1 to poly2 + if(msa1.dist > msa2.dist) { + return this.findVerts(contactArr, poly1, poly2, poly1.tplanes[msa1.index].n, msa1.dist); + } + return this.findVerts(contactArr, poly1, poly2, Phaser.Vec2Utils.negative(poly2.tplanes[msa2.index].n), msa2.dist); + }; + return Collision; + })(); + Advanced.Collision = Collision; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + /// + /// + /// + /// + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - Space + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + (function (Advanced) { + var Space = (function () { + function Space() { + this.stepCount = 0; + this.bodyArr = []; + this.bodyHash = { + }; + this.jointArr = []; + this.jointHash = { + }; + this.numContacts = 0; + this.contactSolvers = []; + //this.postSolve(arb) { }; + this.gravity = new Phaser.Vec2(0, 10); + this.damping = 0; + } + Space.TIME_TO_SLEEP = 0.5; + Space.SLEEP_LINEAR_TOLERANCE = 0.5; + Space.SLEEP_ANGULAR_TOLERANCE = 2 * Phaser.GameMath.DEG_TO_RAD; + Space.prototype.clear = function () { + Advanced.Manager.shapeCounter = 0; + Advanced.Manager.bodyCounter = 0; + Advanced.Manager.jointCounter = 0; + for(var i = 0; i < this.bodyArr.length; i++) { + if(this.bodyArr[i]) { + this.removeBody(this.bodyArr[i]); + } + } + this.bodyArr = []; + this.bodyHash = { + }; + this.jointArr = []; + this.jointHash = { + }; + this.contactSolvers = []; + this.stepCount = 0; + }; + Space.prototype.addBody = function (body) { + if(this.bodyHash[body.id] != undefined) { + return; + } + //console.log('Body added to space', body.name); + var index = this.bodyArr.push(body) - 1; + this.bodyHash[body.id] = index; + body.awake(true); + body.space = this; + body.cacheData(); + }; + Space.prototype.removeBody = function (body) { + if(this.bodyHash[body.id] == undefined) { + return; + } + // Remove linked joint + for(var i = 0; i < body.joints.length; i++) { + if(body.joints[i]) { + this.removeJoint(body.joints[i]); + } + } + body.space = null; + var index = this.bodyHash[body.id]; + delete this.bodyHash[body.id]; + delete this.bodyArr[index]; + }; + Space.prototype.addJoint = function (joint) { + if(this.jointHash[joint.id] != undefined) { + return; + } + joint.body1.awake(true); + joint.body2.awake(true); + var index = this.jointArr.push(joint) - 1; + this.jointHash[joint.id] = index; + var index = joint.body1.joints.push(joint) - 1; + joint.body1.jointHash[joint.id] = index; + var index = joint.body2.joints.push(joint) - 1; + joint.body2.jointHash[joint.id] = index; + }; + Space.prototype.removeJoint = function (joint) { + if(this.jointHash[joint.id] == undefined) { + return; + } + joint.body1.awake(true); + joint.body2.awake(true); + var index = joint.body1.jointHash[joint.id]; + delete joint.body1.jointHash[joint.id]; + delete joint.body1.joints[index]; + var index = joint.body2.jointHash[joint.id]; + delete joint.body2.jointHash[joint.id]; + delete joint.body2.joints[index]; + var index = this.jointHash[joint.id]; + delete this.jointHash[joint.id]; + delete this.jointArr[index]; + }; + Space.prototype.findShapeByPoint = function (p, refShape) { + var firstShape; + for(var i = 0; i < this.bodyArr.length; i++) { + var body = this.bodyArr[i]; + if(!body) { + continue; + } + for(var j = 0; j < body.shapes.length; j++) { + var shape = body.shapes[j]; + if(shape.pointQuery(p)) { + if(!refShape) { + return shape; + } + if(!firstShape) { + firstShape = shape; + } + if(shape == refShape) { + refShape = null; + } + } + } + } + return firstShape; + }; + Space.prototype.findBodyByPoint = function (p, refBody) { + var firstBody; + for(var i = 0; i < this.bodyArr.length; i++) { + var body = this.bodyArr[i]; + if(!body) { + continue; + } + for(var j = 0; j < body.shapes.length; j++) { + var shape = body.shapes[j]; + if(shape.pointQuery(p)) { + if(!refBody) { + return shape.body; + } + if(!firstBody) { + firstBody = shape.body; + } + if(shape.body == refBody) { + refBody = null; + } + break; + } + } + } + return firstBody; + }; + Space.prototype.shapeById = // TODO: Replace this function to shape hashing + function (id) { + var shape; + for(var i = 0; i < this.bodyArr.length; i++) { + var body = this.bodyArr[i]; + if(!body) { + continue; + } + for(var j = 0; j < body.shapes.length; j++) { + if(body.shapes[j].id == id) { + return body.shapes[j]; + } + } + } + return null; + }; + Space.prototype.jointById = function (id) { + var index = this.jointHash[id]; + if(index != undefined) { + return this.jointArr[index]; + } + return null; + }; + Space.prototype.findVertexByPoint = function (p, minDist, refVertexId) { + var firstVertexId = -1; + refVertexId = refVertexId || -1; + for(var i = 0; i < this.bodyArr.length; i++) { + var body = this.bodyArr[i]; + if(!body) { + continue; + } + for(var j = 0; j < body.shapes.length; j++) { + var shape = body.shapes[j]; + var index = shape.findVertexByPoint(p, minDist); + if(index != -1) { + var vertex = (shape.id << 16) | index; + if(refVertexId == -1) { + return vertex; + } + if(firstVertexId == -1) { + firstVertexId = vertex; + } + if(vertex == refVertexId) { + refVertexId = -1; + } + } + } + } + return firstVertexId; + }; + Space.prototype.findEdgeByPoint = function (p, minDist, refEdgeId) { + var firstEdgeId = -1; + refEdgeId = refEdgeId || -1; + for(var i = 0; i < this.bodyArr.length; i++) { + var body = this.bodyArr[i]; + if(!body) { + continue; + } + for(var j = 0; j < body.shapes.length; j++) { + var shape = body.shapes[j]; + if(shape.type != Advanced.Manager.SHAPE_TYPE_POLY) { + continue; + } + var index = shape.findEdgeByPoint(p, minDist); + if(index != -1) { + var edge = (shape.id << 16) | index; + if(refEdgeId == -1) { + return edge; + } + if(firstEdgeId == -1) { + firstEdgeId = edge; + } + if(edge == refEdgeId) { + refEdgeId = -1; + } + } + } + } + return firstEdgeId; + }; + Space.prototype.findJointByPoint = function (p, minDist, refJointId) { + var firstJointId = -1; + var dsq = minDist * minDist; + refJointId = refJointId || -1; + for(var i = 0; i < this.jointArr.length; i++) { + var joint = this.jointArr[i]; + if(!joint) { + continue; + } + var jointId = -1; + if(Phaser.Vec2Utils.distanceSq(p, joint.getWorldAnchor1()) < dsq) { + jointId = (joint.id << 16 | 0); + } else if(Phaser.Vec2Utils.distanceSq(p, joint.getWorldAnchor2()) < dsq) { + jointId = (joint.id << 16 | 1); + } + if(jointId != -1) { + if(refJointId == -1) { + return jointId; + } + if(firstJointId == -1) { + firstJointId = jointId; + } + if(jointId == refJointId) { + refJointId = -1; + } + } + } + return firstJointId; + }; + Space.prototype.findContactSolver = function (shape1, shape2) { + for(var i = 0; i < this.contactSolvers.length; i++) { + var contactSolver = this.contactSolvers[i]; + if(shape1 == contactSolver.shape1 && shape2 == contactSolver.shape2) { + return contactSolver; + } + } + return null; + }; + Space.prototype.genTemporalContactSolvers = function () { + //console.log('genTemporalContactSolvers'); + //var t0 = Date.now(); + var newContactSolverArr = []; + this.numContacts = 0; + for(var body1_index = 0; body1_index < this.bodyArr.length; body1_index++) { + var body1 = this.bodyArr[body1_index]; + //console.log('body1', body1_index, body1.type); + if(!body1) { + continue; + } + body1.stepCount = this.stepCount; + for(var body2_index = 0; body2_index < this.bodyArr.length; body2_index++) { + var body2 = this.bodyArr[body2_index]; + //console.log('body2', body2_index, body2.type); + if(!body2) { + continue; + } + if(body1.stepCount == body2.stepCount) { + continue; + } + //console.log('step'); + var active1 = body1.isAwake && !body1.isStatic; + var active2 = body2.isAwake && !body2.isStatic; + if(!active1 && !active2) { + continue; + } + //console.log('active'); + if(!body1.isCollidable(body2)) { + continue; + } + //console.log('collideable'); + if(!body1.bounds.intersectsBounds(body2.bounds)) { + continue; + } + //console.log('>>>>>>>>>> intersects'); + for(var i = 0; i < body1.shapes.length; i++) { + for(var j = 0; j < body2.shapes.length; j++) { + var shape1 = body1.shapes[i]; + var shape2 = body2.shapes[j]; + var contactArr = []; + if(!Advanced.Manager.collision.collide(shape1, shape2, contactArr)) { + continue; + } + if(shape1.type > shape2.type) { + var temp = shape1; + shape1 = shape2; + shape2 = temp; + } + this.numContacts += contactArr.length; + var contactSolver = this.findContactSolver(shape1, shape2); + if(contactSolver) { + contactSolver.update(contactArr); + newContactSolverArr.push(contactSolver); + } else { + body1.awake(true); + body2.awake(true); + var newContactSolver = new Advanced.ContactSolver(shape1, shape2); + newContactSolver.contacts = contactArr; + newContactSolver.elasticity = Math.max(shape1.elasticity, shape2.elasticity); + newContactSolver.friction = Math.sqrt(shape1.friction * shape2.friction); + newContactSolverArr.push(newContactSolver); + } + } + } + } + } + //stats.timeCollision = Date.now() - t0; + return newContactSolverArr; + }; + Space.prototype.initSolver = function (dt, dt_inv, warmStarting) { + //var t0 = Date.now(); + // Initialize contact solvers + for(var i = 0; i < this.contactSolvers.length; i++) { + this.contactSolvers[i].initSolver(dt_inv); + } + // Initialize joint solver + for(var i = 0; i < this.jointArr.length; i++) { + if(this.jointArr[i]) { + this.jointArr[i].initSolver(dt, warmStarting); + } + } + // Warm starting (apply cached impulse) + if(warmStarting) { + for(var i = 0; i < this.contactSolvers.length; i++) { + this.contactSolvers[i].warmStart(); + } + } + //stats.timeInitSolver = Date.now() - t0; + }; + Space.prototype.velocitySolver = function (iteration) { + //var t0 = Date.now(); + for(var i = 0; i < iteration; i++) { + for(var j = 0; j < this.jointArr.length; j++) { + if(this.jointArr[j]) { + this.jointArr[j].solveVelocityConstraints(); + } + } + for(var j = 0; j < this.contactSolvers.length; j++) { + this.contactSolvers[j].solveVelocityConstraints(); + } + } + //stats.timeVelocitySolver = Date.now() - t0; + }; + Space.prototype.positionSolver = function (iteration) { + //var t0 = Date.now(); + var positionSolved = false; + //stats.positionIterations = 0; + for(var i = 0; i < iteration; i++) { + var contactsOk = true; + var jointsOk = true; + for(var j = 0; j < this.contactSolvers.length; j++) { + var contactOk = this.contactSolvers[j].solvePositionConstraints(); + contactsOk = contactOk && contactsOk; + } + for(var j = 0; j < this.jointArr.length; j++) { + if(this.jointArr[j]) { + var jointOk = this.jointArr[j].solvePositionConstraints(); + jointsOk = jointOk && jointsOk; + } + } + if(contactsOk && jointsOk) { + // exit early if the position errors are small + positionSolved = true; + break; + } + //stats.positionIterations++; + } + //stats.timePositionSolver = Date.now() - t0; + return positionSolved; + }; + Space.prototype.step = function (dt, vel_iteration, pos_iteration, warmStarting, allowSleep) { + var dt_inv = 1 / dt; + this.stepCount++; + // Generate contact & contactSolver + this.contactSolvers = this.genTemporalContactSolvers(); + // Initialize contacts & joints solver + this.initSolver(dt, dt_inv, warmStarting); + // Intergrate velocity + for(var i = 0; i < this.bodyArr.length; i++) { + var body = this.bodyArr[i]; + if(!body) { + continue; + } + if(body.isDynamic && body.isAwake) { + body.updateVelocity(this.gravity, dt, this.damping); + } + } + for(var i = 0; i < this.jointArr.length; i++) { + var joint = this.jointArr[i]; + if(!joint) { + continue; + } + var body1 = joint.body1; + var body2 = joint.body2; + var awake1 = body1.isAwake && !body1.isStatic; + var awake2 = body2.isAwake && !body2.isStatic; + if(awake1 ^ awake2) { + if(!awake1) { + body1.awake(true); + } + if(!awake2) { + body2.awake(true); + } + } + } + // Iterative velocity constraints solver + this.velocitySolver(vel_iteration); + // Intergrate position + for(var i = 0; i < this.bodyArr.length; i++) { + var body = this.bodyArr[i]; + if(!body) { + continue; + } + if(body.isDynamic && body.isAwake) { + body.updatePosition(dt); + } + } + // Process breakable joint + for(var i = 0; i < this.jointArr.length; i++) { + var joint = this.jointArr[i]; + if(!joint) { + continue; + } + if(joint.breakable) { + if(joint.getReactionForce(dt_inv).lengthsq() >= joint.maxForce * joint.maxForce) { + this.removeJoint(joint); + } + } + } + // Iterative position constraints solver + var positionSolved = this.positionSolver(pos_iteration); + for(var i = 0; i < this.bodyArr.length; i++) { + var body = this.bodyArr[i]; + if(!body) { + continue; + } + body.syncTransform(); + } + // Post solve collision callback + for(var i = 0; i < this.contactSolvers.length; i++) { + var arb = this.contactSolvers[i]; + // Re-enable this + //this.postSolve(arb); + } + for(var i = 0; i < this.bodyArr.length; i++) { + var body = this.bodyArr[i]; + if(!body) { + continue; + } + if(body.isDynamic && body.isAwake) { + body.cacheData(); + } + } + // Process sleeping + if(allowSleep) { + var minSleepTime = 999999; + var linTolSqr = Space.SLEEP_LINEAR_TOLERANCE * Space.SLEEP_LINEAR_TOLERANCE; + var angTolSqr = Space.SLEEP_ANGULAR_TOLERANCE * Space.SLEEP_ANGULAR_TOLERANCE; + for(var i = 0; i < this.bodyArr.length; i++) { + var body = this.bodyArr[i]; + if(!body) { + continue; + } + if(!body.isDynamic) { + continue; + } + if(body.angularVelocity * body.angularVelocity > angTolSqr || body.velocity.dot(body.velocity) > linTolSqr) { + body.sleepTime = 0; + minSleepTime = 0; + } else { + body.sleepTime += dt; + minSleepTime = Math.min(minSleepTime, body.sleepTime); + } + } + if(positionSolved && minSleepTime >= Space.TIME_TO_SLEEP) { + for(var i = 0; i < this.bodyArr.length; i++) { + var body = this.bodyArr[i]; + if(!body) { + continue; + } + body.awake(false); + } + } + } + }; + return Space; + })(); + Advanced.Space = Space; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - Body + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + (function (Advanced) { + var Body = (function () { + function Body(sprite, type, x, y) { + if (typeof x === "undefined") { x = 0; } + if (typeof y === "undefined") { y = 0; } + // Shapes + this.shapes = []; + // Joints + this.joints = []; + this.jointHash = { + }; + this.fixedRotation = false; + this.categoryBits = 0x0001; + this.maskBits = 0xFFFF; + this.stepCount = 0; + this._tempVec2 = new Phaser.Vec2(); + this.id = Phaser.Physics.Advanced.Manager.bodyCounter++; + this.name = 'body' + this.id; + this.type = type; + if(sprite) { + this.sprite = sprite; + this.game = sprite.game; + this.position = new Phaser.Vec2(sprite.x, sprite.y); + this.angle = sprite.rotation; + } else { + this.position = new Phaser.Vec2(x, y); + this.angle = 0; + } + this.transform = new Phaser.Transform(this.position, this.angle); + this.centroid = new Phaser.Vec2(); + this.velocity = new Phaser.Vec2(); + this.force = new Phaser.Vec2(); + this.angularVelocity = 0; + this.torque = 0; + this.linearDamping = 0; + this.angularDamping = 0; + this.sleepTime = 0; + this.awaked = false; + this.shapes = []; + this.joints = []; + this.jointHash = { + }; + this.bounds = new Advanced.Bounds(); + this.fixedRotation = false; + this.categoryBits = 0x0001; + this.maskBits = 0xFFFF; + this.stepCount = 0; + } + Body.prototype.duplicate = function () { + //console.log('body duplicate called'); + //var body = new Body(this.type, this.transform.t, this.angle); + //for (var i = 0; i < this.shapes.length; i++) + //{ + // body.addShape(this.shapes[i].duplicate()); + //} + //body.resetMassData(); + //return body; + }; + Object.defineProperty(Body.prototype, "isDisabled", { + get: function () { + return this.type == Phaser.Types.BODY_DISABLED ? true : false; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Body.prototype, "isStatic", { + get: function () { + return this.type == Phaser.Types.BODY_STATIC ? true : false; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Body.prototype, "isKinetic", { + get: function () { + return this.type == Phaser.Types.BODY_KINETIC ? true : false; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Body.prototype, "isDynamic", { + get: function () { + return this.type == Phaser.Types.BODY_DYNAMIC ? true : false; + }, + enumerable: true, + configurable: true + }); + Body.prototype.setType = function (type) { + if(type == this.type) { + return; + } + this.force.setTo(0, 0); + this.velocity.setTo(0, 0); + this.torque = 0; + this.angularVelocity = 0; + this.type = type; + this.awake(true); + }; + Body.prototype.addShape = function (shape) { + // Check not already part of this body + shape.body = this; + this.shapes.push(shape); + return shape; + }; + Body.prototype.removeShape = function (shape) { + var index = this.shapes.indexOf(shape); + if(index != -1) { + this.shapes.splice(index, 1); + shape.body = undefined; + } + }; + Body.prototype.setMass = function (mass) { + this.mass = mass; + this.massInverted = mass > 0 ? 1 / mass : 0; + }; + Body.prototype.setInertia = function (inertia) { + this.inertia = inertia; + this.inertiaInverted = inertia > 0 ? 1 / inertia : 0; + }; + Body.prototype.setTransform = function (pos, angle) { + this.transform.setTo(pos, angle); + // inject the transform into this.position + Phaser.TransformUtils.transform(this.transform, this.centroid, this.position); + //this.position.copyFrom(this.transform.transform(this.centroid)); + this.angle = angle; + }; + Body.prototype.syncTransform = function () { + this.transform.setRotation(this.angle); + //var rotc: Phaser.Vec2 = this.transform.rotate(this.centroid); + //var sub: Phaser.Vec2 = Phaser.Vec2Utils.subtract(this.position, rotc); + //this.transform.setPosition(sub); + // this.transform.setPosition(vec2.sub(this.position, this.transform.rotate(this.centroid))); + //Phaser.Vec2Utils.subtract(this.position, this.transform.rotate(this.centroid), this.transform.t); + // OPTIMISE: Creating new vector + Phaser.Vec2Utils.subtract(this.position, Phaser.TransformUtils.rotate(this.transform, this.centroid), this.transform.t); + }; + Body.prototype.getWorldPoint = function (p) { + // OPTIMISE: Creating new vector + return Phaser.TransformUtils.transform(this.transform, p); + }; + Body.prototype.getWorldVector = function (v) { + // OPTIMISE: Creating new vector + return Phaser.TransformUtils.rotate(this.transform, v); + }; + Body.prototype.getLocalPoint = function (p) { + // OPTIMISE: Creating new vector + return Phaser.TransformUtils.untransform(this.transform, p); + }; + Body.prototype.getLocalVector = function (v) { + // OPTIMISE: Creating new vector + return Phaser.TransformUtils.unrotate(this.transform, v); + }; + Body.prototype.setFixedRotation = function (flag) { + this.fixedRotation = flag; + this.resetMassData(); + }; + Body.prototype.resetMassData = function () { + this.centroid.setTo(0, 0); + this.mass = 0; + this.massInverted = 0; + this.inertia = 0; + this.inertiaInverted = 0; + if(this.isDynamic == false) { + Phaser.TransformUtils.transform(this.transform, this.centroid, this.position); + //this.position.copyFrom(this.transform.transform(this.centroid)); + return; + } + var totalMassCentroid = new Phaser.Vec2(0, 0); + var totalMass = 0; + var totalInertia = 0; + for(var i = 0; i < this.shapes.length; i++) { + var shape = this.shapes[i]; + var centroid = shape.centroid(); + var mass = shape.area() * shape.density; + var inertia = shape.inertia(mass); + //console.log('rmd', centroid, shape); + totalMassCentroid.multiplyAddByScalar(centroid, mass); + totalMass += mass; + totalInertia += inertia; + } + //this.centroid.copy(vec2.scale(totalMassCentroid, 1 / totalMass)); + Phaser.Vec2Utils.scale(totalMassCentroid, 1 / totalMass, this.centroid); + this.setMass(totalMass); + if(!this.fixedRotation) { + //this.setInertia(totalInertia - totalMass * vec2.dot(this.centroid, this.centroid)); + this.setInertia(totalInertia - totalMass * Phaser.Vec2Utils.dot(this.centroid, this.centroid)); + } + //console.log("mass = " + this.m + " inertia = " + this.i); + // Move center of mass + var oldPosition = Phaser.Vec2Utils.clone(this.position); + //this.position.copyFrom(this.transform.transform(this.centroid)); + Phaser.TransformUtils.transform(this.transform, this.centroid, this.position); + // Update center of mass velocity + //this.velocity.mad(vec2.perp(vec2.sub(this.position, old_p)), this.angularVelocity); + oldPosition.subtract(this.position); + this.velocity.multiplyAddByScalar(Phaser.Vec2Utils.perp(oldPosition, oldPosition), this.angularVelocity); + }; + Body.prototype.resetJointAnchors = function () { + for(var i = 0; i < this.joints.length; i++) { + var joint = this.joints[i]; + if(!joint) { + continue; + } + var anchor1 = joint.getWorldAnchor1(); + var anchor2 = joint.getWorldAnchor2(); + joint.setWorldAnchor1(anchor1); + joint.setWorldAnchor2(anchor2); + } + }; + Body.prototype.cacheData = function () { + //console.log('Body cacheData', this.name, 'len', this.shapes.length); + this.bounds.clear(); + for(var i = 0; i < this.shapes.length; i++) { + var shape = this.shapes[i]; + shape.cacheData(this.transform); + this.bounds.addBounds(shape.bounds); + } + }; + Body.prototype.updateVelocity = function (gravity, dt, damping) { + // this.velocity = vec2.mad(this.velocity, vec2.mad(gravity, this.force, this.massInverted), dt); + Phaser.Vec2Utils.multiplyAdd(gravity, this.force, this.massInverted, this._tempVec2); + Phaser.Vec2Utils.multiplyAdd(this.velocity, this._tempVec2, dt, this.velocity); + this.angularVelocity = this.angularVelocity + this.torque * this.inertiaInverted * dt; + // Apply damping. + // ODE: dv/dt + c * v = 0 + // Solution: v(t) = v0 * exp(-c * t) + // Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v * exp(-c * dt) + // v2 = exp(-c * dt) * v1 + // Taylor expansion: + // v2 = (1.0f - c * dt) * v1 + this.velocity.scale(this.game.math.clamp(1 - dt * (damping + this.linearDamping), 0, 1)); + this.angularVelocity *= this.game.math.clamp(1 - dt * (damping + this.angularDamping), 0, 1); + this.force.setTo(0, 0); + this.torque = 0; + }; + Body.prototype.updatePosition = function (dt) { + //console.log('body update pos', this.position.y); + //console.log('pre add temp', this._tempVec2.y); + //this.position.addself(vec2.scale(this.velocity, dt)); + this.position.add(Phaser.Vec2Utils.scale(this.velocity, dt, this._tempVec2)); + //console.log('post add temp', this._tempVec2.y); + //console.log('post add', this.position.y); + this.angle += this.angularVelocity * dt; + }; + Body.prototype.resetForce = function () { + this.force.setTo(0, 0); + this.torque = 0; + }; + Body.prototype.applyForce = function (force, p) { + if(this.isDynamic == false) { + return; + } + if(this.isAwake == false) { + this.awake(true); + } + this.force.add(force); + // this.f.addself(force); + // this.torque += vec2.cross(vec2.sub(p, this.p), force); + Phaser.Vec2Utils.subtract(p, this.position, this._tempVec2); + this.torque += Phaser.Vec2Utils.cross(this._tempVec2, force); + }; + Body.prototype.applyForceToCenter = function (force) { + if(this.isDynamic == false) { + return; + } + if(this.isAwake == false) { + this.awake(true); + } + this.force.add(force); + }; + Body.prototype.applyTorque = function (torque) { + if(this.isDynamic == false) { + return; + } + if(this.isAwake == false) { + this.awake(true); + } + this.torque += torque; + }; + Body.prototype.applyLinearImpulse = function (impulse, p) { + if(this.isDynamic == false) { + return; + } + if(this.isAwake == false) { + this.awake(true); + } + this.velocity.multiplyAddByScalar(impulse, this.massInverted); + // this.angularVelocity += vec2.cross(vec2.sub(p, this.position), impulse) * this.inertiaInverted; + Phaser.Vec2Utils.subtract(p, this.position, this._tempVec2); + this.angularVelocity += Phaser.Vec2Utils.cross(this._tempVec2, impulse) * this.inertiaInverted; + }; + Body.prototype.applyAngularImpulse = function (impulse) { + if(this.isDynamic == false) { + return; + } + if(this.isAwake == false) { + this.awake(true); + } + this.angularVelocity += impulse * this.inertiaInverted; + }; + Body.prototype.kineticEnergy = function () { + var vsq = this.velocity.dot(this.velocity); + var wsq = this.angularVelocity * this.angularVelocity; + return 0.5 * (this.mass * vsq + this.inertia * wsq); + }; + Object.defineProperty(Body.prototype, "isAwake", { + get: function () { + return this.awaked; + }, + enumerable: true, + configurable: true + }); + Body.prototype.awake = function (flag) { + this.awaked = flag; + if(flag) { + this.sleepTime = 0; + } else { + this.velocity.setTo(0, 0); + this.angularVelocity = 0; + this.force.setTo(0, 0); + this.torque = 0; + } + }; + Body.prototype.isCollidable = function (other) { + if(this == other) { + return false; + } + if(this.isDynamic == false && other.isDynamic == false) { + return false; + } + if(!(this.maskBits & other.categoryBits) || !(other.maskBits & this.categoryBits)) { + return false; + } + for(var i = 0; i < this.joints.length; i++) { + var joint = this.joints[i]; + if(!joint) { + continue; + } + if(!joint.collideConnected && other.jointHash[joint.id] != undefined) { + return false; + } + } + return true; + }; + return Body; + })(); + Advanced.Body = Body; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + (function (Advanced) { + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - Shapes - Box + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + (function (Shapes) { + var Box = (function (_super) { + __extends(Box, _super); + // Give in pixels + function Box(x, y, width, height) { + x = Advanced.Manager.pixelsToMeters(x); + y = Advanced.Manager.pixelsToMeters(y); + width = Advanced.Manager.pixelsToMeters(width); + height = Advanced.Manager.pixelsToMeters(height); + var hw = width * 0.5; + var hh = height * 0.5; + _super.call(this, [ + new Phaser.Vec2(-hw + x, +hh + y), + new Phaser.Vec2(-hw + x, -hh + y), + new Phaser.Vec2(+hw + x, -hh + y), + new Phaser.Vec2(+hw + x, +hh + y) + ]); + } + return Box; + })(Phaser.Physics.Advanced.Shapes.Poly); + Shapes.Box = Box; + })(Advanced.Shapes || (Advanced.Shapes = {})); + var Shapes = Advanced.Shapes; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + (function (Physics) { + (function (Advanced) { + /// + /// + /// + /// + /// + /** + * Phaser - Advanced Physics - Shapes - Triangle + * + * Based on the work Ju Hyung Lee started in JS PhyRus. + */ + (function (Shapes) { + var Triangle = (function (_super) { + __extends(Triangle, _super); + function Triangle(p1, p2, p3) { + _super.call(this, [ + new Phaser.Vec2(p1.x, p1.y), + new Phaser.Vec2(p2.x, p2.y), + new Phaser.Vec2(p3.x, p3.y) + ]); + } + return Triangle; + })(Phaser.Physics.Advanced.Shapes.Poly); + Shapes.Triangle = Triangle; + })(Advanced.Shapes || (Advanced.Shapes = {})); + var Shapes = Advanced.Shapes; + })(Physics.Advanced || (Physics.Advanced = {})); + var Advanced = Physics.Advanced; + })(Phaser.Physics || (Phaser.Physics = {})); + var Physics = Phaser.Physics; +})(Phaser || (Phaser = {})); +/// /// /// ///