From 48e9bb91f05854ffb811e4f5f8a3420f8684236a Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Mon, 19 Oct 2020 00:21:32 +0100 Subject: [PATCH] Update AABB algorithms (#164) --- CHANGELOG.md | 2 + docs/reference/classes/aabb.md | 2 +- docs/reference/classes/aabbalgorithm.md | 16 ----- src/shape/aabb.test.ts | 56 +++++++++--------- src/shape/aabb.ts | 37 +++++------- .../collision/algorithm/aabb_algorithm.ts | 58 ++++++++++++------- 6 files changed, 83 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a02ea38..61aee6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ object creation. must be implemented on a per `Component` basis by overriding this function. - The game loop now ensures all game logic messages are processed using `DispatchUntilEmpty` before executing any render logic. +- `AABBAlgorithm` more efficient algorithm involving sweeping horizontal then vertical. +- `AABB` method `FarthestPointInDirection` faster and using less `Vector` objects. ## [v0.9.0] - 2020-09-05 ### Added diff --git a/docs/reference/classes/aabb.md b/docs/reference/classes/aabb.md index 4734e30..c17e174 100644 --- a/docs/reference/classes/aabb.md +++ b/docs/reference/classes/aabb.md @@ -45,7 +45,7 @@ requiring less calculations than a fully defined polygon. Name | Type | Default | ------ | ------ | ------ | `size` | [Vector](vector.md) | - | -`center` | [Vector](vector.md) | Vector.New(0,0) | +`center` | [Vector](vector.md) | Vector.New(0, 0) | **Returns:** *[AABB](aabb.md)* diff --git a/docs/reference/classes/aabbalgorithm.md b/docs/reference/classes/aabbalgorithm.md index ce5dd72..ce816df 100644 --- a/docs/reference/classes/aabbalgorithm.md +++ b/docs/reference/classes/aabbalgorithm.md @@ -18,7 +18,6 @@ This algorithm sacrifices some collision accuracy for performance. ### Methods * [CalculateCollisions](aabbalgorithm.md#calculatecollisions) -* [aabb](aabbalgorithm.md#private-aabb) ## Methods @@ -35,18 +34,3 @@ Name | Type | `shapes` | [IShape](../interfaces/ishape.md)[] | **Returns:** *[CollisionInfo](collisioninfo.md)[]* - -___ - -### `Private` aabb - -▸ **aabb**(`a`: [IShape](../interfaces/ishape.md), `b`: [IShape](../interfaces/ishape.md)): *[CollisionInfo](collisioninfo.md) | undefined* - -**Parameters:** - -Name | Type | ------- | ------ | -`a` | [IShape](../interfaces/ishape.md) | -`b` | [IShape](../interfaces/ishape.md) | - -**Returns:** *[CollisionInfo](collisioninfo.md) | undefined* diff --git a/src/shape/aabb.test.ts b/src/shape/aabb.test.ts index 4228531..2030d1c 100644 --- a/src/shape/aabb.test.ts +++ b/src/shape/aabb.test.ts @@ -22,27 +22,27 @@ describe("AABB - FarthestPointInDirection", () => { type TestTuple = [string, Vector, AABB, Vector]; test.each([ [ - "Square - up - top left point", - new Vector(-1.5,1.5), - new AABB(new Vector(3,3)), + "Square - up - top right point", + new Vector(1.5, 1.5), + new AABB(new Vector(3, 3)), new Vector(0, 1) ], [ - "Square - down - bottom left point", - new Vector(-1.5,-1.5), - new AABB(new Vector(3,3)), + "Square - down - bottom right point", + new Vector(1.5, -1.5), + new AABB(new Vector(3, 3)), new Vector(0, -1) ], [ "Square - left - top left point", - new Vector(-1.5,1.5), - new AABB(new Vector(3,3)), + new Vector(-1.5, 1.5), + new AABB(new Vector(3, 3)), new Vector(-1, 0) ], [ "Square - right - top right point", - new Vector(1.5,1.5), - new AABB(new Vector(3,3)), + new Vector(1.5, 1.5), + new AABB(new Vector(3, 3)), new Vector(1, 0) ], ])("%p", (description: string, expected: Vector, aabb: AABB, direction: Vector) => { @@ -58,20 +58,20 @@ describe("AABB - Transform", () => { test.each([ [ "Square, move up by 2", - new AABB(new Vector(2,2), new Vector(0, 2)), - new AABB(new Vector(2,2)), + new AABB(new Vector(2, 2), new Vector(0, 2)), + new AABB(new Vector(2, 2)), new Transform(new Vector(0, 2)) ], [ "Rectangle around point, move up by 5, left 8", - new AABB(new Vector(2,6), new Vector(-5, 11)), - new AABB(new Vector(2,6), new Vector(3,6)), + new AABB(new Vector(2, 6), new Vector(-5, 11)), + new AABB(new Vector(2, 6), new Vector(3, 6)), new Transform(new Vector(-8, 5)) ], [ "Rectangle around point, move up by 5, left 8, scale by 2 width and 4 height, ignore rotation", - new AABB(new Vector(4,12), new Vector(-5, 11)), - new AABB(new Vector(2,3), new Vector(3,6)), + new AABB(new Vector(4, 12), new Vector(-5, 11)), + new AABB(new Vector(2, 3), new Vector(3, 6)), new Transform(new Vector(-8, 5), new Vector(2, 4), 3) ], ])("%p", (description: string, expected: AABB, aabb: AABB, transform: Transform) => { @@ -86,37 +86,37 @@ describe("AABB - PointInside", () => { "Square around origin, point inside", true, new AABB(new Vector(1, 1)), - new Vector(0.2,0.2) + new Vector(0.2, 0.2) ], [ "Square around origin, point left", false, new AABB(new Vector(2, 2)), - new Vector(-3,0) + new Vector(-3, 0) ], [ "Square around origin, point right", false, new AABB(new Vector(2, 2)), - new Vector(3,0) + new Vector(3, 0) ], [ "Rectangle around origin, point outside", false, new AABB(new Vector(2, 3)), - new Vector(5,3) + new Vector(5, 3) ], [ "Rectangle around arbitrary point, point within", true, - new AABB(new Vector(2, 3), new Vector(5,3)), - new Vector(5,4) + new AABB(new Vector(2, 3), new Vector(5, 3)), + new Vector(5, 4) ], [ "Rectangle around arbitrary point, point outside", false, - new AABB(new Vector(2, 3), new Vector(5,3)), - new Vector(2,1) + new AABB(new Vector(2, 3), new Vector(5, 3)), + new Vector(2, 1) ], ])("%p", (description: string, expected: boolean, aabb: AABB, point: Vector) => { @@ -130,13 +130,13 @@ describe("AABB - Center", () => { test.each([ [ "Rectangle around origin", - new Vector(0,0), - new AABB(new Vector(2, 6), new Vector(0,0)) + new Vector(0, 0), + new AABB(new Vector(2, 6), new Vector(0, 0)) ], [ "Rectangle around point", - new Vector(5,-3), - new AABB(new Vector(2, 3), new Vector(5,-3)) + new Vector(5, -3), + new AABB(new Vector(2, 3), new Vector(5, -3)) ], ])("%p", (description: string, expected: Vector, aabb: AABB) => { expect(aabb.Center()).toEqual(expected); diff --git a/src/shape/aabb.ts b/src/shape/aabb.ts index 07550f8..5e46554 100644 --- a/src/shape/aabb.ts +++ b/src/shape/aabb.ts @@ -29,7 +29,7 @@ class AABB implements IShape { public center: Vector; public size: Vector; - constructor(size: Vector, center: Vector = Vector.New(0,0)) { + constructor(size: Vector, center: Vector = Vector.New(0, 0)) { this.center = center; this.size = size; } @@ -46,30 +46,21 @@ class AABB implements IShape { } public FarthestPointInDirection(direction: Vector): Vector { - const left = this.center.x - this.size.x / 2; - const right = this.center.x + this.size.x / 2; - const top = this.center.y + this.size.y / 2; - const bottom = this.center.y - this.size.y / 2; - - const points = [ - Vector.New(left, top), - Vector.New(right, top), - Vector.New(left, bottom), - Vector.New(right, bottom) - ]; - - let farthestDistance = points[0].Dot(direction); - let farthestPoint = points[0]; - - for (let i = 1; i < points.length; i++) { - const distanceInDirection = points[i].Dot(direction); - if (distanceInDirection > farthestDistance) { - farthestPoint = points[i]; - farthestDistance = distanceInDirection; + if (direction.x >= 0) { + const right = this.center.x + this.size.x / 2; + if (direction.y >= 0) { + return new Vector(right, this.center.y + this.size.y / 2); // top right + } else { + return new Vector(right, this.center.y - this.size.y / 2); // bottom right + } + } else { + const left = this.center.x - this.size.x / 2; + if (direction.y >= 0) { + return new Vector(left, this.center.y + this.size.y / 2); // top left + } else { + return new Vector(left, this.center.y - this.size.y / 2); // bottom left } } - - return farthestPoint; } public PointInside(point: Vector): boolean { diff --git a/src/standard/collision/algorithm/aabb_algorithm.ts b/src/standard/collision/algorithm/aabb_algorithm.ts index da618a2..9c76649 100644 --- a/src/standard/collision/algorithm/aabb_algorithm.ts +++ b/src/standard/collision/algorithm/aabb_algorithm.ts @@ -26,34 +26,52 @@ import Vector from "../../../geometry/vector"; */ class AABBAlgorithm implements ICollisionAlgorithm { public CalculateCollisions(shapes: IShape[]): CollisionInfo[] { - const collisions: CollisionInfo[] = []; - // Iterate over collision pairs + + const horizontalOverlap: [IShape, IShape][] = []; + + const leftDir = Vector.New(-1, 0); + const rightDir = Vector.New(1, 0); + for (let i = 0; i < shapes.length; i++) { + const a = shapes[i]; + const aLeft = a.FarthestPointInDirection(leftDir); + const aRight = a.FarthestPointInDirection(rightDir); for (let j = i + 1; j < shapes.length; j++) { - const a = shapes[i]; const b = shapes[j]; - // Detect collision - const collision = this.aabb(a,b); - if (collision !== undefined) { - collisions.push(collision); + const bLeft = b.FarthestPointInDirection(leftDir); + const bRight = b.FarthestPointInDirection(rightDir); + if (aLeft.x < bRight.x && + aRight.x > bLeft.x) { + horizontalOverlap.push([a, b]); } } } - return collisions; - } - private aabb(a: IShape, b: IShape): CollisionInfo | undefined { - const aTopLeft = a.FarthestPointInDirection(Vector.New(-1, 1)); - const aBottomRight = a.FarthestPointInDirection(Vector.New(1, -1)); - const bTopLeft = b.FarthestPointInDirection(Vector.New(-1, 1)); - const bBottomRight = b.FarthestPointInDirection(Vector.New(1, -1)); - if (aTopLeft.x < bBottomRight.x && - aBottomRight.x > bTopLeft.x && - aBottomRight.y < bTopLeft.y && - aTopLeft.y > bBottomRight.y) { - return new CollisionInfo(a, b); + leftDir.Free(); + rightDir.Free(); + + const collisions: CollisionInfo[] = []; + + const upDir = Vector.New(0, 1); + const downDir = Vector.New(0, -1); + + for (let i = 0; i < horizontalOverlap.length; i++) { + const a = horizontalOverlap[i][0]; + const b = horizontalOverlap[i][1]; + const aUp = a.FarthestPointInDirection(upDir); + const aDown = a.FarthestPointInDirection(downDir); + const bUp = b.FarthestPointInDirection(upDir); + const bDown = b.FarthestPointInDirection(downDir); + if (aDown.y < bUp.y && + aUp.y > bDown.y) { + collisions.push(new CollisionInfo(a, b)); + } } - return; + + upDir.Free(); + downDir.Free(); + + return collisions; } }