scripts/core/utils.js
import {Vector2} from 'three';
import {checkIntersection} from 'line-intersect';
export class Utils
{
/** Determines the distance of a point from a line.
* @param point The Point coordinates as THREE.Vector2
* @param start The starting coordinates of the line as THREE.Vector2
* @param end The ending coordinates of the line as THREE.Vector2
* @returns The distance value (number).
*/
static pointDistanceFromLine(point, start, end)
{
var tPoint = Utils.closestPointOnLine(point, start, end);
var tDx = point.x - tPoint.x;
var tDy = point.y - tPoint.y;
return Math.sqrt(tDx * tDx + tDy * tDy);
}
/** Gets the projection of a point onto a line.
* @param point the point
* @param start the starting coordinates of the line as THREE.Vector2
* @param end the ending coordinates of the line as THREE.Vector2
* @returns The point as THREE.Vector2.
*/
static closestPointOnLine(point, start, end)
{
// Inspired by: http://stackoverflow.com/a/6853926
var tA = point.x - start.x;
var tB = point.y - start.y;
var tC = end.x - start.x;
var tD = end.y - start.y;
var tDot = tA * tC + tB * tD;
var tLenSq = tC * tC + tD * tD;
var tParam = tDot / tLenSq;
var tXx, tYy;
if (tParam < 0 || (start.x == end.x && start.y == end.y))
{
tXx = start.x;
tYy = start.y;
}
else if (tParam > 1)
{
tXx = end.x;
tYy = end.y;
}
else {
tXx = start.x + tParam * tC;
tYy = start.y + tParam * tD;
}
return new Vector2(tXx, tYy);
}
/** Gets the distance of two points.
* @param start the starting coordinate of the line as Vector2
* @param end the ending coordinate of the line as Vector2
* @returns The distance.
*/
static distance(start, end)
{
return Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2));
}
/** Gets the angle between point1 -> start and 0,0 -> point2 (-pi to pi)
* @returns The angle.
*/
static angle(start, end)
{
var tDot = start.x * end.x + start.y * end.y;
var tDet = start.x * end.y - start.y * end.x;
var tAngle = -Math.atan2(tDet, tDot);
return tAngle;
}
/** shifts angle to be 0 to 2pi */
static angle2pi(start, end)
{
var tTheta = Utils.angle(start, end);
if (tTheta < 0)
{
tTheta += 2.0 * Math.PI;
}
return tTheta;
}
/** Checks if an array of points is clockwise.
* @param points Is array of points with x,y attributes
* @returns True if clockwise.
*/
static isClockwise(points)
{
// make positive
let tSubX = Math.min(0, Math.min.apply(null, Utils.map(points, function (p) {
return p.x;
})));
let tSubY = Math.min(0, Math.min.apply(null, Utils.map(points, function (p) {
return p.x;
})));
var tNewPoints = Utils.map(points, function (p) {
return {
x: p.x - tSubX,
y: p.y - tSubY
};
});
// determine CW/CCW, based on:
// http://stackoverflow.com/questions/1165647
var tSum = 0;
for (var tI = 0; tI < tNewPoints.length; tI++)
{
var tC1 = tNewPoints[tI];
var tC2;
if (tI == tNewPoints.length - 1)
{
tC2 = tNewPoints[0];
}
else
{
tC2 = tNewPoints[tI + 1];
}
tSum += (tC2.x - tC1.x) * (tC2.y + tC1.y);
}
return (tSum >= 0);
}
/** Creates a Guide.
* @returns A new Guide.
*/
static guide()
{
var tS4 = function ()
{
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
};
return tS4() + tS4() + '-' + tS4() + '-' + tS4() + '-' + tS4() + '-' + tS4() + tS4() + tS4();
}
/** both arguments are arrays of corners with x,y attributes */
static polygonPolygonIntersect(firstCorners, secondCorners)
{
for (var tI = 0; tI < firstCorners.length; tI++)
{
var tFirstCorner = firstCorners[tI], tSecondCorner;
if (tI == firstCorners.length - 1)
{
tSecondCorner = firstCorners[0];
}
else
{
tSecondCorner = firstCorners[tI + 1];
}
if (Utils.linePolygonIntersect(tFirstCorner.x, tFirstCorner.y,tSecondCorner.x, tSecondCorner.y, secondCorners))
{
return true;
}
}
return false;
}
/** Corners is an array of points with x,y attributes */
static linePolygonIntersect(point, point2, corners)
{
for (var tI = 0; tI < corners.length; tI++)
{
var tFirstCorner = corners[tI],tSecondCorner;
if (tI == corners.length - 1)
{
tSecondCorner = corners[0];
}
else
{
tSecondCorner = corners[tI + 1];
}
if (Utils.lineLineIntersect(point, point2, {x:tFirstCorner.x, y:tFirstCorner.y}, {x:tSecondCorner.x, y:tSecondCorner.y}))
{
return true;
}
}
return false;
}
/** */
static lineLineIntersectPoint(aStart, aEnd, bStart, bEnd)
{
var result = checkIntersection(aStart.x, aStart.y, aEnd.x, aEnd.y, bStart.x, bStart.y, bEnd.x, bEnd.y);
if(result.point)
{
return new Vector2(result.point.x, result.point.y);
}
return undefined;
}
/** */
static lineLineIntersect(lineAStart, lineAEnd, lineBStart, lineBEnd)
{
function tCCW(p1, p2, p3)
{
var tA = p1.x, tB = p1.y, tC = p2.x, tD = p2.y, tE = p3.x, tF = p3.y;
return (tF - tB) * (tC - tA) > (tD - tB) * (tE - tA);
}
var tP1 = lineAStart, tP2 = lineAEnd, tP3 = lineBStart, tP4 = lineBEnd;
return (tCCW(tP1, tP3, tP4) != tCCW(tP2, tP3, tP4)) && (tCCW(tP1, tP2, tP3) != tCCW(tP1, tP2, tP4));
}
/**
@param corners Is an array of points with x,y attributes
@param startX X start coord for raycast
@param startY Y start coord for raycast
*/
static pointInPolygon2(point, polygon)
{
var x = point.x, y = point.y;
var inside = false;
for (var i = 0, j = polygon.length - 1; i < polygon.length; j = i++)
{
var intersect = ((((polygon[i].y <= y) && (y < polygon[j].y)) || ((polygon[j].y <= y) && (y < polygon[i].y))) && (x < (polygon[j].x - polygon[i].x) * (y - polygon[i].y) / (polygon[j].y - polygon[i].y) + polygon[i].x));
if (intersect)
{
inside = !inside;
}
}
return inside;
}
/**
@param corners Is an array of points with x,y attributes
@param startX X start coord for raycast
@param startY Y start coord for raycast
*/
static pointInPolygon(point, corners, start)
{
start = start || new Vector2(0,0);
var startX = start.x || 0;
var startY = start.y || 0;
//ensure that point(startX, startY) is outside the polygon consists of corners
var tMinX = 0, tMinY = 0;
var tI = 0;
if (startX === undefined || startY === undefined)
{
for (tI = 0; tI < corners.length; tI++)
{
tMinX = Math.min(tMinX, corners[tI].x);
tMinY = Math.min(tMinX, corners[tI].y);
}
startX = tMinX - 10;
startY = tMinY - 10;
}
var tIntersects = 0;
for (tI = 0; tI < corners.length; tI++)
{
var tFirstCorner = corners[tI], tSecondCorner;
if (tI == corners.length - 1)
{
tSecondCorner = corners[0];
}
else
{
tSecondCorner = corners[tI + 1];
}
if (Utils.lineLineIntersect(start, point, tFirstCorner.x, tFirstCorner.y, tSecondCorner.x, tSecondCorner.y))
{
tIntersects++;
}
}
// odd intersections means the point is in the polygon
return ((tIntersects % 2) == 1);
}
/** Checks if all corners of insideCorners are inside the polygon described by outsideCorners */
static polygonInsidePolygon(insideCorners, outsideCorners, start)
{
start.x = start.x || 0;
start.y = start.y || 0;
for (var tI = 0; tI < insideCorners.length; tI++)
{
if (!Utils.pointInPolygon(insideCorners[tI].x, insideCorners[tI].y,outsideCorners,start))
{
return false;
}
}
return true;
}
/** Checks if any corners of firstCorners is inside the polygon described by secondCorners */
static polygonOutsidePolygon(insideCorners, outsideCorners, start)
{
start.x = start.x || 0;
start.y = start.y || 0;
for (var tI = 0; tI < insideCorners.length; tI++)
{
if (Utils.pointInPolygon(insideCorners[tI].x, insideCorners[tI].y,outsideCorners,start))
{
return false;
}
}
return true;
}
// arrays
static forEach(array, action)
{
for (var tI = 0; tI < array.length; tI++)
{
action(array[tI]);
}
}
static forEachIndexed(array, action)
{
for (var tI = 0; tI < array.length; tI++)
{
action(tI, array[tI]);
}
}
static map(array, func)
{
var tResult = [];
array.forEach((element) => {
tResult.push(func(element));
});
return tResult;
}
/** Remove elements in array if func(element) returns true */
static removeIf(array, func)
{
var tResult = [];
array.forEach((element) => {
if (!func(element)) {
tResult.push(element);
}
});
return tResult;
}
/** Shift the items in an array by shift (positive integer) */
static cycle(arr, shift)
{
var tReturn = arr.slice(0);
for (var tI = 0; tI < shift; tI++) {
var tmp = tReturn.shift();
tReturn.push(tmp);
}
return tReturn;
}
/** Returns in the unique elemnts in arr */
static unique(arr, hashFunc)
{
var tResults = [];
var tMap = {};
for (var tI = 0; tI < arr.length; tI++) {
if (!tMap.hasOwnProperty(arr[tI])) {
tResults.push(arr[tI]);
tMap[hashFunc(arr[tI])] = true;
}
}
return tResults;
}
/** Remove value from array, if it is present */
static removeValue(array, value)
{
for (var tI = array.length - 1; tI >= 0; tI--)
{
if (array[tI] === value) {
array.splice(tI, 1);
}
}
}
/** Checks if value is in array */
static hasValue(array, value)
{
for (var tI = 0; tI < array.length; tI++)
{
if (array[tI] === value)
{
return true;
}
}
return false;
}
/** Subtracts the elements in subArray from array */
static subtract(array, subArray)
{
return Utils.removeIf(array, function (el) {
return Utils.hasValue(subArray, el);
});
}
}