From 97b005d0870767551229f78f4e5667e88891d832 Mon Sep 17 00:00:00 2001 From: eonarheim Date: Thu, 29 Sep 2016 23:46:04 -0500 Subject: [PATCH 1/8] WIP Physics fix --- GruntFile.js | 4 +- sandbox/web/src/game.js | 2 +- sandbox/web/src/game.ts | 2 +- src/engine/Actor.ts | 36 ++++++++++++++++- src/engine/Collision/Body.ts | 9 +++++ src/engine/Collision/CollisionContact.ts | 4 +- src/engine/Collision/DynamicTree.ts | 6 +-- .../DynamicTreeCollisionBroadphase.ts | 27 ++++++++++--- src/engine/Collision/ICollisionResolver.ts | 9 +++-- .../Collision/NaiveCollisionBroadphase.ts | 6 +-- src/engine/Scene.ts | 39 ++++++++++-------- src/spec/ActorSpec.ts | 40 ++++++++++++++++++- src/spec/DynamicTreeBroadphaseSpec.ts | 8 ++-- 13 files changed, 149 insertions(+), 43 deletions(-) diff --git a/GruntFile.js b/GruntFile.js index fca730026..9ba0fdfa1 100644 --- a/GruntFile.js +++ b/GruntFile.js @@ -151,10 +151,10 @@ module.exports = function (grunt) { }, gitBuild: { - command: 'git clone https://github.com/excaliburjs/excalibur-dist build', + command: 'git clone -q https://github.com/excaliburjs/excalibur-dist build', options: { stdout: true, - failOnError: true + failOnError: false } } diff --git a/sandbox/web/src/game.js b/sandbox/web/src/game.js index 8ef39d426..7dedbe79f 100644 --- a/sandbox/web/src/game.js +++ b/sandbox/web/src/game.js @@ -189,7 +189,7 @@ player.on('update', function () { } player.vel.x = groundSpeed; } - if (game.input.keyboard.isHeld(ex.Input.Keys.Up)) { + if (game.input.keyboard.wasPressed(ex.Input.Keys.Up)) { if (!inAir) { player.vel.y = -jumpSpeed; inAir = true; diff --git a/sandbox/web/src/game.ts b/sandbox/web/src/game.ts index bd99f968d..376c09a6d 100644 --- a/sandbox/web/src/game.ts +++ b/sandbox/web/src/game.ts @@ -231,7 +231,7 @@ player.on('update', () => { // TODO: When platform is moving in same direction, add its dx } - if (game.input.keyboard.isHeld(ex.Input.Keys.Up)) { + if (game.input.keyboard.wasPressed(ex.Input.Keys.Up)) { if (!inAir) { player.vel.y = -jumpSpeed; inAir = true; diff --git a/src/engine/Actor.ts b/src/engine/Actor.ts index bdea0f111..026879fdd 100644 --- a/src/engine/Actor.ts +++ b/src/engine/Actor.ts @@ -663,7 +663,7 @@ module ex { this.opacity = color.a; } // Build default pipeline - this.traits.push(new ex.Traits.EulerMovement()); + //this.traits.push(new ex.Traits.EulerMovement()); // TODO: TileMaps should be converted to a collision area this.traits.push(new ex.Traits.TileMapCollisionDetection()); this.traits.push(new ex.Traits.OffscreenCulling()); @@ -1157,6 +1157,38 @@ module ex { private _getCalculatedAnchor(): Vector { return new ex.Vector(this.getWidth() * this.anchor.x, this.getHeight() * this.anchor.y); } + + /** + * Perform euler integration at the specified time step + */ + public integrate(delta: number) { + // Update placements based on linear algebra + var seconds = delta / 1000; + + var totalAcc = this.acc.clone(); + // Only active vanilla actors are affected by global acceleration + if (this.collisionType === ex.CollisionType.Active && + !(this instanceof UIActor) && + !(this instanceof Trigger) && + !(this instanceof Label)) { + totalAcc.addEqual(ex.Physics.acc); + } + + this.oldVel = this.vel; + this.vel.addEqual(totalAcc.scale(seconds)); + + this.pos.addEqual(this.vel.scale(seconds)).addEqual(totalAcc.scale(0.5 * seconds * seconds)); + + this.rx += this.torque * (1.0 / this.moi) * seconds; + this.rotation += this.rx * seconds; + + this.scale.x += this.sx * delta / 1000; + this.scale.y += this.sy * delta / 1000; + + // Update physics body + this.body.update(); + } + /** * Called by the Engine, updates the state of the actor * @param engine The reference to the current game engine @@ -1197,6 +1229,8 @@ module ex { } } + this.integrate(delta); + // Update actor pipeline (movement, collision detection, event propagation, offscreen culling) for (var trait of this.traits) { trait.update(this, engine, delta); diff --git a/src/engine/Collision/Body.ts b/src/engine/Collision/Body.ts index 95697b3d2..795e79cab 100644 --- a/src/engine/Collision/Body.ts +++ b/src/engine/Collision/Body.ts @@ -102,6 +102,15 @@ module ex { } } + /** + * Updates the collision area geometry and internal caches + */ + public update() { + if (this.collisionArea) { + this.collisionArea.recalc(); + } + } + /** * Sets up a box collision area based on the current bounds of the associated actor of this physics body. diff --git a/src/engine/Collision/CollisionContact.ts b/src/engine/Collision/CollisionContact.ts index 11f07e59d..aad98bab1 100644 --- a/src/engine/Collision/CollisionContact.ts +++ b/src/engine/Collision/CollisionContact.ts @@ -103,9 +103,9 @@ module ex { var velY = 0; // both bodies are traveling in the same direction (negative or positive) if (bodyA.vel.y <= 0 && bodyB.vel.y <= 0) { - velY = Math.min(bodyA.vel.y, bodyB.vel.y); - } else if (bodyA.vel.y >= 0 && bodyB.vel.y >= 0) { velY = Math.max(bodyA.vel.y, bodyB.vel.y); + } else if (bodyA.vel.y >= 0 && bodyB.vel.y >= 0) { + velY = Math.min(bodyA.vel.y, bodyB.vel.y); } else { // bodies are traveling in opposite directions velY = 0; diff --git a/src/engine/Collision/DynamicTree.ts b/src/engine/Collision/DynamicTree.ts index de843fcf6..8f911550d 100644 --- a/src/engine/Collision/DynamicTree.ts +++ b/src/engine/Collision/DynamicTree.ts @@ -179,7 +179,7 @@ module ex { } - public registerBody(body: Body) { + public trackBody(body: Body) { var node = new TreeNode(); node.body = body; node.bounds = body.getBounds(); @@ -226,7 +226,7 @@ module ex { return true; } - public removeBody(body: Body) { + public untrackBody(body: Body) { var node = this.nodes[body.actor.id]; if (!node) { return; } this.remove(node); @@ -360,7 +360,7 @@ module ex { public query(body: Body, callback: (other: Body) => boolean): void { var bounds = body.getBounds(); - var helper = currentNode => { + var helper = (currentNode: TreeNode) => { if (currentNode && currentNode.bounds.collides(bounds)) { if (currentNode.isLeaf() && currentNode.body !== body) { if (callback.call(body, currentNode.body)) { diff --git a/src/engine/Collision/DynamicTreeCollisionBroadphase.ts b/src/engine/Collision/DynamicTreeCollisionBroadphase.ts index cc1c53d05..7746fcec9 100644 --- a/src/engine/Collision/DynamicTreeCollisionBroadphase.ts +++ b/src/engine/Collision/DynamicTreeCollisionBroadphase.ts @@ -8,12 +8,26 @@ module ex { private _collisionHash: { [key: string]: boolean; } = {}; private _collisionContactCache: CollisionContact[] = []; - public register(target: Actor): void { - this._dynamicCollisionTree.registerBody(target.body); + /** + * Tracks a physics body for collisions + */ + public track(target: Body): void { + if (!target) { + ex.Logger.getInstance().warn('Cannot track null physics body'); + return; + } + this._dynamicCollisionTree.trackBody(target); } - public remove(target: Actor): void { - this._dynamicCollisionTree.removeBody(target.body); + /** + * Untracks a physics body + */ + public untrack(target: Body): void { + if (!target) { + ex.Logger.getInstance().warn('Cannot untrack a null physics body'); + return; + } + this._dynamicCollisionTree.untrackBody(target); } private _canCollide(actorA: Actor, actorB: Actor) { @@ -35,7 +49,7 @@ module ex { return true; } - public findCollisionContacts(targets: Actor[], delta: number): CollisionContact[] { + public detect(targets: Actor[], delta: number): CollisionContact[] { // TODO optimization use only the actors that are moving to start // Retrieve the list of potential colliders, exclude killed, prevented, and self var potentialColliders = targets.filter((other) => { @@ -108,6 +122,9 @@ module ex { return this._collisionContactCache; } + /** + * Update the dynamic tree positions + */ public update(targets: Actor[], delta: number): number { var updated = 0, i = 0, len = targets.length; diff --git a/src/engine/Collision/ICollisionResolver.ts b/src/engine/Collision/ICollisionResolver.ts index 616ed59ef..95981f173 100644 --- a/src/engine/Collision/ICollisionResolver.ts +++ b/src/engine/Collision/ICollisionResolver.ts @@ -1,10 +1,11 @@ module ex { export interface ICollisionBroadphase { - register(target: Actor); - remove(tartet: Actor); - - findCollisionContacts(targets: Actor[], delta: number): CollisionContact[]; + track(target: Body); + untrack(tartet: Body); + + //getPairs(): CollisionContact[]; + detect(targets: Actor[], delta: number): CollisionContact[]; update(targets: Actor[], delta: number): number; debugDraw(ctx, delta): void; diff --git a/src/engine/Collision/NaiveCollisionBroadphase.ts b/src/engine/Collision/NaiveCollisionBroadphase.ts index c361394a8..bfe7fa9d4 100644 --- a/src/engine/Collision/NaiveCollisionBroadphase.ts +++ b/src/engine/Collision/NaiveCollisionBroadphase.ts @@ -5,15 +5,15 @@ module ex { export class NaiveCollisionBroadphase implements ICollisionBroadphase { - public register(target: Actor) { + public track(target: Body) { // pass } - public remove(tartet: Actor) { + public untrack(tartet: Body) { // pass } - public findCollisionContacts(targets: Actor[], delta: number): CollisionContact[] { + public detect(targets: Actor[], delta: number): CollisionContact[] { // Retrieve the list of potential colliders, exclude killed, prevented, and self var potentialColliders = targets.filter((other) => { diff --git a/src/engine/Scene.ts b/src/engine/Scene.ts index a8149fa64..d206825f4 100644 --- a/src/engine/Scene.ts +++ b/src/engine/Scene.ts @@ -254,26 +254,33 @@ module ex { for (i = 0, len = this.tileMaps.length; i < len; i++) { this.tileMaps[i].update(engine, delta); } - - var iter: number = Physics.collisionPasses; + + for (i = 0, len = this.children.length; i < len; i++) { + //this.children[i].integrate(delta); + } + + // Cycle through actors updating actors + for (i = 0, len = this.children.length; i < len; i++) { + this.children[i].update(engine, delta); + } + this._broadphase.update(this.children, delta); + + var iter: number = 20; //Physics.collisionPasses; var collisionDelta = delta / iter; - while (iter > 0) { - // Cycle through actors updating actors - for (i = 0, len = this.children.length; i < len; i++) { - this.children[i].update(engine, collisionDelta); - this.children[i].collisionArea.recalc(); - } - // TODO meh I don't like how this works... maybe find a way to make collisions - // a trait + while (iter > 0) { // Run collision resolution strategy - if (this._broadphase && Physics.enabled) { - this._broadphase.update(this.children, collisionDelta); - this._broadphase.findCollisionContacts(this.children, collisionDelta); + if (this._broadphase && Physics.enabled) { + this._broadphase.detect(this.children, collisionDelta); + for (i = 0, len = this.children.length; i < len; i++) { + this.children[i].integrate(collisionDelta * .01); + } } iter--; } + + // Remove actors from scene graph after being killed var actorIndex: number; @@ -464,7 +471,7 @@ module ex { return; } if (entity instanceof Actor) { - this._broadphase.remove(entity); + this._broadphase.untrack(entity.body); this._removeChild(entity); } if (entity instanceof Timer) { @@ -501,7 +508,7 @@ module ex { * Adds an actor to the scene, once this is done the actor will be drawn and updated. */ protected _addChild(actor: Actor) { - this._broadphase.register(actor); + this._broadphase.track(actor.body); actor.scene = this; this.children.push(actor); this._sortedDrawingTree.add(actor); @@ -529,7 +536,7 @@ module ex { * Removes an actor from the scene, it will no longer be drawn or updated. */ protected _removeChild(actor: Actor) { - this._broadphase.remove(actor); + this._broadphase.untrack(actor.body); this._killQueue.push(actor); actor.parent = null; } diff --git a/src/spec/ActorSpec.ts b/src/spec/ActorSpec.ts index dbfa79dd0..91a24681b 100644 --- a/src/spec/ActorSpec.ts +++ b/src/spec/ActorSpec.ts @@ -22,6 +22,9 @@ describe('A game actor', () => { spyOn(actor, 'debugDraw'); engine = mock.engine(100, 100, scene); + + ex.Physics.useBoxPhysics(); + ex.Physics.acc.setTo(0, 0); }); it('should be loaded', () => { @@ -502,8 +505,9 @@ describe('A game actor', () => { }); it('with an active collision type can be placed on a fixed type', () => { + ex.Physics.useBoxPhysics(); var scene = new ex.Scene(engine); - + var active = new ex.Actor(0, -50, 100, 100); active.collisionType = ex.CollisionType.Active; active.vel.y = 10; @@ -526,8 +530,42 @@ describe('A game actor', () => { scene.update(engine, 100); } + expect(active.pos.x).toBeCloseTo(0, .0001); + expect(active.pos.y).toBeCloseTo(-50, .0001); + + expect(fixed.pos.x).toBe(-100); + expect(fixed.pos.y).toBe(50); + }); + + it('with an active collision type can jump on a fixed type', () => { + var scene = new ex.Scene(engine); + var active = new ex.Actor(0, -50, 100, 100); + active.collisionType = ex.CollisionType.Active; + active.vel.y = -100; + //active.acc.y = 1000; + + var fixed = new ex.Actor(-100, 50, 1000, 100); + fixed.collisionType = ex.CollisionType.Fixed; + + scene.add(active); + scene.add(fixed); + expect(active.pos.x).toBe(0); expect(active.pos.y).toBe(-50); + + expect(fixed.pos.x).toBe(-100); + expect(fixed.pos.y).toBe(50); + + var iterations = 50; + // update many times for safety + for (var i = 0; i < iterations; i++) { + scene.update(engine, 100); + } + + var seconds = 100 / 1000; + + expect(active.pos.x).toBeCloseTo(0, .0001); + expect(active.pos.y).toBeCloseTo(-100 * seconds * iterations, .0001); expect(fixed.pos.x).toBe(-100); expect(fixed.pos.y).toBe(50); diff --git a/src/spec/DynamicTreeBroadphaseSpec.ts b/src/spec/DynamicTreeBroadphaseSpec.ts index 11c14e0bf..c6d5dacc4 100644 --- a/src/spec/DynamicTreeBroadphaseSpec.ts +++ b/src/spec/DynamicTreeBroadphaseSpec.ts @@ -44,12 +44,12 @@ describe('A DynamicTree Broadphase', () => { it('can find collision pairs for actors that are potentially colliding', () => { var dt = new ex.DynamicTreeCollisionBroadphase(); - dt.register(actorA); - dt.register(actorB); - dt.register(actorC); + dt.track(actorA.body); + dt.track(actorB.body); + dt.track(actorC.body); // only should be 1 pair since C is very far away - var pairs = dt.findCollisionContacts([actorA, actorB, actorC], 100); + var pairs = dt.detect([actorA, actorB, actorC], 100); expect(pairs.length).toBe(1); From 82dcdb6553f67ead9bfb4619f20b2243cdb8489b Mon Sep 17 00:00:00 2001 From: eonarheim Date: Fri, 30 Sep 2016 18:08:25 -0500 Subject: [PATCH 2/8] Tests pass --- sandbox/web/src/game.js | 3 +-- sandbox/web/src/game.ts | 4 ++-- src/engine/Collision/CollisionContact.ts | 22 ++++++++++++---------- src/engine/Scene.ts | 22 ++++++++++------------ src/spec/ActorSpec.ts | 11 +++++------ 5 files changed, 30 insertions(+), 32 deletions(-) diff --git a/sandbox/web/src/game.js b/sandbox/web/src/game.js index 7dedbe79f..5aa27b0bd 100644 --- a/sandbox/web/src/game.js +++ b/sandbox/web/src/game.js @@ -189,7 +189,7 @@ player.on('update', function () { } player.vel.x = groundSpeed; } - if (game.input.keyboard.wasPressed(ex.Input.Keys.Up)) { + if (game.input.keyboard.isHeld(ex.Input.Keys.Up)) { if (!inAir) { player.vel.y = -jumpSpeed; inAir = true; @@ -300,7 +300,6 @@ player.on('postupdate', function (data) { data.target.acc.y = 800; // * data.delta/1000; } else { - data.target.acc.y = 0; } // Reset values because we don't know until we check the next update // inAir = true; diff --git a/sandbox/web/src/game.ts b/sandbox/web/src/game.ts index 376c09a6d..fa44253cf 100644 --- a/sandbox/web/src/game.ts +++ b/sandbox/web/src/game.ts @@ -231,7 +231,7 @@ player.on('update', () => { // TODO: When platform is moving in same direction, add its dx } - if (game.input.keyboard.wasPressed(ex.Input.Keys.Up)) { + if (game.input.keyboard.isHeld(ex.Input.Keys.Up)) { if (!inAir) { player.vel.y = -jumpSpeed; inAir = true; @@ -353,7 +353,7 @@ player.on('postupdate', (data?: ex.PostUpdateEvent) => { if (!isColliding) { data.target.acc.y = 800;// * data.delta/1000; } else { - data.target.acc.y = 0; + //data.target.acc.y = 0; } // Reset values because we don't know until we check the next update diff --git a/src/engine/Collision/CollisionContact.ts b/src/engine/Collision/CollisionContact.ts index aad98bab1..0be00f842 100644 --- a/src/engine/Collision/CollisionContact.ts +++ b/src/engine/Collision/CollisionContact.ts @@ -91,9 +91,9 @@ module ex { } else if (bodyA.vel.x >= 0 && bodyB.vel.x >= 0) { velX = Math.max(bodyA.vel.x, bodyB.vel.x); velX = bodyA.vel.x + bodyA.vel.x; - }else { + } else if (bodyB.collisionType === ex.CollisionType.Fixed) { // bodies are traveling in opposite directions - velX = 0; + velX = bodyB.vel.x; } bodyA.vel.x = velX; } @@ -101,15 +101,17 @@ module ex { if (this.mtv.y !== 0) { var velY = 0; - // both bodies are traveling in the same direction (negative or positive) - if (bodyA.vel.y <= 0 && bodyB.vel.y <= 0) { - velY = Math.max(bodyA.vel.y, bodyB.vel.y); - } else if (bodyA.vel.y >= 0 && bodyB.vel.y >= 0) { - velY = Math.min(bodyA.vel.y, bodyB.vel.y); - } else { + if (bodyB.collisionType === ex.CollisionType.Fixed) { // bodies are traveling in opposite directions - velY = 0; - } + velY = bodyB.vel.y; + } else { + // both bodies are traveling in the same direction (negative or positive) + if (bodyA.vel.y <= 0 && bodyB.vel.y <= 0) { + velY = Math.min(bodyA.vel.y, bodyB.vel.y); + } else if (bodyA.vel.y >= 0 && bodyB.vel.y >= 0) { + velY = Math.max(bodyA.vel.y, bodyB.vel.y); + } + } bodyA.vel.y = velY; } diff --git a/src/engine/Scene.ts b/src/engine/Scene.ts index d206825f4..07127c7cc 100644 --- a/src/engine/Scene.ts +++ b/src/engine/Scene.ts @@ -255,31 +255,29 @@ module ex { this.tileMaps[i].update(engine, delta); } - for (i = 0, len = this.children.length; i < len; i++) { - //this.children[i].integrate(delta); - } - // Cycle through actors updating actors for (i = 0, len = this.children.length; i < len; i++) { - this.children[i].update(engine, delta); + this.children[i].update(engine, delta); + } + + // Run the broadphase + if (this._broadphase) { + this._broadphase.update(this.children, delta); } - this._broadphase.update(this.children, delta); - var iter: number = 20; //Physics.collisionPasses; + // Run the narrowphase + var iter: number = Physics.collisionPasses; var collisionDelta = delta / iter; - while (iter > 0) { // Run collision resolution strategy if (this._broadphase && Physics.enabled) { this._broadphase.detect(this.children, collisionDelta); for (i = 0, len = this.children.length; i < len; i++) { - this.children[i].integrate(collisionDelta * .01); + this.children[i].integrate(collisionDelta * .001); } } iter--; - } - - + } // Remove actors from scene graph after being killed var actorIndex: number; diff --git a/src/spec/ActorSpec.ts b/src/spec/ActorSpec.ts index 91a24681b..0424846ad 100644 --- a/src/spec/ActorSpec.ts +++ b/src/spec/ActorSpec.ts @@ -542,7 +542,7 @@ describe('A game actor', () => { var active = new ex.Actor(0, -50, 100, 100); active.collisionType = ex.CollisionType.Active; active.vel.y = -100; - //active.acc.y = 1000; + ex.Physics.acc.setTo(0, 0); var fixed = new ex.Actor(-100, 50, 1000, 100); fixed.collisionType = ex.CollisionType.Fixed; @@ -556,16 +556,15 @@ describe('A game actor', () => { expect(fixed.pos.x).toBe(-100); expect(fixed.pos.y).toBe(50); - var iterations = 50; + var iterations = 1; // update many times for safety for (var i = 0; i < iterations; i++) { - scene.update(engine, 100); + scene.update(engine, 1000); } - var seconds = 100 / 1000; - + expect(ex.Physics.acc.y).toBe(0); expect(active.pos.x).toBeCloseTo(0, .0001); - expect(active.pos.y).toBeCloseTo(-100 * seconds * iterations, .0001); + expect(active.pos.y).toBeCloseTo(-100 * iterations + (-50) /* original y is -50 */, .0001); expect(fixed.pos.x).toBe(-100); expect(fixed.pos.y).toBe(50); From 4e4e87f9f301f377172f5af3e1f44f79fa06938e Mon Sep 17 00:00:00 2001 From: eonarheim Date: Fri, 30 Sep 2016 20:06:17 -0500 Subject: [PATCH 3/8] Fix pointerup optin bug, add tests --- src/engine/Actor.ts | 2 +- src/engine/Collision/CollisionContact.ts | 35 ++++++++++------- src/engine/Scene.ts | 2 + src/spec/ActorSpec.ts | 49 ++++++++++++++++++++++++ src/spec/Mocks.ts | 5 +++ 5 files changed, 79 insertions(+), 14 deletions(-) diff --git a/src/engine/Actor.ts b/src/engine/Actor.ts index 026879fdd..5d93c9aba 100644 --- a/src/engine/Actor.ts +++ b/src/engine/Actor.ts @@ -690,7 +690,7 @@ module ex { private _checkForPointerOptIn(eventName: string) { if (eventName && (eventName.toLowerCase() === 'pointerdown' || - eventName.toLowerCase() === 'pointerdown' || + eventName.toLowerCase() === 'pointerup' || eventName.toLowerCase() === 'pointermove')) { this.enableCapturePointer = true; if (eventName.toLowerCase() === 'pointermove') { diff --git a/src/engine/Collision/CollisionContact.ts b/src/engine/Collision/CollisionContact.ts index 0be00f842..6c094a255 100644 --- a/src/engine/Collision/CollisionContact.ts +++ b/src/engine/Collision/CollisionContact.ts @@ -85,15 +85,20 @@ module ex { if (this.mtv.x !== 0) { var velX = 0; // both bodies are traveling in the same direction (negative or positve) - if (bodyA.vel.x <= 0 && bodyB.vel.x <= 0) { + if (bodyA.vel.x < 0 && bodyB.vel.x < 0) { velX = Math.min(bodyA.vel.x, bodyB.vel.x); velX = bodyA.vel.x + bodyA.vel.x; - } else if (bodyA.vel.x >= 0 && bodyB.vel.x >= 0) { + } else if (bodyA.vel.x > 0 && bodyB.vel.x > 0) { velX = Math.max(bodyA.vel.x, bodyB.vel.x); velX = bodyA.vel.x + bodyA.vel.x; } else if (bodyB.collisionType === ex.CollisionType.Fixed) { // bodies are traveling in opposite directions - velX = bodyB.vel.x; + if (bodyA.pos.sub(bodyB.pos).dot(bodyA.vel) > 0) { + velX = bodyA.vel.x; + } else { + // bodyA is heading towards b + velX = bodyB.vel.x; + } } bodyA.vel.x = velX; } @@ -101,17 +106,21 @@ module ex { if (this.mtv.y !== 0) { var velY = 0; - if (bodyB.collisionType === ex.CollisionType.Fixed) { + + // both bodies are traveling in the same direction (negative or positive) + if (bodyA.vel.y < 0 && bodyB.vel.y < 0) { + velY = Math.min(bodyA.vel.y, bodyB.vel.y); + } else if (bodyA.vel.y > 0 && bodyB.vel.y > 0) { + velY = Math.max(bodyA.vel.y, bodyB.vel.y); + } else if (bodyB.collisionType === ex.CollisionType.Fixed) { // bodies are traveling in opposite directions - velY = bodyB.vel.y; - } else { - // both bodies are traveling in the same direction (negative or positive) - if (bodyA.vel.y <= 0 && bodyB.vel.y <= 0) { - velY = Math.min(bodyA.vel.y, bodyB.vel.y); - } else if (bodyA.vel.y >= 0 && bodyB.vel.y >= 0) { - velY = Math.max(bodyA.vel.y, bodyB.vel.y); - } - } + if (bodyA.pos.sub(bodyB.pos).dot(bodyA.vel) > 0) { + velY = bodyA.vel.y; + } else { + // bodyA is heading towards b + velY = bodyB.vel.y; + } + } bodyA.vel.y = velY; } diff --git a/src/engine/Scene.ts b/src/engine/Scene.ts index 07127c7cc..bc13e2d8b 100644 --- a/src/engine/Scene.ts +++ b/src/engine/Scene.ts @@ -273,6 +273,8 @@ module ex { if (this._broadphase && Physics.enabled) { this._broadphase.detect(this.children, collisionDelta); for (i = 0, len = this.children.length; i < len; i++) { + // helps move settle collisions + // todo there is a better way to do this this.children[i].integrate(collisionDelta * .001); } } diff --git a/src/spec/ActorSpec.ts b/src/spec/ActorSpec.ts index 0424846ad..28e6f25cd 100644 --- a/src/spec/ActorSpec.ts +++ b/src/spec/ActorSpec.ts @@ -669,4 +669,53 @@ describe('A game actor', () => { scene.update(engine, 100); scene.draw(engine.ctx, 100); }); + + it('opt into pointer capture when pointerup', () => { + var actor = new ex.Actor(0, 0, 100, 100); + expect(actor.enableCapturePointer).toBe(false, 'Actors start without pointer caputer enabled'); + actor.on('pointerup', () => { + // doesn't matter; + }); + expect(actor.enableCapturePointer).toBe(true, 'Actors should have pointer caputer enabled after pointerup'); + }); + + it('opt into pointer capture when pointerdown', () => { + var actor = new ex.Actor(0, 0, 100, 100); + expect(actor.enableCapturePointer).toBe(false, 'Actors start without pointer caputer enabled'); + actor.on('pointerdown', () => { + // doesn't matter; + }); + expect(actor.enableCapturePointer).toBe(true, 'Actors should have pointer caputer enabled after pointerdown'); + }); + + it('opt into pointer capture when pointermove', () => { + var actor = new ex.Actor(0, 0, 100, 100); + expect(actor.enableCapturePointer).toBe(false, 'Actors start without pointer caputer enabled'); + actor.on('pointermove', () => { + // doesn't matter; + }); + expect(actor.enableCapturePointer).toBe(true, 'Actors should have pointer caputer enabled after pointermove'); + expect(actor.capturePointer.captureMoveEvents).toBe(true, 'Actor should capture move events after pointermove'); + + }); + + it('only has pointer events happen once per frame', () => { + var actor = new ex.Actor(0, 0, 100, 100); + //actor.enableCapturePointer = true; + var numPointerUps = 0; + + spyOn(engine.input.pointers, 'propogate').and.callThrough(); + (engine.input.pointers)._pointerUp.push({x: 0, y: 0}); + + actor.on('pointerup', () => { + numPointerUps++; + }); + + scene.add(actor); + + scene.update(engine, 100); + + expect(numPointerUps).toBe(1, 'Pointer up should be triggered once'); + expect(engine.input.pointers.propogate.calls.count()).toEqual(1, 'Propogate should be called once'); + }); }); diff --git a/src/spec/Mocks.ts b/src/spec/Mocks.ts index adf3e5be5..8e7c15feb 100644 --- a/src/spec/Mocks.ts +++ b/src/spec/Mocks.ts @@ -90,6 +90,11 @@ module Mocks { camera: { getZoom: function () { return 1; } }, + input : { + keyboard: null, + pointers: new ex.Input.Pointers(this), + gamepads: null + }, worldToScreenCoordinates: ex.Engine.prototype.worldToScreenCoordinates, screenToWorldCoordinates: ex.Engine.prototype.screenToWorldCoordinates, addScene: ex.Engine.prototype.addScene, From 54feac8b2a0640b7228ddf60871541055347688d Mon Sep 17 00:00:00 2001 From: eonarheim Date: Fri, 30 Sep 2016 20:15:39 -0500 Subject: [PATCH 4/8] Fix odd behavior in box collison --- src/engine/Collision/CollisionContact.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/engine/Collision/CollisionContact.ts b/src/engine/Collision/CollisionContact.ts index 6c094a255..3f43d1c58 100644 --- a/src/engine/Collision/CollisionContact.ts +++ b/src/engine/Collision/CollisionContact.ts @@ -87,10 +87,8 @@ module ex { // both bodies are traveling in the same direction (negative or positve) if (bodyA.vel.x < 0 && bodyB.vel.x < 0) { velX = Math.min(bodyA.vel.x, bodyB.vel.x); - velX = bodyA.vel.x + bodyA.vel.x; } else if (bodyA.vel.x > 0 && bodyB.vel.x > 0) { velX = Math.max(bodyA.vel.x, bodyB.vel.x); - velX = bodyA.vel.x + bodyA.vel.x; } else if (bodyB.collisionType === ex.CollisionType.Fixed) { // bodies are traveling in opposite directions if (bodyA.pos.sub(bodyB.pos).dot(bodyA.vel) > 0) { From 97db31b6a9f585a21b5ce30b7517a6bddad6c70e Mon Sep 17 00:00:00 2001 From: eonarheim Date: Fri, 30 Sep 2016 20:28:49 -0500 Subject: [PATCH 5/8] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fe7e4947..4e99a0c84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). - `Actor.debugDraw` now works properly for child actors ([#505](https://github.com/excaliburjs/Excalibur/issues/505), [#645](https://github.com/excaliburjs/Excalibur/issues/645)) - Sprite culling was double scaling calculations ([#646](https://github.com/excaliburjs/Excalibur/issues/646)) - Fix negative zoom sprite culling ([#539](https://github.com/excaliburjs/Excalibur/issues/539)) +- Fix Actor updates happening more than once per frame, causing multiple pointer events to trigger ([#643](https://github.com/excaliburjs/Excalibur/issues/643)) +- Fix `Actor.on('pointerup')` capturePointer opt-in ## [0.7.0] - 2016-08-29 ### Breaking Changes From 1a1cea5d99d7107f159a20de63eaeb31e75c5f67 Mon Sep 17 00:00:00 2001 From: eonarheim Date: Sat, 1 Oct 2016 19:48:25 -0500 Subject: [PATCH 6/8] Implement suggestions --- CHANGELOG.md | 2 +- src/engine/Physics.ts | 5 +++++ src/engine/Scene.ts | 5 ++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e99a0c84..a5408ce5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Sprite culling was double scaling calculations ([#646](https://github.com/excaliburjs/Excalibur/issues/646)) - Fix negative zoom sprite culling ([#539](https://github.com/excaliburjs/Excalibur/issues/539)) - Fix Actor updates happening more than once per frame, causing multiple pointer events to trigger ([#643](https://github.com/excaliburjs/Excalibur/issues/643)) -- Fix `Actor.on('pointerup')` capturePointer opt-in +- Fix `Actor.on('pointerup')` capturePointer events opt-in on event handler. The opt-in was triggering correctly for handlerse on 'pointerdown' and 'pointermove', but not 'pointerup'. ## [0.7.0] - 2016-08-29 ### Breaking Changes diff --git a/src/engine/Physics.ts b/src/engine/Physics.ts index e665ae201..e04eb3f42 100644 --- a/src/engine/Physics.ts +++ b/src/engine/Physics.ts @@ -198,5 +198,10 @@ module ex { public static useRigidBodyPhysics(): void { ex.Physics.collisionResolutionStrategy = ex.CollisionResolutionStrategy.RigidBody; } + + /** + * Small value to help collision passes settle themselves after the narrowphase. + */ + public static collisionShift = .001; }; } \ No newline at end of file diff --git a/src/engine/Scene.ts b/src/engine/Scene.ts index bc13e2d8b..1bf750630 100644 --- a/src/engine/Scene.ts +++ b/src/engine/Scene.ts @@ -273,9 +273,8 @@ module ex { if (this._broadphase && Physics.enabled) { this._broadphase.detect(this.children, collisionDelta); for (i = 0, len = this.children.length; i < len; i++) { - // helps move settle collisions - // todo there is a better way to do this - this.children[i].integrate(collisionDelta * .001); + // helps move settle collisions, really there is a better way to do this + this.children[i].integrate(collisionDelta * ex.Physics.collisionShift); } } iter--; From b7cc2ea5305c7ddff920426522d5c361e5c5fcf9 Mon Sep 17 00:00:00 2001 From: eonarheim Date: Sat, 1 Oct 2016 19:50:04 -0500 Subject: [PATCH 7/8] Oops missed grunt --- GruntFile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GruntFile.js b/GruntFile.js index 9ba0fdfa1..fca730026 100644 --- a/GruntFile.js +++ b/GruntFile.js @@ -151,10 +151,10 @@ module.exports = function (grunt) { }, gitBuild: { - command: 'git clone -q https://github.com/excaliburjs/excalibur-dist build', + command: 'git clone https://github.com/excaliburjs/excalibur-dist build', options: { stdout: true, - failOnError: false + failOnError: true } } From 0e170181b8be3e97fca75e609203dcd15621e1ec Mon Sep 17 00:00:00 2001 From: eonarheim Date: Sat, 1 Oct 2016 19:51:50 -0500 Subject: [PATCH 8/8] Fix typo in handlerse :shipit: --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5408ce5c..d56614347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Sprite culling was double scaling calculations ([#646](https://github.com/excaliburjs/Excalibur/issues/646)) - Fix negative zoom sprite culling ([#539](https://github.com/excaliburjs/Excalibur/issues/539)) - Fix Actor updates happening more than once per frame, causing multiple pointer events to trigger ([#643](https://github.com/excaliburjs/Excalibur/issues/643)) -- Fix `Actor.on('pointerup')` capturePointer events opt-in on event handler. The opt-in was triggering correctly for handlerse on 'pointerdown' and 'pointermove', but not 'pointerup'. +- Fix `Actor.on('pointerup')` capturePointer events opt-in on event handler. The opt-in was triggering correctly for handlers on 'pointerdown' and 'pointermove', but not 'pointerup'. ## [0.7.0] - 2016-08-29 ### Breaking Changes