From 8abc4d4e450d7c3110c5e5c6b331a07e3bc6090d Mon Sep 17 00:00:00 2001 From: Matheus Dall'Rosa Date: Thu, 7 Sep 2017 19:36:15 -0300 Subject: [PATCH 01/11] Add operations for 2d vectors --- .../geometry/vector_operations2d.js | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/algorithms/geometry/vector_operations2d.js diff --git a/src/algorithms/geometry/vector_operations2d.js b/src/algorithms/geometry/vector_operations2d.js new file mode 100644 index 0000000..b559472 --- /dev/null +++ b/src/algorithms/geometry/vector_operations2d.js @@ -0,0 +1,25 @@ +/** + * Performs the cross product between two vectors. + * @param A vector object, example: {x : 0,y : 0} + * @param A vector object, example: {x : 0,y : 0} + * @return The result of the cross product between u and v. + */ +const crossProduct = (u, v) => { + return u.x*v.y - u.y*v.x; +}; + +/** + * @param A point object, example: {x : 0,y : 0} + * @param A point object, example: {x : 0,y : 0} + * @param A point object, example: {x : 0,y : 0} + * @return Returns true if the point c is counter-clockwise with respect + * to the straight-line which contains the vector ab, otherwise returns false. + */ +const isClockwise = (a, b, c) => { + return crossProduct({x: b.x-a.x, y: b.y-a.y}, c) < 0; +}; + +module.exports = { + crossProduct: crossProduct, + isClockwise: isClockwise +}; From 0954867f403f3af647499dfa51057620df4c962c Mon Sep 17 00:00:00 2001 From: Matheus Dall'Rosa Date: Thu, 7 Sep 2017 19:36:57 -0300 Subject: [PATCH 02/11] Add tests for 2d vector operations --- .../geometry/vector_operations2d.js | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/test/algorithms/geometry/vector_operations2d.js diff --git a/src/test/algorithms/geometry/vector_operations2d.js b/src/test/algorithms/geometry/vector_operations2d.js new file mode 100644 index 0000000..b041f8a --- /dev/null +++ b/src/test/algorithms/geometry/vector_operations2d.js @@ -0,0 +1,34 @@ +const vectorOp = require('../../../algorithms/geometry/vector_operations2d'); +const assert = require('assert'); + +describe('VectorOperations', () => { + it('crossProduct: {-1,7}x{-5,8}', () => { + assert.equal(vectorOp. + crossProduct({x: -1, y: 7}, {x: -5, y: 8}), 27); + }); + + it('crossProduct: {45,45}x{-45,-45}', () => { + assert.equal(vectorOp. + crossProduct({x: 45, y: 45}, {x: -45, y: -45}), 0); + }); + + it('crossProduct: {1,1}x{1,0}', () => { + assert.equal(vectorOp. + crossProduct({x: 1, y: 1}, {x: 1, y: 0}), -1); + }); + + it('isClockwise: {0,0},{2,2},{0,2}', () => { + assert.equal(vectorOp. + isClockwise({x: 0, y: 0}, {x: 2, y: 2}, {x: 0, y: 2}), false); + }); + + it('isClockwise: {0,0},{2,2},{0,-2}', () => { + assert.equal(vectorOp. + isClockwise({x: 0, y: 0}, {x: 2, y: 2}, {x: 0, y: -2}), true); + }); + + it('isClockwise: {0,0},{2,2},{1,1}', () => { + assert.equal(vectorOp. + isClockwise({x: 0, y: 0}, {x: 2, y: 2}, {x: 1, y: 1}), false); + }); +}); From 1568761b1a90dbe63095a3c56ebe6fbf971c58d5 Mon Sep 17 00:00:00 2001 From: Matheus Dall'Rosa Date: Fri, 8 Sep 2017 00:34:26 -0300 Subject: [PATCH 03/11] Add more 2d vector operations and it's tests --- .../geometry/vector_operations2d.js | 50 +++++++++++++++++-- .../geometry/vector_operations2d.js | 5 ++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/algorithms/geometry/vector_operations2d.js b/src/algorithms/geometry/vector_operations2d.js index b559472..db97d46 100644 --- a/src/algorithms/geometry/vector_operations2d.js +++ b/src/algorithms/geometry/vector_operations2d.js @@ -1,3 +1,12 @@ +/** + * @param A point a, example: {x : 0,y : 0} + * @param A point b, example: {x : 0,y : 0} + * @return A vector ab, example: {x : 0,y : 0} + */ +const newVector = (a, b) => { + return {x: b.x-a.x, y: b.y-a.y}; +}; + /** * Performs the cross product between two vectors. * @param A vector object, example: {x : 0,y : 0} @@ -8,18 +17,53 @@ const crossProduct = (u, v) => { return u.x*v.y - u.y*v.x; }; +/** + * Calculates the area of the parallelogram that can be + * generated by the vectors ab and ac. + * + * @param A point a, example: {x : 0,y : 0} + * @param A point b, example: {x : 0,y : 0} + * @param A point c, example: {x : 0,y : 0} + * @return A vector ab, example: {x : 0,y : 0} + * Note: Given that the area of the parallelogram is equal + * to the length of the vector w = (ab)x(ac) times -1, if the + * z coordinate of w is negative. With the right-hand rule + * we can deduce that if the area is negative, then + * the vector ab followed by the vector ac do not performs a + * left-turn. + */ +const parallelogramArea = (a, b, c) => { + return crossProduct(newVector(a,b), newVector(a,c)); +}; + /** * @param A point object, example: {x : 0,y : 0} * @param A point object, example: {x : 0,y : 0} * @param A point object, example: {x : 0,y : 0} - * @return Returns true if the point c is counter-clockwise with respect + * @return Returns true if the point c is clockwise with respect * to the straight-line which contains the vector ab, otherwise returns false. + * Note: This rule is given by the Right-hand rule. */ const isClockwise = (a, b, c) => { - return crossProduct({x: b.x-a.x, y: b.y-a.y}, c) < 0; + return parallelogramArea(a, b, c) < 0; +}; + +/** + * @param A point object, example: {x : 0,y : 0} + * @param A point object, example: {x : 0,y : 0} + * @param A point object, example: {x : 0,y : 0} + * @return Returns true if the point c is counter-clockwise with respect + * to the straight-line which contains the vector ab, otherwise returns false. + * Note: This rule is given by the Right-hand rule. + */ +const isCounterClockwise = (a, b, c) => { + return parallelogramArea(a, b, c) > 0; }; module.exports = { + newVector: newVector, crossProduct: crossProduct, - isClockwise: isClockwise + parallelogramArea: parallelogramArea, + isClockwise: isClockwise, + isCounterClockwise: isCounterClockwise }; diff --git a/src/test/algorithms/geometry/vector_operations2d.js b/src/test/algorithms/geometry/vector_operations2d.js index b041f8a..8bb0051 100644 --- a/src/test/algorithms/geometry/vector_operations2d.js +++ b/src/test/algorithms/geometry/vector_operations2d.js @@ -2,6 +2,11 @@ const vectorOp = require('../../../algorithms/geometry/vector_operations2d'); const assert = require('assert'); describe('VectorOperations', () => { + it('newVector: {0,4}->{1,2}', () => { + assert.deepEqual(vectorOp. + newVector({x: 0, y: 4}, {x: 1, y: 2}), {x: 1, y: -2}); + }); + it('crossProduct: {-1,7}x{-5,8}', () => { assert.equal(vectorOp. crossProduct({x: -1, y: 7}, {x: -5, y: 8}), 27); From e33f30b1a9cef92d4bd55e62047a2a0e3d66c5bf Mon Sep 17 00:00:00 2001 From: Matheus Dall'Rosa Date: Fri, 8 Sep 2017 00:35:47 -0300 Subject: [PATCH 04/11] Making the git pre-commit happy --- src/algorithms/geometry/vector_operations2d.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/algorithms/geometry/vector_operations2d.js b/src/algorithms/geometry/vector_operations2d.js index db97d46..afc83c7 100644 --- a/src/algorithms/geometry/vector_operations2d.js +++ b/src/algorithms/geometry/vector_operations2d.js @@ -33,7 +33,7 @@ const crossProduct = (u, v) => { * left-turn. */ const parallelogramArea = (a, b, c) => { - return crossProduct(newVector(a,b), newVector(a,c)); + return crossProduct(newVector(a, b), newVector(a, c)); }; /** From e8e271aa99f45deffbc3f1f0dddd4121e89deaa8 Mon Sep 17 00:00:00 2001 From: Matheus Dall'Rosa Date: Fri, 8 Sep 2017 03:42:00 -0300 Subject: [PATCH 05/11] Add length function --- src/algorithms/geometry/vector_operations2d.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/algorithms/geometry/vector_operations2d.js b/src/algorithms/geometry/vector_operations2d.js index afc83c7..54c2bce 100644 --- a/src/algorithms/geometry/vector_operations2d.js +++ b/src/algorithms/geometry/vector_operations2d.js @@ -7,6 +7,14 @@ const newVector = (a, b) => { return {x: b.x-a.x, y: b.y-a.y}; }; +/** + * @param A vector v, example: {x : 0,y : 0} + * @return The length of v. + */ +const length = v => { + return v.x*v.x + v.y*v.y; +}; + /** * Performs the cross product between two vectors. * @param A vector object, example: {x : 0,y : 0} @@ -62,6 +70,7 @@ const isCounterClockwise = (a, b, c) => { module.exports = { newVector: newVector, + length: length, crossProduct: crossProduct, parallelogramArea: parallelogramArea, isClockwise: isClockwise, From 417b626ff35ee171ecc10d4297f237d59d9f0e9a Mon Sep 17 00:00:00 2001 From: Matheus Dall'Rosa Date: Fri, 8 Sep 2017 03:46:29 -0300 Subject: [PATCH 06/11] Add Graham's scan alorithm, it need tests --- src/algorithms/geometry/graham_scan.js | 51 ++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/algorithms/geometry/graham_scan.js diff --git a/src/algorithms/geometry/graham_scan.js b/src/algorithms/geometry/graham_scan.js new file mode 100644 index 0000000..0b7c97f --- /dev/null +++ b/src/algorithms/geometry/graham_scan.js @@ -0,0 +1,51 @@ +const vectorOp = require('./vector_operations2d.js'); + +/** + * Given an array P with N points on a two dimensional space, + * the function grahamScan calculates the convex hull of P + * in O(N * log(N)) time and using O(N) space. + * Implemented algorithm: Graham's Scan. + * + * @param An array P with N points, each point should be a literal, + * example: {x:0,y:0}. + * @return An array P' with N' points which belongs to the convex hull of P. + * + * Node: If three points are collinear and all of the three points + * belongs to the convex hull, then the three points will be present on P'. + */ +const grahamScan = function(P) { + if (P.length <= 3) { + return P; + } + preprocessing(P); + const convexHull = [P[0], P[1]]; + for (let i = 2; i < P.length; i++) { + let j = convexHull.length; + while (j >= 2 && vectorOp. + isClockwise(convexHull[j-2], convexHull[j-1], P[i])) { + convexHull.pop(); + j--; + } + convexHull.push(P[i]); + } + return convexHull; +}; + +const preprocessing = function(P) { + let pivot = P[0]; + for (let i = 1; i < P.length; i++) { + if (pivot.y > P[i].y || (pivot.y === P[i].y && pivot.x > P[i].x)) { + pivot = P[i]; + } + } + P.sort(function cmp(a, b) { + const area = vectorOp.parallelogramArea(pivot, a, b); + if (Math.abs(area) < 1e-6) { + return vectorOp.length(vectorOp.newVector(pivot, a)) + - vectorOp.length(vectorOp.newVector(pivot, b)); + } + return -area; + }); +}; + +module.exports = grahamScan; From bfc6e051a7a1622262d5ef827d5c7d440d82804b Mon Sep 17 00:00:00 2001 From: Matheus Dall'Rosa Date: Fri, 8 Sep 2017 14:47:48 -0300 Subject: [PATCH 07/11] Changes the approach to handle collinear points --- src/algorithms/geometry/graham_scan.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/algorithms/geometry/graham_scan.js b/src/algorithms/geometry/graham_scan.js index 0b7c97f..1775e52 100644 --- a/src/algorithms/geometry/graham_scan.js +++ b/src/algorithms/geometry/graham_scan.js @@ -10,19 +10,22 @@ const vectorOp = require('./vector_operations2d.js'); * example: {x:0,y:0}. * @return An array P' with N' points which belongs to the convex hull of P. * - * Node: If three points are collinear and all of the three points - * belongs to the convex hull, then the three points will be present on P'. + * Note: If exists a subset S of P that contains only collinear points + * which are present on the convex hull of P, then only the pair with + * the largest distance will be present on P'. */ const grahamScan = function(P) { if (P.length <= 3) { return P; } preprocessing(P); + console.log(P); const convexHull = [P[0], P[1]]; for (let i = 2; i < P.length; i++) { let j = convexHull.length; - while (j >= 2 && vectorOp. - isClockwise(convexHull[j-2], convexHull[j-1], P[i])) { + while (j >= 2 && + vectorOp. + parallelogramArea(convexHull[j-2], convexHull[j-1], P[i]) <= 0) { convexHull.pop(); j--; } @@ -31,6 +34,14 @@ const grahamScan = function(P) { return convexHull; }; +/** + * @param An array P with N points, each point should be a literal, + * @return An array P' with N' points which belongs to the convex hull of P. + * + * Note: After the preprocessing the points are ordered by the angle + * between the vectors pivot -> Pi and (0,1). Where Pi is a point on P. + * On the counter-clockwise direction. + */ const preprocessing = function(P) { let pivot = P[0]; for (let i = 1; i < P.length; i++) { @@ -38,6 +49,7 @@ const preprocessing = function(P) { pivot = P[i]; } } + P.sort(function cmp(a, b) { const area = vectorOp.parallelogramArea(pivot, a, b); if (Math.abs(area) < 1e-6) { From f991768bb434e54a9ea8e07d974cca10618bb855 Mon Sep 17 00:00:00 2001 From: Matheus Dall'Rosa Date: Fri, 8 Sep 2017 14:54:55 -0300 Subject: [PATCH 08/11] Graham's Scan tests --- src/test/algorithms/geometry/graham_scan.js | 78 +++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/test/algorithms/geometry/graham_scan.js diff --git a/src/test/algorithms/geometry/graham_scan.js b/src/test/algorithms/geometry/graham_scan.js new file mode 100644 index 0000000..2f3534a --- /dev/null +++ b/src/test/algorithms/geometry/graham_scan.js @@ -0,0 +1,78 @@ +const grahamScan = require('../../../algorithms/geometry/graham_scan'); +const assert = require('assert'); + +describe('Graham s Scan algorithm', () => { + /* we have to ensure the order before using deepEqual. */ + let pointComparison; + before(() => { + pointComparison = function(a, b) { + return (a.x != b.x) ? a.x < b.x : a.y < b.y; + }; + }); + + it('ConvexHull of set 0', () => { + const P = [{x: 3, y: 4}, {x: 5, y: 2}, {x: 7, y: 4}, + {x: 3, y: 5}, {x: 4.7, y: 3.66}, {x: 5.4, y: 4.52}, + {x: 5, y: 6}, {x: 6, y: 6}, {x: 5.62, y: 3.5}]; + const convexHullOfP = [{x: 3, y: 4}, {x: 5, y: 2}, {x: 7, y: 4}, + {x: 3, y: 5}, {x: 5, y: 6}, {x: 6, y: 6}]; + convexHullOfP.sort(pointComparison); + const computedConvexHull = grahamScan(P); + computedConvexHull.sort(pointComparison); + assert.deepEqual(computedConvexHull, convexHullOfP); + }); + + it('ConvexHull of set 1', () => { + const P = [{x: 2.8, y: 3.6}, {x: 4.2, y: 3.7}, {x: 3.6, y: 4.3}, + {x: 2.2, y: 5.2}, {x: 3.6, y: 5.8}, {x: 4.6, y: 5.2}, + {x: 4.8, y: 4.5}, {x: 5.6, y: 3.4}, {x: 4, y: 3}, + {x: 3, y: 3.1}, {x: 1.9, y: 2.7}, {x: 1.6, y: 3.7}, + {x: 2.3, y: 4.2}, {x: 3, y: 2.4}]; + const convexHullOfP = [{x: 2.2, y: 5.2}, {x: 3.6, y: 5.8}, + {x: 4.6, y: 5.2}, {x: 5.6, y: 3.4}, + {x: 1.9, y: 2.7}, {x: 1.6, y: 3.7}, + {x: 3, y: 2.4}]; + convexHullOfP.sort(pointComparison); + const computedConvexHull = grahamScan(P); + computedConvexHull.sort(pointComparison); + assert.deepEqual(computedConvexHull, convexHullOfP); + }); + + it('ConvexHull of set 2', () => { + const P = [{x: 1.7, y: 2.4}, {x: 2.8, y: 2}, {x: 1.3, y: 1.6}, + {x: 2.4, y: 1.4}, {x: 2.4, y: 0.6}, {x: 1.6, y: 0.7}, + {x: 4, y: 1}, {x: 4.9, y: 1.3}, {x: 3.5, y: 2.1}, + {x: 3.7, y: 2.8}, {x: 2.6, y: 3.1}, {x: 0.7, y: 2.8}, + {x: 0.9, y: 1.6}]; + const convexHullOfP = [{x: 2.4, y: 0.6}, {x: 1.6, y: 0.7}, + {x: 4, y: 1}, {x: 4.9, y: 1.3}, + {x: 3.7, y: 2.8}, {x: 2.6, y: 3.1}, + {x: 0.7, y: 2.8}, {x: 0.9, y: 1.6}]; + convexHullOfP.sort(pointComparison); + const computedConvexHull = grahamScan(P); + computedConvexHull.sort(pointComparison); + assert.deepEqual(computedConvexHull, convexHullOfP); + }); + + it('ConvexHull of set 3', () => { + const P = [{x: 2, y: 1}, {x: 3, y: 2}, {x: 4, y: 3}, {x: 5, y: 4}, + {x: 3, y: 3}, {x: 3, y: 4}, {x: 2.4, y: 3.5}, {x: 3.6, y: 3.5}, + {x: 2, y: 4}, {x: 2, y: 3}, {x: 2, y: 2}]; + const convexHullOfP = [{x: 2, y: 1}, {x: 5, y: 4}, {x: 2, y: 4}]; + convexHullOfP.sort(pointComparison); + const computedConvexHull = grahamScan(P); + computedConvexHull.sort(pointComparison); + assert.deepEqual(computedConvexHull, convexHullOfP); + }); + + it('ConvexHull of set 4', () => { + const P = [{x: 2, y: 1}, {x: 3, y: 2}, {x: 4, y: 3}, {x: 4, y: 4}, + {x: 2, y: 5}, {x: 2, y: 6}, {x: 2, y: 4}, {x: 5, y: 4}, + {x: 3, y: 5}, {x: 2, y: 3}, {x: 2, y: 2}]; + const convexHullOfP = [{x: 2, y: 1}, {x: 5, y: 4}, {x: 2, y: 6}]; + convexHullOfP.sort(pointComparison); + const computedConvexHull = grahamScan(P); + computedConvexHull.sort(pointComparison); + assert.deepEqual(computedConvexHull, convexHullOfP); + }); +}); From 8a78249a110b3a2693f63ccdf73c6a57996cd203 Mon Sep 17 00:00:00 2001 From: Matheus Dall'Rosa Date: Sun, 10 Sep 2017 18:49:31 -0300 Subject: [PATCH 09/11] Remove debugging info --- src/algorithms/geometry/graham_scan.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/algorithms/geometry/graham_scan.js b/src/algorithms/geometry/graham_scan.js index 1775e52..04d6116 100644 --- a/src/algorithms/geometry/graham_scan.js +++ b/src/algorithms/geometry/graham_scan.js @@ -19,7 +19,6 @@ const grahamScan = function(P) { return P; } preprocessing(P); - console.log(P); const convexHull = [P[0], P[1]]; for (let i = 2; i < P.length; i++) { let j = convexHull.length; From 400b9ca61c7ce49d11d9690c1e071507d74066e8 Mon Sep 17 00:00:00 2001 From: Matheus Dall'Rosa Date: Sun, 10 Sep 2017 22:14:51 -0300 Subject: [PATCH 10/11] Change paralellogramArea <= 0 to ~isCounterClockwise --- src/algorithms/geometry/graham_scan.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/algorithms/geometry/graham_scan.js b/src/algorithms/geometry/graham_scan.js index 04d6116..f47aa81 100644 --- a/src/algorithms/geometry/graham_scan.js +++ b/src/algorithms/geometry/graham_scan.js @@ -23,8 +23,8 @@ const grahamScan = function(P) { for (let i = 2; i < P.length; i++) { let j = convexHull.length; while (j >= 2 && - vectorOp. - parallelogramArea(convexHull[j-2], convexHull[j-1], P[i]) <= 0) { + !vectorOp. + isCounterClockwise(convexHull[j-2], convexHull[j-1], P[i])) { convexHull.pop(); j--; } From dc7cf4d6e45cabdfdf90ffcfd17f9df9df51cf2b Mon Sep 17 00:00:00 2001 From: Matheus Dall'Rosa Date: Mon, 11 Sep 2017 14:27:52 -0300 Subject: [PATCH 11/11] Move to arrow function and correct the variable names --- src/algorithms/geometry/graham_scan.js | 50 +++++++++++++------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/algorithms/geometry/graham_scan.js b/src/algorithms/geometry/graham_scan.js index f47aa81..e5f6308 100644 --- a/src/algorithms/geometry/graham_scan.js +++ b/src/algorithms/geometry/graham_scan.js @@ -1,55 +1,55 @@ const vectorOp = require('./vector_operations2d.js'); /** - * Given an array P with N points on a two dimensional space, - * the function grahamScan calculates the convex hull of P + * Given an array p with N points on a two dimensional space, + * the function grahamScan calculates the convex hull of p * in O(N * log(N)) time and using O(N) space. * Implemented algorithm: Graham's Scan. * - * @param An array P with N points, each point should be a literal, + * @param An array p with N points, each point should be a literal, * example: {x:0,y:0}. - * @return An array P' with N' points which belongs to the convex hull of P. + * @return An array p' with N' points which belongs to the convex hull of p. * - * Note: If exists a subset S of P that contains only collinear points - * which are present on the convex hull of P, then only the pair with - * the largest distance will be present on P'. + * Note: If exists a subset S of p that contains only collinear points + * which are present on the convex hull of p, then only the pair with + * the largest distance will be present on p'. */ -const grahamScan = function(P) { - if (P.length <= 3) { - return P; +const grahamScan = p => { + if (p.length <= 3) { + return p; } - preprocessing(P); - const convexHull = [P[0], P[1]]; - for (let i = 2; i < P.length; i++) { + preprocessing(p); + const convexHull = [p[0], p[1]]; + for (let i = 2; i < p.length; i++) { let j = convexHull.length; while (j >= 2 && !vectorOp. - isCounterClockwise(convexHull[j-2], convexHull[j-1], P[i])) { + isCounterClockwise(convexHull[j-2], convexHull[j-1], p[i])) { convexHull.pop(); j--; } - convexHull.push(P[i]); + convexHull.push(p[i]); } return convexHull; }; /** - * @param An array P with N points, each point should be a literal, - * @return An array P' with N' points which belongs to the convex hull of P. + * @param An array p with N points, each point should be a literal, + * example: {x:0,y:0}. * - * Note: After the preprocessing the points are ordered by the angle - * between the vectors pivot -> Pi and (0,1). Where Pi is a point on P. + * Note: After the preprocessing the points will be ordered by the angle + * between the vectors pivot -> pi and (0,1). Where pi is a point on p. * On the counter-clockwise direction. */ -const preprocessing = function(P) { - let pivot = P[0]; - for (let i = 1; i < P.length; i++) { - if (pivot.y > P[i].y || (pivot.y === P[i].y && pivot.x > P[i].x)) { - pivot = P[i]; +const preprocessing = p => { + let pivot = p[0]; + for (let i = 1; i < p.length; i++) { + if (pivot.y > p[i].y || (pivot.y === p[i].y && pivot.x > p[i].x)) { + pivot = p[i]; } } - P.sort(function cmp(a, b) { + p.sort((a, b) => { const area = vectorOp.parallelogramArea(pivot, a, b); if (Math.abs(area) < 1e-6) { return vectorOp.length(vectorOp.newVector(pivot, a))