(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["2d-geometry"] = {})); })(this, (function (exports) { 'use strict'; class Point2d { constructor(x = 0, y = 0) { this.x = x; this.y = y; } static getDistance(x1, y1, x2, y2) { return Number.parseFloat(Math.hypot(x2 - x1, y2 - y1).toFixed(2)); } clone() { return new Point2d(this.x, this.y); } equals(p) { return this.x == p.x && this.y === p.y; } add(value) { this.x += value.x; this.y += value.y; } substract(value) { this.x -= value.x; this.y -= value.y; } multiply(scalar) { this.x *= scalar; this.y *= scalar; } divide(scalar) { this.x /= scalar; this.y /= scalar; } distanceTo(value) { return Point2d.getDistance(this.x, this.y, value.x, value.y); } translate(vector) { this.x += vector.x; this.y += vector.y; } rotate(angle, origin = new Point2d()) { if (angle === 0 || angle === 360) { return; } let angleRad = (angle * Math.PI) / 180; let xo = this.x - origin.x; let yo = this.y - origin.y; let xp = xo * Math.cos(angleRad) - yo * Math.sin(angleRad); let yp = yo * Math.cos(angleRad) + xo * Math.sin(angleRad); xp = Number.parseFloat(xp.toFixed(2)); yp = Number.parseFloat(yp.toFixed(2)); this.x = xp + origin.x; this.y = yp + origin.y; } } class Vector2d { constructor(x = 0, y = 0) { this.x = x; this.y = y; } static fromPolarCoordinates(magnitude, angle) { let angleRad = (angle * Math.PI) / 180; let x = magnitude * Math.cos(angleRad); let y = magnitude * Math.sin(angleRad); return new Vector2d(Number.parseFloat(x.toFixed(2)), Number.parseFloat(y.toFixed(2))); } get magnitude() { return Number.parseFloat(Math.hypot(this.x - 0, this.y - 0).toFixed(2)); } get angle() { return Number.parseFloat((Math.atan(this.y / this.x) * (180 / Math.PI)).toFixed(2)); } add(value) { this.x += value.x; this.y += value.y; } substract(value) { this.x -= value.x; this.y -= value.y; } multiply(scalar) { this.x *= scalar; this.y *= scalar; } divide(scalar) { this.x /= scalar; this.y /= scalar; } dot(value) { return this.x * value.x + this.y * value.y; } } class Polygon2d { constructor(item) { if (item[0] instanceof Point2d) { if (item.length < 3) { throw new Error("Minimum number of vertices is 3"); } this._vertices = item; this._edges = []; for (let i = 0; i < this._vertices.length - 1; i++) { this._edges.push(new Line2d(this._vertices[i], this._vertices[i + 1])); } this._edges.push(new Line2d(this._vertices[this._vertices.length - 1], this._vertices[0])); } else { if (item.length < 3) { throw new Error("Minimum number of edges is 3"); } this._vertices = []; this._edges = item; for (let i = 0; i < this._edges.length; i++) { const p1 = this._edges[i].p1; const p2 = this._edges[i].p2; if (i === 0) { this._vertices.push(p1); } else if (!this._vertices[i - 1].equals(p1)) { this._vertices.push(p1); } if (!this._vertices[0].equals(p2)) { this._vertices.push(p2); } } } } get vertices() { return this._vertices; } get edges() { return this._edges; } get centroid() { const length = this._vertices.length; const sumX = this._vertices .map((v) => v.x) .reduce((accumulator, currentValue) => { return accumulator + currentValue; }, 0); const sumY = this._vertices .map((v) => v.y) .reduce((accumulator, currentValue) => { return accumulator + currentValue; }, 0); return new Point2d(sumX / length, sumY / length); } get area() { const triangles = []; this.triangulate().forEach((points) => { triangles.push([ new Line2d(points[0], points[1]), new Line2d(points[1], points[2]), new Line2d(points[2], points[0]), ]); }); const area = triangles .map((edges) => { const b = edges[0].length; const h = edges[0].getOrthogonalLineThrough(edges[1].p2).length; return (b * h) / 2; }) .reduce((accumulator, currentValue) => { return accumulator + currentValue; }, 0); return Number.parseFloat(area.toFixed(1)); } get perimeter() { return Number.parseFloat(this.edges .map((edge) => edge.length) .reduce((sum, current) => sum + current, 0) .toFixed(2)); } get interiorAngles() { const bound = this.edges.length - 1; return this.edges.map((edge, index, array) => { const angle = index === bound ? edge.angleTo(array[0]) : edge.angleTo(array[index + 1]); return angle > 0 ? 360 - angle : Math.abs(angle); }); } get isEquiAngular() { return [...new Set(this.interiorAngles)].length === 1; } get isEquilateral() { const lengths = this.edges.map((e) => e.length); return [...new Set(lengths)].length === 1; } get isConvex() { return !this.isConcave; } get isConcave() { return this.interiorAngles.some((a) => a > 180); } translate(vector) { this.vertices.forEach((vertice) => { return vertice.translate(vector); }); } rotate(angle, origin = new Point2d()) { if (angle === 0 || angle === 360) { return; } this.vertices.forEach((vertice) => { return vertice.rotate(angle, origin); }); } isOnEdge(point, threshold = 0) { return this.edges.some((edge) => { return edge.isOnEdge(point, threshold); }); } contains(point) { return this.triangulate().some((triangle) => { const v1 = triangle[0]; const v2 = triangle[1]; const v3 = triangle[2]; const d1 = (point.x - v2.x) * (v1.y - v2.y) - (v1.x - v2.x) * (point.y - v2.y); const d2 = (point.x - v3.x) * (v2.y - v3.y) - (v2.x - v3.x) * (point.y - v3.y); const d3 = (point.x - v1.x) * (v3.y - v1.y) - (v3.x - v1.x) * (point.y - v1.y); const has_neg = d1 < 0 || d2 < 0 || d3 < 0; const has_pos = d1 > 0 || d2 > 0 || d3 > 0; return !(has_neg && has_pos); }); } triangulate() { const bound = this.edges.length - 1; const reflexPoints = this.edges.map((edge, index, array) => { const angle = index === bound ? edge.angleTo(array[0]) : edge.angleTo(array[index + 1]); return { angle: angle > 0 ? 360 - angle : Math.abs(angle), point: edge.p2, }; }); const triangles = []; let workingSet = [...reflexPoints]; const concaveIndex = reflexPoints.findIndex((t) => t.angle > 180); if (concaveIndex !== -1) { workingSet = reflexPoints .slice(concaveIndex) .concat(reflexPoints.slice(0, concaveIndex)); } for (let i = 1; i < workingSet.length - 1; i++) { triangles.push([ workingSet[0].point, workingSet[i].point, workingSet[i + 1].point, ]); } return triangles; } doesIntersect(shape) { return exports.Intersection2d.getIntersections(this, shape).length > 0; } getIntersectionPoints(shape) { return exports.Intersection2d.getIntersections(this, shape); } } class Triangle2d extends Polygon2d { constructor(item) { if (item[0] instanceof Point2d) { if (item.length != 3) { throw new Error("Expected number of vertices is 3"); } super(item); } else { if (item.length != 3) { throw new Error("Expected number of edges is 3"); } super(item); } } get area() { const b = this.edges[0].length; const h = this.edges[0].getOrthogonalLineThrough(this.edges[1].p2).length; return Number.parseFloat(((b * h) / 2).toFixed(2)); } get medians() { return [ new Line2d(this.edges[0].p1, this.edges[1].centroid), new Line2d(this.edges[1].p1, this.edges[2].centroid), new Line2d(this.edges[2].p1, this.edges[0].centroid), ]; } get centroid() { const medians = this.medians; return medians[0].getIntersectionPoints(medians[1])[0]; } get circumCenter() { const length = Math.max(...this.edges.map((e) => e.length)); const ortho1 = this.edges[0].getOrthogonalLineFrom(this.edges[0].centroid, length, true); const ortho2 = this.edges[1].getOrthogonalLineFrom(this.edges[1].centroid, length); return exports.Intersection2d.getLineLineIntersection(ortho1, ortho2); } get orthoCenter() { const ortho1 = this.edges[1].getOrthogonalLineThrough(this.edges[0].p1); const ortho2 = this.edges[2].getOrthogonalLineThrough(this.edges[1].p1); return exports.Intersection2d.getLineLineIntersection(ortho1, ortho2); } normalize(line1, line2) { const intersection = line1.getIntersectionPoints(line2)[0]; if (intersection.equals(line1.p1)) { if (intersection.equals(line2.p1)) { return [line1, line2]; } if (intersection.equals(line2.p2)) { return [line1, new Line2d(line2.p2, line2.p1)]; } } if (intersection.equals(line1.p2)) { if (intersection.equals(line2.p1)) { return [new Line2d(line1.p2, line1.p1), line2]; } if (intersection.equals(line2.p2)) { return [new Line2d(line1.p2, line1.p1), new Line2d(line2.p2, line2.p1)]; } } return []; } get isRight() { const n1 = this.normalize(this.edges[0], this.edges[1]); const n2 = this.normalize(this.edges[1], this.edges[2]); const n3 = this.normalize(this.edges[2], this.edges[0]); const angles = [ n1[0].angleTo(n1[1], false), n2[0].angleTo(n2[1], false), n3[0].angleTo(n3[1], false), ]; return angles.some((a) => a === 90); } get isIsoceles() { const lengths = this.edges.map((e) => e.length); return (!this.isEquilateral && (lengths[0] === lengths[1] || lengths[0] === lengths[2] || lengths[1] === lengths[2])); } } class Quadiralteral2d extends Polygon2d { constructor(item) { if (item[0] instanceof Point2d) { if (item.length != 4) { throw new Error("Expected number of vertices is 4"); } super(item); } else { if (item.length != 4) { throw new Error("Expected number of edges is 4"); } super(item); } } get centroid() { var diag1 = new Line2d(this.vertices[0], this.vertices[2]); var diag2 = new Line2d(this.vertices[1], this.vertices[3]); return diag1.getIntersectionPoints(diag2)[0]; } } class Rect2d extends Quadiralteral2d { constructor(location, width, height) { const vertices = [ location, new Point2d(location.x + width, location.y), new Point2d(location.x + width, location.y + height), new Point2d(location.x, location.y + height), ]; super(vertices); this._location = location; this.width = width; this.height = height; } get location() { return this._location; } get area() { return this.width * this.height; } get perimeter() { return this.width * 2 + this.height * 2; } } class Square2d extends Quadiralteral2d { constructor(location, width) { const vertices = [ location, new Point2d(location.x + width, location.y), new Point2d(location.x + width, location.y + width), new Point2d(location.x, location.y + width), ]; super(vertices); this._location = location; this.width = width; } get location() { return this._location; } get height() { return this.width; } set height(value) { this.width = value; } get area() { return this.width * this.width; } get perimeter() { return this.width * 4; } } class Circle2d { static getPerimeter(radius) { return 2 * Math.PI * radius; } static getArea(radius) { return Math.PI * Math.pow(radius, 2); } constructor(center, radius) { this._center = center; this.radius = radius; } get center() { return this._center; } get perimeter() { return Number.parseFloat(Circle2d.getPerimeter(this.radius).toFixed(2)); } get diameter() { return 2 * this.radius; } get area() { return Number.parseFloat(Circle2d.getArea(this.radius).toFixed(2)); } translate(vector) { this._center.translate(vector); } rotate(angle, origin = new Point2d()) { if (angle === 0 || angle === 360) { return; } this._center.rotate(angle, origin); } isOnEdge(point, threshold = 0) { return Math.abs(this.radius - this._center.distanceTo(point)) <= threshold; } contains(point) { return this.radius >= this._center.distanceTo(point); } doesIntersect(shape) { return exports.Intersection2d.getIntersections(this, shape).length > 0; } getIntersectionPoints(shape) { return exports.Intersection2d.getIntersections(this, shape); } } class Ellipse2d { static getPerimeter(semiMajorAxis, semiMinorAxis) { return (2 * Math.PI * Math.sqrt((semiMajorAxis * semiMajorAxis + semiMinorAxis * semiMinorAxis) / 2.0)); } static getArea(semiMajorAxis, semiMinorAxis) { return Math.PI * semiMajorAxis * semiMinorAxis; } constructor(center, width, height) { this._center = center; this.width = width; this.height = height; } get center() { return this._center; } get perimeter() { return Number.parseFloat(Ellipse2d.getPerimeter(this.width / 2, this.height / 2).toFixed(2)); } get area() { return Number.parseFloat(Ellipse2d.getArea(this.width / 2, this.height / 2).toFixed(2)); } get eccentricity() { const a = this.width / 2; const b = this.height / 2; const ecc = Math.sqrt(a * a - b * b); return Number.parseFloat(ecc.toFixed(2)); } get f1() { const e = this.eccentricity; return new Point2d(this._center.x - e, this._center.y); } get f2() { const e = this.eccentricity; return new Point2d(this._center.x + e, this._center.y); } translate(vector) { this._center.translate(vector); } rotate(angle, origin = new Point2d()) { if (angle === 0 || angle === 360) { return; } this._center.rotate(angle, origin); } computeCheckpoint(point) { const a = this.width / 2; const b = this.height / 2; return (Math.pow(point.x - this._center.x, 2) / Math.pow(a, 2) + Math.pow(point.y - this._center.y, 2) / Math.pow(b, 2)); } isOnEdge(point, threshold = 0) { const c = this.computeCheckpoint(point); return c >= 1 - threshold && c <= 1 + threshold; } contains(point) { const c = this.computeCheckpoint(point); return c <= 1; } doesIntersect(shape) { return exports.Intersection2d.getIntersections(this, shape).length > 0; } getIntersectionPoints(shape) { return exports.Intersection2d.getIntersections(this, shape); } } class Arc2d { constructor(from, to, center) { this._from = from; this._to = to; this._center = center; } get from() { return this._from; } get to() { return this._to; } get center() { return this._center; } get isCircular() { const r1 = this.fromToCenter; const r2 = this.toToCenter; return r1 === r2; } get angle() { return Line2d.getAngleBetween(this._center.x, this._center.y, this._from.x, this._from.y, this._center.x, this._center.y, this._to.x, this._to.y, false); } get length() { const r1 = this.fromToCenter; const r2 = this.toToCenter; const angle = this.angle; const isCircular = this.isCircular; const perimiter = isCircular ? Circle2d.getPerimeter(r1) : Ellipse2d.getPerimeter(r1, r2); const length = (perimiter * angle) / 360; return Number.parseFloat(length.toFixed(2)); } get fromToCenter() { return Point2d.getDistance(this._from.x, this._from.y, this._center.x, this._center.y); } get toToCenter() { return Point2d.getDistance(this._to.x, this._to.y, this._center.x, this._center.y); } translate(vector) { this._to.translate(vector); this._from.translate(vector); this._center.translate(vector); } rotate(angle, origin = new Point2d()) { this._to.rotate(angle, origin); this._from.rotate(angle, origin); this._center.rotate(angle, origin); } isOnEdge(point, threshold = 0) { const inRadar = exports.Intersection2d.isPointInRadar(point, this.from, this.to, this.center, this.angle); if (!inRadar) { return false; } const r1 = this.fromToCenter; const r2 = this.toToCenter; const baseShape = this.isCircular ? new Circle2d(this._center, r1) : new Ellipse2d(this._center, r1 * 2, r2 * 2); return baseShape.isOnEdge(point, threshold); } doesIntersect(shape) { return this.getIntersectionPoints(shape).length > 0; } getIntersectionPoints(shape) { return exports.Intersection2d.getIntersections(this, shape); } } class Sector2d extends Arc2d { constructor(from, to, center) { super(from, to, center); } get area() { const r1 = this.fromToCenter; const r2 = this.toToCenter; const angle = this.angle; const globalArea = this.isCircular ? Circle2d.getArea(r1) : Ellipse2d.getArea(r1, r2); const area = (globalArea * angle) / 360; return Number.parseFloat(area.toFixed(2)); } get perimeter() { return this.length + this.fromToCenter + this.toToCenter; } contains(point) { const inRadar = exports.Intersection2d.isPointInRadar(point, this.from, this.to, this.center, this.angle); if (!inRadar) { return false; } const r1 = this.fromToCenter; const r2 = this.toToCenter; const baseShape = this.isCircular ? new Circle2d(this.center, r1) : new Ellipse2d(this.center, r1 * 2, r2 * 2); return baseShape.contains(point); } getIntersectionPoints(shape) { return exports.Intersection2d.getIntersections(this, shape); } } class Polynomial { constructor(...args) { this._values = [...args].reverse(); } get degree() { return this._values.length - 1; } simplify() { for (let i = this.degree; i >= 0; i--) { if (Math.abs(this._values[i]) <= Polynomial.TOLERANCE) { this._values.pop(); } else { break; } } } getLinearRoot() { const results = []; if (this._values[1] !== 0) { results.push(-this._values[0] / this._values[1]); } return results; } getQuadraticRoots() { const results = []; if (this.degree === 2) { const a = this._values[2]; const b = this._values[1] / a; const c = this._values[0] / a; const d = b * b - 4 * c; if (d > 0) { const e = Math.sqrt(d); results.push(0.5 * (-b + e)); results.push(0.5 * (-b - e)); } else if (d === 0) { results.push(0.5 * -b); } } return results; } getCubicRoots() { const results = []; if (this.degree === 3) { const c3 = this._values[3]; const c2 = this._values[2] / c3; const c1 = this._values[1] / c3; const c0 = this._values[0] / c3; const a = (3 * c1 - c2 * c2) / 3; const b = (2 * c2 * c2 * c2 - 9 * c1 * c2 + 27 * c0) / 27; const offset = c2 / 3; const halfB = b / 2; let discrim = (b * b) / 4 + (a * a * a) / 27; if (Math.abs(discrim) <= Polynomial.TOLERANCE) { discrim = 0; } if (discrim > 0) { const e = Math.sqrt(discrim); let root; let tmp = -halfB + e; if (tmp >= 0) root = Math.pow(tmp, 1 / 3); else root = -Math.pow(-tmp, 1 / 3); tmp = -halfB - e; if (tmp >= 0) root += Math.pow(tmp, 1 / 3); else root -= Math.pow(-tmp, 1 / 3); results.push(root - offset); } else if (discrim < 0) { const distance = Math.sqrt(-a / 3); const angle = Math.atan2(Math.sqrt(-discrim), -halfB) / 3; const cos = Math.cos(angle); const sin = Math.sin(angle); const sqrt3 = Math.sqrt(3); results.push(2 * distance * cos - offset); results.push(-distance * (cos + sqrt3 * sin) - offset); results.push(-distance * (cos - sqrt3 * sin) - offset); } else { let tmp; if (halfB >= 0) tmp = -Math.pow(halfB, 1 / 3); else tmp = Math.pow(-halfB, 1 / 3); results.push(2 * tmp - offset); results.push(-tmp - offset); } } return results; } getQuarticRoots() { const results = []; if (this.degree === 4) { const c4 = this._values[4]; const c3 = this._values[3] / c4; const c2 = this._values[2] / c4; const c1 = this._values[1] / c4; const c0 = this._values[0] / c4; const resolveRoots = new Polynomial(1, -c2, c3 * c1 - 4 * c0, -c3 * c3 * c0 + 4 * c2 * c0 - c1 * c1).getCubicRoots(); const y = resolveRoots[0]; let discrim = (c3 * c3) / 4 - c2 + y; if (Math.abs(discrim) <= Polynomial.TOLERANCE) { discrim = 0; } if (discrim > 0) { const e = Math.sqrt(discrim); const t1 = (3 * c3 * c3) / 4 - e * e - 2 * c2; const t2 = (4 * c3 * c2 - 8 * c1 - c3 * c3 * c3) / (4 * e); let plus = t1 + t2; let minus = t1 - t2; if (Math.abs(plus) <= Polynomial.TOLERANCE) { plus = 0; } if (Math.abs(minus) <= Polynomial.TOLERANCE) { minus = 0; } if (plus >= 0) { const f = Math.sqrt(plus); results.push(-c3 / 4 + (e + f) / 2); results.push(-c3 / 4 + (e - f) / 2); } if (minus >= 0) { const f = Math.sqrt(minus); results.push(-c3 / 4 + (f - e) / 2); results.push(-c3 / 4 - (f + e) / 2); } } else if (discrim < 0) ; else { let t2 = y * y - 4 * c0; if (t2 >= -Polynomial.TOLERANCE) { if (t2 < 0) t2 = 0; t2 = 2 * Math.sqrt(t2); let t1 = (3 * c3 * c3) / 4 - 2 * c2; if (t1 + t2 >= Polynomial.TOLERANCE) { const d = Math.sqrt(t1 + t2); results.push(-c3 / 4 + d / 2); results.push(-c3 / 4 - d / 2); } if (t1 - t2 >= Polynomial.TOLERANCE) { const d = Math.sqrt(t1 - t2); results.push(-c3 / 4 + d / 2); results.push(-c3 / 4 - d / 2); } } } } return results; } getRoots() { this.simplify(); switch (this.degree) { case 1: return this.getLinearRoot(); case 2: return this.getQuadraticRoots(); case 3: return this.getCubicRoots(); case 4: return this.getQuarticRoots(); default: return []; } } } Polynomial.TOLERANCE = 1e-6; Polynomial.ACCURACY = 6; exports.Intersection2d = void 0; (function (Intersection2d) { function removeDuplicatedPoints(points) { const map = new Map(); for (const item of points) { map.set(`(${item.x}x${item.y})`, item); } return [...map.values()]; } function getLineIntersections(line, shape) { let intersections = []; switch (shape.constructor) { case Line2d: const point = getSegmentSegmentIntersection(shape, line); if (point !== null) intersections.push(point); break; case Polygon2d: case Triangle2d: case Quadiralteral2d: case Rect2d: case Square2d: const polygon = shape; polygon.edges.forEach((edge1) => { const point = getSegmentSegmentIntersection(edge1, line); if (point !== null) intersections.push(point); }); break; case Circle2d: return getLineCircleIntersections(line, shape); case Ellipse2d: return getLineEllipseIntersections(line, shape); case Arc2d: return getArcLineIntersections(shape, line); case Sector2d: return getSectorLineIntersections(shape, line); } return removeDuplicatedPoints(intersections); } function getPolygonIntersections(polygon, shape) { let intersections = []; switch (shape.constructor) { case Line2d: const line = shape; polygon.edges.forEach((edge) => { const point = getSegmentSegmentIntersection(edge, line); if (point !== null) intersections.push(point); }); break; case Polygon2d: case Triangle2d: case Quadiralteral2d: case Rect2d: case Square2d: const polygon2 = shape; polygon.edges.forEach((edge1) => { polygon2.edges.forEach((edge2) => { const point = getSegmentSegmentIntersection(edge1, edge2); if (point !== null) intersections.push(point); }); }); break; case Circle2d: const circle = shape; polygon.edges.forEach((edge) => { intersections.push(...getCircleLineIntersections(circle, edge)); }); break; case Ellipse2d: const ellipse = shape; polygon.edges.forEach((edge) => { intersections.push(...getEllipseLineIntersections(ellipse, edge)); }); break; case Arc2d: const arc = shape; polygon.edges.forEach((edge) => { intersections.push(...getArcLineIntersections(arc, edge)); }); break; case Sector2d: const sector = shape; polygon.edges.forEach((edge) => { intersections.push(...getSectorLineIntersections(sector, edge)); }); break; } return removeDuplicatedPoints(intersections); } function getCircleIntersections(circle, shape) { let intersections = []; switch (shape.constructor) { case Line2d: return getCircleLineIntersections(circle, shape); case Polygon2d: case Triangle2d: case Quadiralteral2d: case Rect2d: case Square2d: const polygon = shape; polygon.edges.forEach((edge) => { intersections.push(...getCircleLineIntersections(circle, edge)); }); break; case Circle2d: return getCircleCircleIntersections(circle, shape); case Ellipse2d: return getCircleEllipseIntersections(circle, shape); case Arc2d: return getArcCircleIntersections(shape, circle); case Sector2d: return getSectorCircleIntersections(shape, circle); } return removeDuplicatedPoints(intersections); } function getEllipseIntersections(ellipse, shape) { let intersections = []; switch (shape.constructor) { case Line2d: return getEllipseLineIntersections(ellipse, shape); case Polygon2d: case Triangle2d: case Quadiralteral2d: case Rect2d: case Square2d: const polygon = shape; polygon.edges.forEach((edge) => { intersections.push(...getEllipseLineIntersections(ellipse, edge)); }); break; case Circle2d: return getEllipseCircleIntersections(ellipse, shape); case Ellipse2d: return getEllipseEllipseIntersections(ellipse, shape); case Arc2d: return getEllipseArcIntersections(ellipse, shape); case Sector2d: return getEllipseSectorIntersections(ellipse, shape); } return intersections; } function getArcIntersections(arc, shape) { let intersections = []; switch (shape.constructor) { case Line2d: return getArcLineIntersections(arc, shape); case Polygon2d: case Triangle2d: case Quadiralteral2d: case Rect2d: case Square2d: const polygon = shape; polygon.edges.forEach((edge) => { intersections.push(...getArcLineIntersections(arc, edge)); }); break; case Circle2d: return getArcCircleIntersections(arc, shape); case Ellipse2d: return getArcEllipseIntersections(arc, shape); case Arc2d: return getArcArcIntersections(arc, shape); case Sector2d: return getArcSectorIntersections(arc, shape); } return intersections; } function getSectorIntersections(sector, shape) { let intersections = []; switch (shape.constructor) { case Line2d: return getSectorLineIntersections(sector, shape); case Polygon2d: case Triangle2d: case Quadiralteral2d: case Rect2d: case Square2d: const polygon = shape; polygon.edges.forEach((edge) => { intersections.push(...getSectorLineIntersections(sector, edge)); }); break; case Circle2d: return getSectorCircleIntersections(sector, shape); case Ellipse2d: return getSectorEllipseIntersections(sector, shape); case Arc2d: return getArcSectorIntersections(shape, sector); case Sector2d: return getSectorSectorIntersections(sector, shape); } return intersections; } function getIntersections(shape1, shape2) { switch (shape1.constructor) { case Line2d: return getLineIntersections(shape1, shape2); case Polygon2d: case Triangle2d: case Quadiralteral2d: case Rect2d: case Square2d: return getPolygonIntersections(shape1, shape2); case Circle2d: return getCircleIntersections(shape1, shape2); case Ellipse2d: return getEllipseIntersections(shape1, shape2); case Arc2d: return getArcIntersections(shape1, shape2); case Sector2d: return getSectorIntersections(shape1, shape2); } return []; } Intersection2d.getIntersections = getIntersections; function getPointDistanceTo(point, line) { return (Math.abs((line.p2.x - line.p1.x) * (line.p1.y - point.y) - (line.p1.x - point.x) * (line.p2.y - line.p1.y)) / Math.sqrt((line.p2.x - line.p1.x) ** 2 + (line.p2.y - line.p1.y) ** 2)); } Intersection2d.getPointDistanceTo = getPointDistanceTo; function isPointOnLine(point, line, threshold = 0) { return Math.round(getPointDistanceTo(point, line)) <= threshold; } Intersection2d.isPointOnLine = isPointOnLine; function isPointOnSegment(point, line, threshold = 0) { return (isPointOnLine(point, line, threshold) && point.x >= Math.min(line.p1.x, line.p2.x) && point.x <= Math.max(line.p1.x, line.p2.x) && point.y >= Math.min(line.p1.y, line.p2.y) && point.y <= Math.max(line.p1.y, line.p2.y)); } Intersection2d.isPointOnSegment = isPointOnSegment; function getLinesDenominator(line1, line2) { return ((line1.p1.x - line1.p2.x) * (line2.p1.y - line2.p2.y) - (line1.p1.y - line1.p2.y) * (line2.p1.x - line2.p2.x)); } function areLinesParallel(line1, line2) { return getLinesDenominator(line1, line2) === 0; } Intersection2d.areLinesParallel = areLinesParallel; function getLineLineIntersection(line1, line2) { const denom = getLinesDenominator(line1, line2); if (denom === 0) { return null; } const deta = line1.p1.x * line1.p2.y - line1.p1.y * line1.p2.x; const detb = line2.p1.x * line2.p2.y - line2.p1.y * line2.p2.x; const px = (deta * (line2.p1.x - line2.p2.x) - (line1.p1.x - line1.p2.x) * detb) / denom; const py = (deta * (line2.p1.y - line2.p2.y) - (line1.p1.y - line1.p2.y) * detb) / denom; return new Point2d(Number.parseFloat(px.toFixed(2)), Number.parseFloat(py.toFixed(2))); } Intersection2d.getLineLineIntersection = getLineLineIntersection; function getSegmentSegmentIntersection(line1, line2) { const theoricalPoint = Intersection2d.getLineLineIntersection(line1, line2); return theoricalPoint != null && isPointOnSegment(theoricalPoint, line1) && isPointOnSegment(theoricalPoint, line2) ? theoricalPoint : null; } Intersection2d.getSegmentSegmentIntersection = getSegmentSegmentIntersection; function getLineCircleIntersections(line, circle) { return getCircleLineIntersections(circle, line); } Intersection2d.getLineCircleIntersections = getLineCircleIntersections; function getCircleLineIntersections(circle, line) { const c = circle.center; const r = circle.radius; const a1 = line.p1; const a2 = line.p2; const a = (a2.x - a1.x) * (a2.x - a1.x) + (a2.y - a1.y) * (a2.y - a1.y); const b = 2 * ((a2.x - a1.x) * (a1.x - c.x) + (a2.y - a1.y) * (a1.y - c.y)); const cc = c.x * c.x + c.y * c.y + a1.x * a1.x + a1.y * a1.y - 2 * (c.x * a1.x + c.y * a1.y) - r * r; const deter = b * b - 4 * a * cc; if (deter < 0) { return []; } if (deter === 0) { const secant = line.getOrthogonalLineThrough(c); return [getSegmentSegmentIntersection(secant, line)]; } const e = Math.sqrt(deter); const u1 = (-b + e) / (2 * a); const u2 = (-b - e) / (2 * a); if ((u1 < 0 || u1 > 1) && (u2 < 0 || u2 > 1)) { return []; } const intersections = []; if (0 <= u1 && u1 <= 1) { const px1 = a1.x + (a2.x - a1.x) * u1; const py1 = a1.y + (a2.y - a1.y) * u1; intersections.push(new Point2d(Number.parseFloat(px1.toFixed(2)), Number.parseFloat(py1.toFixed(2)))); } if (0 <= u2 && u2 <= 1) { const px2 = a1.x + (a2.x - a1.x) * u2; const py2 = a1.y + (a2.y - a1.y) * u2; intersections.push(new Point2d(Number.parseFloat(px2.toFixed(2)), Number.parseFloat(py2.toFixed(2)))); } return intersections; } Intersection2d.getCircleLineIntersections = getCircleLineIntersections; function getCircleCircleIntersections(circle1, circle2) { const c1 = circle1.center; const r1 = circle1.radius; const c2 = circle2.center; const r2 = circle2.radius; const r_max = r1 + r2; const r_min = Math.abs(r1 - r2); const c_dist = c1.distanceTo(c2); if (c_dist > r_max) { return []; } if (c_dist < r_min) { return []; } const intersections = []; var a = (r1 * r1 - r2 * r2 + c_dist * c_dist) / (2 * c_dist); var h = Math.sqrt(r1 * r1 - a * a); const px = c1.x + (c2.x - c1.x) * (a / c_dist); const py = c1.y + (c2.y - c1.y) * (a / c_dist); var p = new Point2d(px, py); var b = h / c_dist; intersections.push(new Point2d(Number.parseFloat((p.x - b * (c2.y - c1.y)).toFixed(2)), Number.parseFloat((p.y + b * (c2.x - c1.x)).toFixed(2)))); intersections.push(new Point2d(Number.parseFloat((p.x + b * (c2.y - c1.y)).toFixed(2)), Number.parseFloat((p.y - b * (c2.x - c1.x)).toFixed(2)))); return intersections; } Intersection2d.getCircleCircleIntersections = getCircleCircleIntersections; function getLineEllipseIntersections(line, ellipse) { return getEllipseLineIntersections(ellipse, line); } Intersection2d.getLineEllipseIntersections = getLineEllipseIntersections; function getEllipseLineIntersections(ellipse, line) { const dir = new Vector2d(line.p2.x - line.p1.x, line.p2.y - line.p1.y); const diff = new Vector2d(line.p1.x - ellipse.center.x, line.p1.y - ellipse.center.y); const rx = ellipse.width / 2; const ry = ellipse.height / 2; const mDir = new Vector2d(dir.x / (rx * rx), dir.y / (ry * ry)); const mDiff = new Vector2d(diff.x / (rx * rx), diff.y / (ry * ry)); const a = dir.dot(mDir); const b = dir.dot(mDiff); const c = diff.dot(mDiff) - 1.0; const d = b * b - a * c; const intersections = []; if (d > 0) { const t_a = (-b - Math.sqrt(d)) / a; const t_b = (-b + Math.sqrt(d)) / a; if (0 <= t_a && t_a <= 1) { intersections.push(new Point2d(Number.parseFloat((line.p1.x + (line.p2.x - line.p1.x) * t_a).toFixed(2)), Number.parseFloat((line.p1.y + (line.p2.y - line.p1.y) * t_a).toFixed(2)))); } if (0 <= t_b && t_b <= 1) { intersections.push(new Point2d(Number.parseFloat((line.p1.x + (line.p2.x - line.p1.x) * t_b).toFixed(2)), Number.parseFloat((line.p1.y + (line.p2.y - line.p1.y) * t_b).toFixed(2)))); } } else if (d === 0) { const t = -b / a; if (0 <= t && t <= 1) { intersections.push(new Point2d(Number.parseFloat((line.p1.x + (line.p2.x - line.p1.x) * t).toFixed(2)), Number.parseFloat((line.p1.y + (line.p2.y - line.p1.y) * t).toFixed(2)))); } } return intersections; } Intersection2d.getEllipseLineIntersections = getEllipseLineIntersections; function getCircleEllipseIntersections(circle, ellipse) { return getEllipseCircleIntersections(ellipse, circle); } Intersection2d.getCircleEllipseIntersections = getCircleEllipseIntersections; function getEllipseCircleIntersections(ellipse, circle) { return getEllipseEllipseIntersections(ellipse, new Ellipse2d(circle.center, circle.diameter, circle.diameter)); } Intersection2d.getEllipseCircleIntersections = getEllipseCircleIntersections; function bezout(arg1, arg2) { const AB = arg1[0] * arg2[1] - arg2[0] * arg1[1]; const AC = arg1[0] * arg2[2] - arg2[0] * arg1[2]; const AD = arg1[0] * arg2[3] - arg2[0] * arg1[3]; const AE = arg1[0] * arg2[4] - arg2[0] * arg1[4]; const AF = arg1[0] * arg2[5] - arg2[0] * arg1[5]; const BC = arg1[1] * arg2[2] - arg2[1] * arg1[2]; const BE = arg1[1] * arg2[4] - arg2[1] * arg1[4]; const BF = arg1[1] * arg2[5] - arg2[1] * arg1[5]; const CD = arg1[2] * arg2[3] - arg2[2] * arg1[3]; const DE = arg1[3] * arg2[4] - arg2[3] * arg1[4]; const DF = arg1[3] * arg2[5] - arg2[3] * arg1[5]; const BFpDE = BF + DE; const BEmCD = BE - CD; return new Polynomial(AB * BC - AC * AC, AB * BEmCD + AD * BC - 2 * AC * AE, AB * BFpDE + AD * BEmCD - AE * AE - 2 * AC * AF, AB * DF + AD * BFpDE - 2 * AE * AF, AD * DF - AF * AF); } function getEllipseEllipseIntersections(ellipse1, ellipse2) { const c1 = ellipse1.center; const rx1 = ellipse1.width / 2; const ry1 = ellipse1.height / 2; const c2 = ellipse2.center; const rx2 = ellipse2.width / 2; const ry2 = ellipse2.height / 2; const a = [ ry1 * ry1, 0, rx1 * rx1, -2 * ry1 * ry1 * c1.x, -2 * rx1 * rx1 * c1.y, ry1 * ry1 * c1.x * c1.x + rx1 * rx1 * c1.y * c1.y - rx1 * rx1 * ry1 * ry1, ]; const b = [ ry2 * ry2, 0, rx2 * rx2, -2 * ry2 * ry2 * c2.x, -2 * rx2 * rx2 * c2.y, ry2 * ry2 * c2.x * c2.x + rx2 * rx2 * c2.y * c2.y - rx2 * rx2 * ry2 * ry2, ]; const epsilon = 1e-3; const norm0 = (a[0] * a[0] + 2 * a[1] * a[1] + a[2] * a[2]) * epsilon; const norm1 = (b[0] * b[0] + 2 * b[1] * b[1] + b[2] * b[2]) * epsilon; const intersections = []; const yPoly = bezout(a, b); const yRoots = yPoly.getRoots(); for (var y = 0; y < yRoots.length; y++) { const xPoly = new Polynomial(a[0], a[3] + yRoots[y] * a[1], a[5] + yRoots[y] * (a[4] + yRoots[y] * a[2])); const xRoots = xPoly.getRoots(); for (let x = 0; x < xRoots.length; x++) { let test = (a[0] * xRoots[x] + a[1] * yRoots[y] + a[3]) * xRoots[x] + (a[2] * yRoots[y] + a[4]) * yRoots[y] + a[5]; if (Math.abs(test) < norm0) { test = (b[0] * xRoots[x] + b[1] * yRoots[y] + b[3]) * xRoots[x] + (b[2] * yRoots[y] + b[4]) * yRoots[y] + b[5]; if (Math.abs(test) < norm1) { intersections.push(new Point2d(Number.parseFloat(xRoots[x].toFixed(2)), Number.parseFloat(yRoots[y].toFixed(2)))); } } } } return intersections; } Intersection2d.getEllipseEllipseIntersections = getEllipseEllipseIntersections; function isPointInRadar(point, from, to, center, angle) { const fromAngle = Line2d.getAngleBetween(center.x, center.y, from.x, from.y, center.x, center.y, point.x, point.y, false); const toAngle = Line2d.getAngleBetween(center.x, center.y, to.x, to.y, center.x, center.y, point.x, point.y, false); if (!angle) { angle = Line2d.getAngleBetween(center.x, center.y, from.x, from.y, center.x, center.y, to.x, to.y, false); } return fromAngle + toAngle === angle; } Intersection2d.isPointInRadar = isPointInRadar; function getLineArcIntersections(line, arc) { return getArcLineIntersections(arc, line); } Intersection2d.getLineArcIntersections = getLineArcIntersections; function getArcLineIntersections(arc, line) { const intersections = arc.isCircular ? getCircleLineIntersections(new Circle2d(arc.center, arc.from.distanceTo(arc.center)), line) : getEllipseLineIntersections(new Ellipse2d(arc.center, arc.from.distanceTo(arc.center) * 2, arc.to.distanceTo(arc.center) * 2), line); return intersections.filter((point) => isPointInRadar(point, arc.from, arc.to, arc.center, arc.angle)); } Intersection2d.getArcLineIntersections = getArcLineIntersections; function getCircleArcIntersections(circle, arc) { return getArcCircleIntersections(arc, circle); } Intersection2d.getCircleArcIntersections = getCircleArcIntersections; function getArcCircleIntersections(arc, circle) { const intersections = arc.isCircular ? getCircleCircleIntersections(new Circle2d(arc.center, arc.from.distanceTo(arc.center)), circle) : getEllipseCircleIntersections(new Ellipse2d(arc.center, arc.from.distanceTo(arc.center) * 2, arc.to.distanceTo(arc.center) * 2), circle); return intersections.filter((point) => isPointInRadar(point, arc.from, arc.to, arc.center, arc.angle)); } Intersection2d.getArcCircleIntersections = getArcCircleIntersections; function getEllipseArcIntersections(ellipse, arc) { return getArcEllipseIntersections(arc, ellipse); } Intersection2d.getEllipseArcIntersections = getEllipseArcIntersections; function getArcEllipseIntersections(arc, ellipse) { const intersections = arc.isCircular ? getCircleEllipseIntersections(new Circle2d(arc.center, arc.from.distanceTo(arc.center)), ellipse) : getEllipseEllipseIntersections(new Ellipse2d(arc.center, arc.from.distanceTo(arc.center) * 2, arc.to.distanceTo(arc.center) * 2), ellipse); return removeDuplicatedPoints(intersections.filter((point) => isPointInRadar(point, arc.from, arc.to, arc.center, arc.angle))); } Intersection2d.getArcEllipseIntersections = getArcEllipseIntersections; function getArcArcIntersections(arc1, arc2) { const arc1Circular = arc1.isCircular; const arc2Circular = arc2.isCircular; let intersections = []; if (arc1Circular && !arc2Circular) { intersections = getCircleEllipseIntersections(new Circle2d(arc1.center, arc1.from.distanceTo(arc1.center)), new Ellipse2d(arc2.center, arc2.from.distanceTo(arc2.center) * 2, arc2.to.distanceTo(arc2.center) * 2)); } if (!arc1Circular && arc2Circular) { intersections = getEllipseCircleIntersections(new Ellipse2d(arc1.center, arc1.from.distanceTo(arc1.center) * 2, arc1.to.distanceTo(arc1.center) * 2), new Circle2d(arc2.center, arc2.from.distanceTo(arc2.center))); } if (arc1Circular && arc2Circular) { intersections = getCircleCircleIntersections(new Circle2d(arc1.center, arc1.from.distanceTo(arc1.center)), new Circle2d(arc2.center, arc2.from.distanceTo(arc2.center))); } return removeDuplicatedPoints(intersections.filter((point) => isPointInRadar(point, arc1.from, arc1.to, arc1.center, arc1.angle) && isPointInRadar(point, arc2.from, arc2.to, arc2.center, arc2.angle))); } Intersection2d.getArcArcIntersections = getArcArcIntersections; function getLineSectorIntersections(line, sector) { return getSectorLineIntersections(sector, line); } Intersection2d.getLineSectorIntersections = getLineSectorIntersections; function getSectorLineIntersections(sector, line) { const arcIntersections = getArcLineIntersections(sector, line); const fromIntersection = getSegmentSegmentIntersection(new Line2d(sector.center, sector.from), line); if (fromIntersection !== null) { arcIntersections.push(fromIntersection); } const toIntersection = getSegmentSegmentIntersection(new Line2d(sector.center, sector.to), line); if (toIntersection !== null) { arcIntersections.push(toIntersection); } return removeDuplicatedPoints(arcIntersections); } Intersection2d.getSectorLineIntersections = getSectorLineIntersections; function getCircleSectorIntersections(circle, sector) { return getSectorCircleIntersections(sector, circle); } Intersection2d.getCircleSectorIntersections = getCircleSectorIntersections; function getSectorCircleIntersections(sector, circle) { const arcIntersections = getArcCircleIntersections(sector, circle); const fromIntersections = getLineCircleIntersections(new Line2d(sector.center, sector.from), circle); const toIntersections = getLineCircleIntersections(new Line2d(sector.center, sector.to), circle); return removeDuplicatedPoints(arcIntersections.concat(fromIntersections, toIntersections)); } Intersection2d.getSectorCircleIntersections = getSectorCircleIntersections; function getEllipseSectorIntersections(ellipse, sector) { return getSectorEllipseIntersections(sector, ellipse); } Intersection2d.getEllipseSectorIntersections = getEllipseSectorIntersections; function getSectorEllipseIntersections(sector, ellipse) { const arcIntersections = getArcEllipseIntersections(sector, ellipse); const fromIntersections = getLineEllipseIntersections(new Line2d(sector.center, sector.from), ellipse); const toIntersections = getLineEllipseIntersections(new Line2d(sector.center, sector.to), ellipse); return removeDuplicatedPoints(arcIntersections.concat(fromIntersections, toIntersections)); } Intersection2d.getSectorEllipseIntersections = getSectorEllipseIntersections; function getArcSectorIntersections(arc, sector) { return getSectorArcIntersections(sector, arc); } Intersection2d.getArcSectorIntersections = getArcSectorIntersections; function getSectorArcIntersections(sector, arc) { const arcIntersections = getArcArcIntersections(sector, arc); const fromIntersections = getLineArcIntersections(new Line2d(sector.center, sector.from), arc); const toIntersections = getLineArcIntersections(new Line2d(sector.center, sector.to), arc); return removeDuplicatedPoints(arcIntersections.concat(fromIntersections, toIntersections)); } Intersection2d.getSectorArcIntersections = getSectorArcIntersections; function getSectorSectorIntersections(sector1, sector2) { const arcIntersections = getArcArcIntersections(sector1, sector2); const sector1Lines = [ new Line2d(sector1.center, sector1.from), new Line2d(sector1.center, sector1.to), ]; const sector2Lines = [ new Line2d(sector2.center, sector2.from), new Line2d(sector2.center, sector2.to), ]; const lineIntersections = []; sector1Lines.forEach((s1) => { const match1 = getSegmentSegmentIntersection(s1, sector2Lines[0]); if (match1 !== null) { lineIntersections.push(match1); } const match2 = getSegmentSegmentIntersection(s1, sector2Lines[1]); if (match2 !== null) { lineIntersections.push(match2); } }); return removeDuplicatedPoints(arcIntersections.concat(lineIntersections)); } Intersection2d.getSectorSectorIntersections = getSectorSectorIntersections; })(exports.Intersection2d || (exports.Intersection2d = {})); class Line2d { constructor(p1, p2) { this._p1 = p1; this._p2 = p2; } static getLength(p1x, p1y, p2x, p2y) { return Point2d.getDistance(p1x, p1y, p2x, p2y); } static reorder(l1, l2) { const ac = Line2d.getLength(l2.p1.x, l2.p1.y, l1.p1.x, l1.p1.y); const ad = Line2d.getLength(l2.p1.x, l2.p1.y, l1.p2.x, l1.p2.y); const bc = Line2d.getLength(l2.p2.x, l2.p2.y, l1.p1.x, l1.p1.y); const bd = Line2d.getLength(l2.p2.x, l2.p2.y, l1.p2.x, l1.p2.y); const min = Math.min(ac, ad, bc, bd); switch (min) { case bc: case ad: return new Line2d(l1.p2, l1.p1); case ac: case bd: default: return l1; } } static getAngleBetween(p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y, keepOrientation = true) { let l1 = new Line2d(new Point2d(p1x, p1y), new Point2d(p2x, p2y)); let l2 = Line2d.reorder(new Line2d(new Point2d(p3x, p3y), new Point2d(p4x, p4y)), l1); const dAx = l1.p2.x - l1.p1.x; const dAy = l1.p2.y - l1.p1.y; const dBx = l2.p2.x - l2.p1.x; const dBy = l2.p2.y - l2.p1.y; const angleRad = Math.atan2(dAx * dBy - dAy * dBx, dAx * dBx + dAy * dBy); const angleDeg = Number.parseFloat((angleRad * (180 / Math.PI)).toFixed(2)); return keepOrientation ? angleDeg : Math.abs(angleDeg); } get p1() { return this._p1; } get p2() { return this._p2; } get length() { return Line2d.getLength(this.p1.x, this.p1.y, this.p2.x, this.p2.y); } get centroid() { return new Point2d((this.p1.x + this.p2.x) / 2, (this.p1.y + this.p2.y) / 2); } get isVertical() { return this.p2.x - this.p1.x === 0; } get isHorizontal() { return this.p2.y - this.p1.y === 0; } clone() { return new Line2d(this._p1.clone(), this._p2.clone()); } translate(vector) { this.p1.translate(vector); this.p2.translate(vector); } rotate(angle, origin = new Point2d()) { if (angle === 0 || angle === 360) { return; } this.p1.rotate(angle, origin); this.p2.rotate(angle, origin); } dot(line) { const vt = new Vector2d(this.p1.x - line.p1.x, this.p1.y - line.p1.y); const lp = line.clone(); lp.translate(vt); const v1 = new Vector2d(this.p2.x, this.p2.y); const v2 = new Vector2d(lp.p2.x, lp.p2.y); return v1.dot(v2); } goesSameDirection(line) { return this.dot(line) > 0; } angleTo(line, keepOrientation = true) { return Line2d.getAngleBetween(this.p1.x, this.p1.y, this.p2.x, this.p2.y, line.p1.x, line.p1.y, line.p2.x, line.p2.y, keepOrientation); } getLineAtAngle(angle, length = 20) { if (this.isHorizontal && (angle === 0 || Math.abs(angle) === 360)) { return new Line2d(this.p1.clone(), new Point2d(this.p1.x + length, this.p1.y)); } if (this.isVertical) { if (angle === 0 || Math.abs(angle) === 360) { return new Line2d(this.p1.clone(), new Point2d(this.p1.x, this.p1.y + length)); } else if (Math.abs(angle) === 180) { return new Line2d(this.p1.clone(), new Point2d(this.p1.x, this.p1.y - length)); } } const angleRad = angle * (Math.PI / 180); const invert = this.isVertical; const x = Number.parseFloat((this.p1.x + Math.cos(angleRad) * length).toFixed(3)); const y = Number.parseFloat((this.p1.y + Math.sin(angleRad) * length).toFixed(3)); return new Line2d(this.p1.clone(), new Point2d(invert ? -y : x, invert ? x : y)); } getOrthogonalLineThrough(p3) { const p1 = this.p1; const p2 = this.p2; let p4 = new Point2d(); if (this.isHorizontal) { p4.x = p3.x; p4.y = p1.y; } else if (this.isVertical) { p4.x = p1.x; p4.y = p3.y; } else { var gradientofThis = (p1.y - p2.y) / (p1.x - p2.x); var interceptOfThis = p1.y - gradientofThis * p1.x; var gradientOfOrtho = -1 / gradientofThis; var interceptOfOrtho = p3.y - gradientOfOrtho * p3.x; p4.x = (interceptOfThis - interceptOfOrtho) / (gradientOfOrtho - gradientofThis); p4.y = gradientOfOrtho * p4.x + interceptOfOrtho; } return new Line2d(p4, p3); } getOrthogonalLineFrom(p3, length = 20, clockwise = false) { const p1 = this.p1; if (!this.isOnLine(p3)) { return null; } let p4 = new Point2d(); if (this.isHorizontal) { p4.x = p3.x; p4.y = p1.y + (clockwise === true ? -length : length); } else if (this.isVertical) { p4.x = p1.x + (clockwise === true ? length : -length); p4.y = p3.y; } else { const a = this.angleTo(new Line2d(p1, new Point2d(p1.x + length, p1.y))); const teta = (90 - a) / (180 / Math.PI); p4.x = Number.parseFloat((p3.x + length * Math.cos(teta) * (clockwise ? -1 : 1)).toFixed(2)); p4.y = Number.parseFloat((p3.y + length * Math.sin(teta) * (clockwise ? -1 : 1)).toFixed(2)); } return new Line2d(p3, p4); } isOrthogonalTo(line) { let m1, m2; if (this.isVertical && line.isVertical) { return false; } if (this.isVertical) { m2 = (line.p2.y - line.p1.y) / (line.p2.x - line.p1.x); return m2 === 0; } if (line.isVertical) { m1 = (this.p2.y - this.p1.y) / (this.p2.x - this.p1.x); return m1 === 0; } m1 = (this.p2.y - this.p1.y) / (this.p2.x - this.p1.x); m2 = (line.p2.y - line.p1.y) / (line.p2.x - line.p1.x); return m1 * m2 === -1; } isOnLine(point, threshold = 0) { return exports.Intersection2d.isPointOnLine(point, this, threshold); } isOnEdge(point, threshold = 0) { return exports.Intersection2d.isPointOnSegment(point, this, threshold); } isParallelTo(line) { return exports.Intersection2d.areLinesParallel(this, line); } doesIntersect(shape) { return exports.Intersection2d.getIntersections(this, shape).length > 0; } getIntersectionPoints(shape) { return exports.Intersection2d.getIntersections(this, shape); } } exports.Arc2d = Arc2d; exports.Circle2d = Circle2d; exports.Ellipse2d = Ellipse2d; exports.Line2d = Line2d; exports.Point2d = Point2d; exports.Polygon2d = Polygon2d; exports.Quadiralteral2d = Quadiralteral2d; exports.Rect2d = Rect2d; exports.Sector2d = Sector2d; exports.Square2d = Square2d; exports.Triangle2d = Triangle2d; exports.Vector2d = Vector2d; }));