From e4893a9f2efc6d7d1b9a14f3420e0b311960a057 Mon Sep 17 00:00:00 2001 From: Erik Onarheim Date: Fri, 4 Oct 2024 21:43:58 -0500 Subject: [PATCH] fix: [#3205] Prevent degenerate PolygonColliders closes: #3205 --- CHANGELOG.md | 1 + src/engine/Collision/Colliders/PolygonCollider.ts | 6 +++++- src/spec/CollisionShapeSpec.ts | 15 ++++++++++++--- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c836953dc..cf837245a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,6 +104,7 @@ are doing mtv adjustments during precollision. ### Fixed +- Fixed issue where `ex.PolygonColliders` would get trapped in infinite loop for degenerate polygons (< 3 vertices) - Fixed issue where certain devices that support large numbers of texture slots exhaust the maximum number of if statements (complexity) in the shader. - Fixed issue where `ex.Label` where setting the opacity of caused a multiplicative opacity effect when actor opacity set - Fixed issue where the `ex.Loader` would have a low res logo on small configured resolution sizes diff --git a/src/engine/Collision/Colliders/PolygonCollider.ts b/src/engine/Collision/Colliders/PolygonCollider.ts index 41429fb51..91167064b 100644 --- a/src/engine/Collision/Colliders/PolygonCollider.ts +++ b/src/engine/Collision/Colliders/PolygonCollider.ts @@ -23,6 +23,7 @@ export interface PolygonColliderOptions { offset?: Vector; /** * Points in the polygon in order around the perimeter in local coordinates. These are relative from the body transform position. + * **Must be at least 3 points** */ points: Vector[]; @@ -61,6 +62,9 @@ export class PolygonCollider extends Collider { * Excalibur stores these in counter-clockwise order */ public set points(points: Vector[]) { + if (points.length < 3) { + throw new Error('PolygonCollider cannot be created with less that 3 points'); + } this._points = points; this._checkAndUpdateWinding(this._points); this._calculateNormals(); @@ -97,7 +101,7 @@ export class PolygonCollider extends Collider { this.offset = options.offset ?? Vector.Zero; this._transform.pos.x += this.offset.x; this._transform.pos.y += this.offset.y; - this.points = options.points ?? []; + this.points = options.points; if (!this.isConvex()) { if (!options.suppressConvexWarning) { diff --git a/src/spec/CollisionShapeSpec.ts b/src/spec/CollisionShapeSpec.ts index 9f69a1568..8183235a7 100644 --- a/src/spec/CollisionShapeSpec.ts +++ b/src/spec/CollisionShapeSpec.ts @@ -474,21 +474,30 @@ describe('Collision Shape', () => { it('can be constructed with empty args', () => { const poly = new ex.PolygonCollider({ - points: [ex.Vector.One] + points: [ex.Vector.One, ex.Vector.Zero, ex.Vector.Right] }); expect(poly).not.toBe(null); }); + it('does not allow degenerate polygons', () => { + const action = () => { + const poly = new ex.PolygonCollider({ + points: [ex.Vector.One] + }); + }; + expect(action).toThrowError('PolygonCollider cannot be created with less that 3 points'); + }); + it('can be cloned', () => { const actor1 = new ex.Actor({ x: 0, y: 0, width: 20, height: 20 }); - const poly = actor1.collider.usePolygonCollider([ex.Vector.One, ex.Vector.Half], new ex.Vector(20, 25)); + const poly = actor1.collider.usePolygonCollider([ex.Vector.One, ex.Vector.Half, ex.Vector.Right], new ex.Vector(20, 25)); const sut = poly.clone(); expect(sut).not.toBe(poly); expect(sut.offset).toBeVector(poly.offset); expect(sut.offset).not.toBe(poly.offset); - expect(sut.points.length).toBe(2); + expect(sut.points.length).toBe(3); }); it('can be constructed with points', () => {