UNPKG

gramoloss

Version:

Graph theory package for edition and computation

267 lines (224 loc) 7.9 kB
import { Coord } from "./coord"; import { Option } from "./option"; // --------------------- // decide if there is equality between two sets xs and ys export function eqSet (xs: Set<number | string>, ys: Set<number | string>): boolean { return xs.size === ys.size && [...xs].every((x) => ys.has(x)); } /** * (1-t)^2 p0 + 2t(1-t)p1 + t^2 p2 */ export function bezierValue(t: number, p0: number, p1: number, p2: number) { return (1.0 - t) * (1.0 - t) * p0 + 2.0 * (1.0 - t) * t * p1 + t * t * p2; } /** * Compute the binomial coefficient C(n,k) by recurrence * */ export function binomialCoef(n: number, k: number): number{ if ( k == 0 || k == n ){ return 1; }else if ( n < k){ return 0; } else { return binomialCoef(n-1,k) + binomialCoef(n-1,k-1) } } /** * Compute B(t) where t is in [0,1] and B is a Bezier curve given by its list of points * @param t in [0,1] * @param points * @returns Sum( points[i]*t^i*Binom(n,i)) */ export function bezierCurvePoint(t: number, points: Array<Coord>): Coord { const n = points.length-1; const q = 1 - t; let r = new Coord(0,0); let ti = 1; // ti = t^i let qi = q**n // qi = q^(n-i) for (let i = 0 ; i <= n ; i ++){ const cni = binomialCoef(n,i); r.x += cni * qi * ti * points[i].x; r.y += cni * qi * ti * points[i].y; ti *= t; qi /= q; } return r; } // --------------------- // Solve equation t u + t'v = c where u, v and c are 2d vectors // return false if there is no solution export function solveLinearEquation2D( u: Coord, v: Coord, c: Coord){ const det = u.x * v.y - u.y * v.x; if (det == 0){ return false; } const t1 = (c.x * v.y - c.y * v.x )/det; const t2 = (c.y * u.x - c.x * u.y )/det; return [t1,t2]; } // --------------------- // TODO : check the 0.01 precision /** * Search for an intersection between the segments [a,b] and [c,d]. * Returns an Option with the coord of the intersection if it exists. */ export function isSegmentsIntersection(a: Coord, b: Coord, c: Coord, d: Coord): boolean{ const r = segmentsIntersection(a,b,c,d); return typeof r === "undefined" ? false : true; } /** * UNTESTED * Search for an intersection between the segments [a,b] and [c,d]. * Returns an Option with the coord of the intersection if it exists. */ export function segmentsIntersection(a: Coord, b: Coord, c: Coord, d: Coord): Option<Coord>{ const det = (a.x-b.x)*(d.y-c.y) - (a.y-b.y)*(d.x-c.x); if ( det == 0) { return undefined; } const t1 = ((d.x-b.x)*(d.y-c.y) + (d.y-b.y)*(-(d.x-c.x))) / det; const t2 = ((d.x-b.x)*(-(a.y-b.y))+(d.y-b.y)*(a.x-b.x)) / det; const condition = 0 <= t1 && t1 <= 1 && 0 <= t2 && t2 <= 1; if (condition){ return new Coord(b.x + t1*(a.x-b.x), b.y + t1*(a.y-b.y)); } else { return undefined; } } /** * UNTESTED * Search for an intersection between the segments ]a,b[ and ]c,d[. * Returns an Option with the coord of the intersection if it exists. */ export function segmentsInteriorIntersection(a: Coord, b: Coord, c: Coord, d: Coord): Option<Coord>{ const e = 0.00001; const det = (a.x-b.x)*(d.y-c.y) - (a.y-b.y)*(d.x-c.x); if ( det == 0) { return undefined; } const t1 = ((d.x-b.x)*(d.y-c.y) + (d.y-b.y)*(-(d.x-c.x))) / det; const t2 = ((d.x-b.x)*(-(a.y-b.y))+(d.y-b.y)*(a.x-b.x)) / det; const condition = 0+e < t1 && t1 < 1-e && 0+e < t2 && t2 < 1-e; if (condition){ return new Coord(b.x + t1*(a.x-b.x), b.y + t1*(a.y-b.y)); } else { return undefined; } } /** * UNTESTED * Search for an intersection between the lines (AB) and (CD). * Returns an Option with the coord of the intersection if it exists. */ export function linesIntersection(a: Coord, b: Coord, c: Coord, d: Coord): Option<Coord>{ const det = (a.x-b.x)*(d.y-c.y) - (a.y-b.y)*(d.x-c.x); if ( det == 0) { return undefined; } const t1 = ((d.x-b.x)*(d.y-c.y) + (d.y-b.y)*(-(d.x-c.x))) / det; return new Coord(b.x + t1*(a.x-b.x), b.y + t1*(a.y-b.y)); } // --------------------- // Given a point and triangle defined by its three corners q1, q2 and q3 // Returns true iff point is in the triangle export function isPointInTriangle(point: Coord, q1: Coord, q2: Coord, q3: Coord): boolean{ const sol = solveLinearEquation2D(q1.sub(q3), q2.sub(q3), point.sub(q3)); if ( typeof sol == "boolean"){ return false; } else { const r = sol[0]; const s = sol[1]; return 0 <= r && 0 <= s && 0 <= 1-r-s } } // --------------------- // return true iff there is an intersection between two triangles given by their corners // this includes convex hull intersection (if a triangle is included in the other triangle) export function isTrianglesIntersection(p1: Coord, p2: Coord, p3: Coord, q1: Coord, q2: Coord, q3: Coord): boolean{ if( isPointInTriangle(p1, q1,q2,q3) && isPointInTriangle(p2, q1,q2,q3) && isPointInTriangle(p3, q1,q2,q3 )){ return true; } if ( isPointInTriangle(q1, p1,p2,p3) && isPointInTriangle(q2, p1,p2,p3) && isPointInTriangle(q3, p1,p2,p3)){ return true; } if( isSegmentsIntersection(p1,p2, q1,q2) || isSegmentsIntersection(p1,p2, q1,q3) || isSegmentsIntersection(p1,p2, q2,q3) || isSegmentsIntersection(p1,p3, q1,q2) || isSegmentsIntersection(p1,p3, q1,q3) || isSegmentsIntersection(p1,p3, q2,q3) || isSegmentsIntersection(p3,p2, q1,q2) || isSegmentsIntersection(p3,p2, q1,q3) || isSegmentsIntersection(p3,p2, q2,q3)){ return true; } return false; } // -------------------- // Decide if there is an intersection between two quadratic Bezier curves export function isQuadraticBezierCurvesIntersection(p1: Coord, cp1: Coord, p2: Coord, q1: Coord, cp2: Coord, q2: Coord): boolean{ if ( isTrianglesIntersection(p1, cp1, p2, q1, cp2, q2) == false){ return false; } const m = 10; const bezier1 = new Array(p1,cp1,p2); const bezier2 = new Array(q1,cp2,q2); for (let i = 0 ; i < m ; i ++){ const a = bezierCurvePoint(i/m, bezier1); const b = bezierCurvePoint((i+1)/m, bezier1); for (let j = 0 ; j <= m ; j ++){ const c = bezierCurvePoint(j/m, bezier2); const d = bezierCurvePoint((j+1)/m, bezier2); if (isSegmentsIntersection(a,b,c,d)){ return true; } } } return false; } /** * Returns the determinant of the matrix. * Algorithm: Leibniz formula (recursive). * @param matrix is supposed to be square and not of size 0 * No error are triggered it is not the case. */ export function det(matrix: Array<Array<number>>){ const n = matrix.length; if (n === 1) { return matrix[0][0]; } let d = 0; for (let i = 0; i < n; i++) { const subMatrix = matrix.slice(1).map(row => row.filter((_, j) => j !== i)); const sign = i % 2 === 0 ? 1 : -1; d += sign * matrix[0][i] * det(subMatrix); } return d; } /** * Return true if n is a square modulo m. * @param n integer * @param m integer * @example * isModSquare(4,5) == true // because 4 = 2^2 mod 5 * isModSquare(3,5) == false // because 0, 1 and 4 are the only squares in Z/5Z. */ export function isModSquare(n: number, m: number){ for( let i = 0 ; i < m ; i ++){ if ( (n - i*i) % m == 0 ){ return true; } } return false; } export function booleanArrayToString(array: boolean[]): string { return array.map((value) => value ? '1' : '0').join(''); } export function isPrime(num: number): boolean { if (num <= 1) return false; for (let i = 2; i <= Math.sqrt(num); i++) { if (num % i === 0) return false; } return true; }