import { Vector } from "./base/2d/Primitives.js";
function isMobile() {
return /Android|webOS|iPhone|iPad|iPod|Opera Mini/i.test(navigator.userAgent) ;
}
function isSafari() {
return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
}
function pointToCircleDistance(x, y, circle) {
const pointToCircleCenterDistance = new Vector(x, y, circle.x, circle.y).length;
return pointToCircleCenterDistance - circle.r;
}
function countClosestTraversal(line, sight) {
const x1 = sight.x1,
y1 = sight.y1,
x2 = sight.x2,
y2 = sight.y2;
const x3 = line.x1,
y3 = line.y1,
x4 = line.x2,
y4 = line.y2;
const r_px = x1,
r_py = y1,
r_dx = x2-x1,
r_dy = y2-y1;
const s_px = x3,
s_py = y3,
s_dx = x4-x3,
s_dy = y4-y3;
const r_mag = Math.sqrt(r_dx*r_dx+r_dy*r_dy),
s_mag = Math.sqrt(s_dx*s_dx+s_dy*s_dy);
if(r_dx/r_mag==s_dx/s_mag && r_dy/r_mag==s_dy/s_mag){
return null;
}
const T2 = (r_dx*(s_py-r_py) + r_dy*(r_px-s_px))/(s_dx*r_dy - s_dy*r_dx),
T1 = (s_px+s_dx*T2-r_px)/r_dx;
if(T1<0 || isNaN(T1)) return null;
if(T2<0 || T2>1) return null;
return {
x: r_px+r_dx*T1,
y: r_py+r_dy*T1,
p: T1
};
}
/**
*
* @param {{x1:number, y1:number, x2:number, y2:number}} line1
* @param {{x1:number, y1:number, x2:number, y2:number}} line2
* @returns {{x:number, y:number, p:number} | undefined}
* @ignore
*/
function countClosestTraversal2(line1, line2) {
const x1 = line2.x1,
y1 = line2.y1,
x2 = line2.x2,
y2 = line2.y2;
const x3 = line1.x1,
y3 = line1.y1,
x4 = line1.x2,
y4 = line1.y2;
const det = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
// lines are parallel, or coincident
if (det === 0){
return;
}
let x = ((x1*y2 - y1*x2) * (x3 - x4) - (x1 - x2) * (x3*y4 - y3*x4)) / det;
let y = ((x1*y2 - y1*x2) * (y3 - y4) - (y1 - y2) * (x3*y4 - y3*x4)) / det;
const point = {x, y};
if (isPointOnTheLine(point, line1, 0.0000000000001) && isPointOnTheLine(point, line2, 0.0000000000001)) {
const p = Math.sqrt(Math.pow((x - x1), 2) + Math.pow((y - y1), 2));
return {x, y, p};
} else {
return;
}
}
function angle_2points(x1, y1, x2, y2) {
return Math.atan2(y2 - y1, x2 - x1);
}
function angle_3points(a, b, c) {
const x1 = a.x - b.x,
x2 = c.x - b.x,
y1 = a.y - b.y,
y2 = c.y - b.y,
d1 = Math.sqrt(x1 * x1 + y1 * y1),
d2 = Math.sqrt(x2 * x2 + y2 * y2);
//console.log("angle: ", (Math.acos((x1* x2 + y1 * y2) / (d1 * d2))* 180) / Math.PI);
return Math.acos((x1* x2 + y1 * y2) / (d1 * d2));
}
function dotProductWithAngle(lenA, lenB, angle) {
return lenA * lenB * Math.cos(angle);
}
function dotProduct(vec1, vec2) {
return vec1.x * vec2.x + vec1.y * vec2.y;
}
function crossProduct(a, b) {
return (a.x * b.y - b.x * a.y);
}
function isPointOnTheLine(point, line, m_error = 0) {
return (
((point.x >= (line.x1 - m_error)) && (point.x <= (line.x2 + m_error))) ||
((point.x <= (line.x1 + m_error)) && (point.x >= (line.x2 - m_error)))
) && (
((point.y >= (line.y1 - m_error)) && (point.y <= (line.y2 + m_error))) ||
((point.y <= (line.y1 + m_error)) && (point.y >= (line.y2 - m_error)))
);
}
function countDistance(obj1, obj2) {
return new Vector(obj1.x, obj1.y, obj2.x, obj2.y).length;
}
function isLineShorter(line1, line2) {
return (new Vector(line1.x1, line1.y1, line1.x2, line1.y2)).length < (new Vector(line2.x1, line2.y1, line2.x2, line2.y2)).length;
}
function isPointLineIntersect(point, line) {
const lineL = new Vector(line.x1, line.y1, line.x2, line.y2).length,
lengthAB = new Vector(line.x1, line.y1, point.x, point.y).length + new Vector(line.x2, line.y2, point.x, point.y).length;
if (lengthAB <= lineL + 0.2) {
//console.log("point to line intersect. line len: " + lineL + ", line AB len: " + lengthAB);
return true;
}
return false;
}
/**
*
* @param {Array<Array<number>>} polygon
* @param {{x1:number, y1:number, x2:number, y2:number}} line
* @returns {{x:number, y:number, p:number} | null}
* @ignore
*/
function isPolygonLineIntersect(polygon, line) {
const len = polygon.length;
for (let i = 0; i < len; i+=1) {
let curr = polygon[i],
next = polygon[i+1];
//if next item not exist and current is not first
if (!next) {
// if current vertex is not the first one
if (!(curr[0] === polygon[0][0] && curr[1] === polygon[0][1])) {
next = polygon[0];
} else {
continue;
}
}
const edge = { x1: curr[0], y1: curr[1], x2: next[0], y2: next[1] };
const intersection = countClosestTraversal2(edge, line);
if (intersection) {
return intersection;
}
}
if (polygon[len-1][0] !== polygon[0][0] && polygon[len-1][1] !== polygon[0][1]) {
//check one last item
const curr = polygon[len - 1],
next = polygon[0];
const edge = { x1: curr[0], y1: curr[1], x2: next[0], y2: next[1] };
const intersection = countClosestTraversal2(edge, line);
if (intersection) {
return intersection;
}
}
return null;
}
function isPointPolygonIntersect(x, y, polygon) {
const len = polygon.length;
for (let i = 0; i < len; i+=1) {
let vertex1 = polygon[i],
vertex2 = polygon[i + 1];
// if last vertex, set vertex2 as the first
if (!vertex2) {
vertex2 = polygon[0];
}
if (isPointLineIntersect({x,y}, {x1: vertex1[0], y1: vertex1[1], x2: vertex2[0], y2: vertex2[1]})) {
return true;
}
}
return false;
}
function isPointInsidePolygon(x, y, polygon) {
const len = polygon.length;
let intersections = 0;
for (let i = 0; i < len; i++) {
let vertex1 = polygon[i],
vertex2 = polygon[i + 1] ? polygon[i + 1] : polygon[0],
x1 = vertex1[0],
y1 = vertex1[1],
x2 = vertex2[0],
y2 = vertex2[1];
if (y < y1 !== y < y2 &&
x < (x2 - x1) * (y - y1) / (y2 - y1) + x1) {
intersections++;
}
}
if (intersections > 0) {
if (intersections % 2 === 0) {
return false;
} else {
return true;
}
} else {
return false;
}
}
function isPointRectIntersect(x, y, rect) {
if (x >= rect.x && x <= rect.width + rect.x && y >= rect.y && y <= rect.y + rect.height) {
return true;
} else {
return false;
}
}
/**
*
* @param {number} x
* @param {number} y
* @param {{x:number, y:number, r:number}} circle
* @returns {boolean}
*/
function isPointCircleIntersect(x, y, circle) {
const radius = circle.r,
lineToCircleCenter = new Vector(x, y, circle.x, circle.y),
pointCircleLineLength = lineToCircleCenter.length;
if (pointCircleLineLength < radius)
return true;
else
return false;
}
function isCircleLineIntersect(x, y, r, line) {
const x1 = line.x1,
y1 = line.y1,
x2 = line.x2,
y2 = line.y2,
vec1 = {x: x1 - x, y: y1-y}, //new Vector(x, y, x1, y1),
vec2 = {x: x2 - x, y: y2-y}, //new Vector(x, y, x2, y2),
vec3 = {x: x2 - x1, y: y2-y1}, //new Vector(x1 ,y1, x2, y2),
vec4 = {x: x1 - x2, y: y1-y2}, //new Vector(x2, y2, x1, y1),
vec3Len = Math.sqrt(Math.pow(vec3.x, 2) + Math.pow(vec3.y, 2)),//vec3.length,
dotP1 = dotProduct(vec1, vec4),
dotP2 = dotProduct(vec2, vec3);
// checks if the line is inside the circle,
// max_dist = Math.max(vec1Len, vec2Len);
let min_dist;
if (dotP1 > 0 && dotP2 > 0) {
min_dist = crossProduct(vec1,vec2)/vec3Len;
if (min_dist < 0) {
min_dist *= -1;
}
} else {
min_dist = Math.min(vec1.length, vec2.length);
}
if (min_dist <= r) { // && max_dist >= r) {
return true;
} else {
return false;
}
}
/**
*
* @param {Array<number>} ellipse - x,y,radX,radY
* @param {Array<Array<number>>} line [x1,y1],[x2,y2]
*/
function isEllipseLineIntersect(ellipse, line) {
const x = ellipse[0],
y = ellipse[1],
radX = ellipse[2],
radY = ellipse[3],
x1 = line[0][0],
y1 = line[0][1],
x2 = line[1][0],
y2 = line[1][1],
lineAToElCenter = { x: x - x1, y: y - y1 }, //new Vector(x, y, x1, y1),
lineBToElCenter = { x: x - x2, y: y - y2 }, //new Vector(x, y, x2, y2),
lineAToElCenterLen = Math.sqrt(Math.pow(lineAToElCenter.x, 2) + Math.pow(lineAToElCenter.y, 2)),
lineBToElCenterLen = Math.sqrt(Math.pow(lineBToElCenter.x, 2) + Math.pow(lineBToElCenter.y, 2)),
lineToCenterLenMin = Math.min(lineAToElCenterLen, lineBToElCenterLen),
ellipseMax = Math.max(radX, radY);
if (lineToCenterLenMin > ellipseMax) {
return false;
}
const traversalLine = lineToCenterLenMin === lineAToElCenterLen ? lineAToElCenter : lineBToElCenter,
angleToAxisX = Math.atan2(traversalLine.y, traversalLine.x);
const intersectX = Math.cos(angleToAxisX) * radX,
intersectY = Math.sin(angleToAxisX) * radY,
lineToCenter = { x: 0 - intersectX, y: 0 - intersectY },
intersectLineLen = Math.sqrt(Math.pow(lineToCenter.x, 2) + Math.pow(lineToCenter.y, 2));
//console.log("lenToCheck: ", lenToCheck);
//console.log("x: ", intersectX);
if (lineToCenterLenMin > intersectLineLen) {
return false;
}
return true;
}
/**
*
* @param {Array<number>} ellipse - x,y,radX,radY
* @param {{x:number, y:number, r:number}} circle
* @returns {{x:number, y:number, p:number} | boolean}
*/
function isEllipseCircleIntersect(ellipse, circle) {
const ellipseX = ellipse[0],
ellipseY = ellipse[1],
ellipseToCircleLine = { x: ellipseX - circle.x, y: ellipseY - circle.y },
len = Math.sqrt(Math.pow(ellipseToCircleLine.x, 2) + Math.pow(ellipseToCircleLine.y, 2)),
maxRad = Math.max(ellipse[2], ellipse[3]);
// no collisions for sure
if (len > (maxRad + circle.r)) {
return false;
} else {
// check possible collision
const angle = angle_2points(ellipseX, ellipseY, circle.x, circle.y),
traversalX = ellipseX + (ellipse[2] * Math.cos(angle)),
traversalY = ellipseY + (ellipse[3] * Math.sin(angle)),
vecTrX = ellipseX - traversalX,
vecTrY = ellipseY - traversalY,
traversalLen = Math.sqrt(Math.pow(vecTrX, 2) + Math.pow(vecTrY, 2)) + circle.r;
if (len <= traversalLen) {
return {x: vecTrX, y: vecTrY, p:1};
} else {
return false;
}
}
}
/**
*
* @param {Array<number>} ellipse - x,y,radX,radY
* @param {Array<Array<number>>} polygon - x,y
* @returns {boolean}
*/
function isEllipsePolygonIntersect(ellipse, polygon) {
const len = polygon.length;
for (let i = 0; i < len; i+=1) {
let vertex1 = polygon[i],
vertex2 = polygon[i + 1];
// if last vertex, set vertex2 as the first
if (!vertex2) {
vertex2 = polygon[0];
}
if (isEllipseLineIntersect(ellipse, [vertex1, vertex2])) {
return true;
}
}
return false;
}
function generateUniqId() {
return Math.round(Math.random() * 1000000);
}
function randomFromArray(array) {
return array[Math.floor(Math.random()*array.length)];
}
function verticesArrayToArrayNumbers(array) {
const len = array.length,
numbers = [];
for (let i = 0; i < len; i++) {
const vertex = array[i];
numbers.push([vertex.x, vertex.y]);
}
return numbers;
}
/**
*
* @param {Array<Array<number>>} arrayDots
* @returns {Array<Array<number>>}
*/
function calculateLinesVertices(x = 0, y = 0, r, arrayDots) {
const len = arrayDots.length;
let arrayLines = Array(len),
arrayDotsIterator = 0;
for (let i = 0; i < len; i++) {
const dot1 = arrayDots[i];
let dot2 = arrayDots[i+1];
if (!dot2) {
dot2 = arrayDots[0];
}
const x1 = dot1[0],
y1 = dot1[1],
x2 = dot2[0],
y2 = dot2[1];
const x1R = x1 * Math.cos(r) - y1 * Math.sin(r),
y1R = x1 * Math.sin(r) + y1 * Math.cos(r),
x2R = x2 * Math.cos(r) - y2 * Math.sin(r),
y2R = x2 * Math.sin(r) + y2 * Math.cos(r);
const line = [x1R + x, y1R + y, x2R + x, y2R + y];
arrayLines[arrayDotsIterator] = line;
arrayDotsIterator++;
}
return arrayLines;
}
/**
* @param {number} x
* @param {number} y
* @param {number} radiusX
* @param {number} radiusY
* @param {number} [angle = 2 * Math.PI]
* @param {number} [step = Math.PI/12]
* @returns {Array<Array<number>>}
*/
function calculateEllipseVertices(x = 0, y = 0, radiusX, radiusY, angle = 2*Math.PI, step = Math.PI/8) {
let ellipsePolygonCoords = [];
for (let r = 0; r <= angle; r += step) {
let x2 = Math.cos(r) * radiusX + x,
y2 = Math.sin(r) * radiusY + y;
ellipsePolygonCoords.push([x2, y2]);
}
return ellipsePolygonCoords;
}
/**
*
* @param { Array<number> } mat1
* @param { Array<number> } mat2
* @returns { Array<number> }
*/
function mat3Multiply(mat1, mat2) {
let matResult = [];
for (let resultIdx = 0; resultIdx < 9; resultIdx += 3) {
let resultIndex = resultIdx;
for (let i = 0; i < 3; i++) {
let resultVal = 0,
k = i;
for (let j = 0; j < 3; j++) {
const mat1Val = mat1[resultIdx + j],
mat2Val = mat2[k];
resultVal += (mat1Val * mat2Val);
k+=3;
}
matResult[resultIndex] = resultVal;
resultIndex++;
}
}
return matResult;
}
/**
*
* @param {Array<number>} mat3 [a, b, c,
* d. e, f,
* g, h, i]
* @param {Array<number>} vec3 [x1, y1]
* @returns {Array<number>} [a * x1 + b * y1 + c * 1, d * x1 + e * y1 + f * 1]
*/
function mat3MultiplyVector (mat3, vec3) {
let result = [];
let resultIndex = 0;
for (let rowStartIdx = 0; rowStartIdx < 6; rowStartIdx += 3) {
let resultVal = 0;
const stopInt = rowStartIdx + 3;
let vecIdx = 0;
for (let rowIdx = rowStartIdx; rowIdx < stopInt; rowIdx++) {
const matVal = mat3[rowIdx],
vecVal = vec3[vecIdx] || 1; // z1 coord
resultVal += (matVal * vecVal);
vecIdx++;
}
result[resultIndex] = resultVal;
resultIndex++;
}
return result;
}
/**
*
* @param {Array<number>} mat3 [a, b, c,
* d. e, f,
* g, h, i]
* @param {Array<number>} vec3 [x1, y1, x2, y1, x1, y2, x1, y2, x2, y1, x2, y2, ...]
* @returns {Array<number>} [a*x1 + b*y1 + c*1, d*y1 + e*y1 + f*1, ...]
*/
function mat3MultiplyPosCoords (mat3, vec3) {
const vec3Len = vec3.length;
let result = [];
let resultIndex = 0;
for (let nPair = 0; nPair < vec3Len; nPair += 2) {
for (let rowStartIdx = 0; rowStartIdx < 6; rowStartIdx += 3) {
let resultVal = 0;
const stopInt = rowStartIdx + 3;
let vecIdx = nPair;
let iteration = 1;
for (let rowIdx = rowStartIdx; rowIdx < stopInt; rowIdx++) {
const matVal = mat3[rowIdx],
vecVal = iteration === 3 ? 1 : vec3[vecIdx]; // 3: z1 = 1 coord
resultVal += (matVal * vecVal);
vecIdx++;
iteration++;
}
result[resultIndex] = resultVal;
resultIndex++;
}
}
return result;
}
export {
isMobile,
isSafari,
pointToCircleDistance,
countClosestTraversal,
countClosestTraversal2,
angle_2points,
angle_3points,
dotProductWithAngle,
dotProduct,
crossProduct,
isPointOnTheLine,
isLineShorter,
isPointLineIntersect,
isPointPolygonIntersect,
isPointRectIntersect,
isPointCircleIntersect,
isPolygonLineIntersect,
isCircleLineIntersect,
isEllipseLineIntersect,
isEllipseCircleIntersect,
isEllipsePolygonIntersect,
isPointInsidePolygon,
generateUniqId,
randomFromArray,
verticesArrayToArrayNumbers,
countDistance,
calculateEllipseVertices,
calculateLinesVertices,
mat3Multiply,
mat3MultiplyVector,
mat3MultiplyPosCoords
};