Skip to content

Instantly share code, notes, and snippets.

@Azeirah
Last active December 30, 2023 03:40
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Azeirah/75d44a6803b88e37ea8703a040e89353 to your computer and use it in GitHub Desktop.
Save Azeirah/75d44a6803b88e37ea8703a040e89353 to your computer and use it in GitHub Desktop.
Generate simple polygons
/**
* Calculates if a given point `point` lies above a given line `p1p2`
* THIS FUNCTION DOES NOT RETURN BOOLEANS. It has three different return values, above = 1, below = -1, on the line = 0.
* @param {[x, y]} point The point to test for
* @param {[x, y]} p1 The starting point of a line
* @param {[x, y]} p2 The ending point of a line
* @return {number} 1 = above, -1 = below, 0 = exactly on the line
*/
function pointAboveLine(point, p1, p2) {
// first, horizontally sort the points in the line
let first;
let second;
if (p1[0] > p2[0]) {
first = p2;
second = p1;
} else {
first = p1;
second = p2;
}
let v1 = [second[0] - first[0], second[1] - first[1]];
let v2 = [second[0] - point[0], second[1] - point[1]];
let xp = v1[0] * v2[1] - v1[1] * v2[0];
// above
if (xp > 0) {
return 1;
}
// below
else if (xp < 0) {
return -1;
}
// exactly on the line
else {
return 0;
}
}
/**
* Takes any representation of a point and transforms it into the representation
* this geometry library understands
*
* @example
* point(5, 5) => [5, 5]
* point({x: 5, y: 5}) => [5, 5]
* point([5, 5]) => [5, 5]
*
* @return {[x, y]} A point representation understood by this geometry library
*/
function point () {
if (arguments.length === 1) {
// either object or array
if (arguments[0].length !== undefined) {
// it's an array
return arguments[0];
}
return [arguments[0].x, arguments[0].y];
} else if (arguments.length === 2) {
// point(x, y)
return [arguments[0], arguments[1]];
}
}
let geometry = {
point: point,
pointAboveLine: pointAboveLine
}
function findSmallest (array, key) {
if (key === undefined) {
key = function (value) {
return value;
};
}
return array.reduce(function (a, b, i, arr) {
if (Math.min(key(a), key(b)) === key(a)) {
return a;
} else {
return b;
}
}, Infinity);
}
function findLargest (array, key) {
if (key === undefined) {
key = function (value) {
return value;
};
}
return array.reduce(function (a, b, i, arr) {
if (Math.max(key(a), key(b)) === key(a)) {
return a;
} else {
return b;
}
}, Infinity);
}
function createNonIntersectingPolygon(initialPoints, amountOfPoints, minimumArea, maximumArea) {
let remainingPoints = amountOfPoints - initialPoints.length;
// algorithm to generate non intersecting polygons
// http://stackoverflow.com/a/20623817/2302759
// This way of generating new points is beyond bad
// both the minimum and maximum area constraints are not satisfied
// and all polygons end up biased to the right, this is because that made sense for my application
// please take the time to find a better way to do this
let lmax = Math.sqrt(maximumArea);
let points = initialPoints;
for (var i = 0; i < remainingPoints; i++) {
points.push({
x: initialPoints[0].x + ((Math.random() - 0.5) * 2) * lmax,
y: initialPoints[0].y + Math.random() * lmax
});
}
// (1) find the leftmost point p
let leftmost = findSmallest(points, (point) => point.x);
// (2) find rightmost point q
let rightmost = findLargest(points, (point) => point.x);
// (3) Partition the points into A, the set of points below pq
// and B, the set of points above pq
let A = points.filter(function (point) {
return geometry.pointAboveLine(geometry.point(point), geometry.point(leftmost), geometry.point(rightmost)) === -1;
});
let B = points.filter(function (point) {
return geometry.pointAboveLine(geometry.point(point), geometry.point(leftmost), geometry.point(rightmost)) === 1;
});
// (4) sort A by increasing x-coordinates
A = A.sort((p1, p2) => p1.x > p2.x);
// (5) sort B by decreasing x-coordinates
B = B.sort((p1, p2) => p1.x < p2.x);
// (6) assemble a polygon in this order [p, ...A, q, ...B];
return [leftmost, ...A, rightmost, ...B];
}
@OmkarRajam
Copy link

OmkarRajam commented Feb 9, 2021

Hello @Azeirah!

Thanks a ton for codifying this algorithm!!! It helped me a lot for my use case.

Just a minor correction :-
In JavaScript, Array.prototype.sort's compareFunction should return either a positive number, a negative number, or zero.

But, on line 134 and 137, you have returned a Boolean from sort compareFunction

    // (4) sort A by increasing x-coordinates
    A = A.sort((p1, p2) => p1.x > p2.x);

    // (5) sort B by decreasing x-coordinates
    B = B.sort((p1, p2) => p1.x < p2.x);

These lines should be corrected to either

    // (4) sort A by increasing x-coordinates
    A = A.sort((p1, p2) => p1.x > p2.x ? 1 : p1.x < p2.x ? -1 : 0);

    // (5) sort B by decreasing x-coordinates
    B = B.sort((p1, p2) => p1.x > p2.x ? -1 : p1.x < p2.x ? 1 : 0);

or simply

    // (4) sort A by increasing x-coordinates    
    A = A.sort((p1, p2) => p1.x - p2.x);

    // (5) sort B by decreasing x-coordinates
    B = B.sort((p1, p2) => p2.x - p1.x);

Here is some explanation about compareFunctions from MDN Array.prototype.sort docs

  • If compareFunction(a, b) returns less than 0, sort a to an index lower than b (i.e. a comes first).
  • If compareFunction(a, b) returns 0, leave a and b unchanged with respect to each other, but sorted with respect to all different elements. Note: the ECMAScript standard only started guaranteeing this behavior in 2019, thus, older browsers may not respect this.
  • If compareFunction(a, b) returns greater than 0, leave a and b unchanged.
  • compareFunction(a, b) must always return the same value when given a specific pair of elements a and b as its two arguments. If inconsistent results are returned, then the sort order is undefined.

Please update the gist with these corrections.

Thanks again,
Omkar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment