fabric
Version:
Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.
266 lines (251 loc) • 9.3 kB
JavaScript
import { Point } from './Point.mjs';
import { createVector } from './util/misc/vectors.mjs';
/* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */
class Intersection {
constructor(status) {
this.status = status;
this.points = [];
}
/**
* Used to verify if a point is alredy in the collection
* @param {Point} point
* @returns {boolean}
*/
includes(point) {
return this.points.some(p => p.eq(point));
}
/**
* Appends points of intersection
* @param {...Point[]} points
* @return {Intersection} thisArg
* @chainable
*/
append() {
for (var _len = arguments.length, points = new Array(_len), _key = 0; _key < _len; _key++) {
points[_key] = arguments[_key];
}
this.points = this.points.concat(points.filter(point => {
return !this.includes(point);
}));
return this;
}
/**
* check if point T is on the segment or line defined between A and B
*
* @param {Point} T the point we are checking for
* @param {Point} A one extremity of the segment
* @param {Point} B the other extremity of the segment
* @param [infinite] if true checks if `T` is on the line defined by `A` and `B`
* @returns true if `T` is contained
*/
static isPointContained(T, A, B) {
let infinite = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
if (A.eq(B)) {
// Edge case: the segment is a point, we check for coincidence,
// infinite param has no meaning because there are infinite lines to consider
return T.eq(A);
} else if (A.x === B.x) {
// Edge case: horizontal line.
// we first check if T.x has the same value, and then if T.y is contained between A.y and B.y
return T.x === A.x && (infinite || T.y >= Math.min(A.y, B.y) && T.y <= Math.max(A.y, B.y));
} else if (A.y === B.y) {
// Edge case: vertical line.
// we first check if T.y has the same value, and then if T.x is contained between A.x and B.x
return T.y === A.y && (infinite || T.x >= Math.min(A.x, B.x) && T.x <= Math.max(A.x, B.x));
} else {
// Generic case: sloped line.
// we check that AT has the same slope as AB
// for the segment case we need both the vectors to have the same direction and for AT to be lte AB in size
// for the infinite case we check the absolute value of the slope, since direction is meaningless
const AB = createVector(A, B);
const AT = createVector(A, T);
const s = AT.divide(AB);
return infinite ? Math.abs(s.x) === Math.abs(s.y) : s.x === s.y && s.x >= 0 && s.x <= 1;
}
}
/**
* Use the ray casting algorithm to determine if {@link Point} is in the polygon defined by [points]{@link Point}
* @see https://en.wikipedia.org/wiki/Point_in_polygon
* @param point
* @param points polygon points
* @returns
*/
static isPointInPolygon(point, points) {
const other = new Point(point).setX(Math.min(point.x - 1, ...points.map(p => p.x)));
let hits = 0;
for (let index = 0; index < points.length; index++) {
const inter = this.intersectSegmentSegment(
// polygon side
points[index], points[(index + 1) % points.length],
// ray
point, other);
if (inter.includes(point)) {
// point is on the polygon side
return true;
}
hits += Number(inter.status === 'Intersection');
}
return hits % 2 === 1;
}
/**
* Checks if a line intersects another
* @see {@link https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection line intersection}
* @see {@link https://en.wikipedia.org/wiki/Cramer%27s_rule Cramer's rule}
* @static
* @param {Point} a1
* @param {Point} a2
* @param {Point} b1
* @param {Point} b2
* @param {boolean} [aInfinite=true] check segment intersection by passing `false`
* @param {boolean} [bInfinite=true] check segment intersection by passing `false`
* @return {Intersection}
*/
static intersectLineLine(a1, a2, b1, b2) {
let aInfinite = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true;
let bInfinite = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : true;
const a2xa1x = a2.x - a1.x,
a2ya1y = a2.y - a1.y,
b2xb1x = b2.x - b1.x,
b2yb1y = b2.y - b1.y,
a1xb1x = a1.x - b1.x,
a1yb1y = a1.y - b1.y,
uaT = b2xb1x * a1yb1y - b2yb1y * a1xb1x,
ubT = a2xa1x * a1yb1y - a2ya1y * a1xb1x,
uB = b2yb1y * a2xa1x - b2xb1x * a2ya1y;
if (uB !== 0) {
const ua = uaT / uB,
ub = ubT / uB;
if ((aInfinite || 0 <= ua && ua <= 1) && (bInfinite || 0 <= ub && ub <= 1)) {
return new Intersection('Intersection').append(new Point(a1.x + ua * a2xa1x, a1.y + ua * a2ya1y));
} else {
return new Intersection();
}
} else {
if (uaT === 0 || ubT === 0) {
const segmentsCoincide = aInfinite || bInfinite || Intersection.isPointContained(a1, b1, b2) || Intersection.isPointContained(a2, b1, b2) || Intersection.isPointContained(b1, a1, a2) || Intersection.isPointContained(b2, a1, a2);
return new Intersection(segmentsCoincide ? 'Coincident' : undefined);
} else {
return new Intersection('Parallel');
}
}
}
/**
* Checks if a segment intersects a line
* @see {@link intersectLineLine} for line intersection
* @static
* @param {Point} s1 boundary point of segment
* @param {Point} s2 other boundary point of segment
* @param {Point} l1 point on line
* @param {Point} l2 other point on line
* @return {Intersection}
*/
static intersectSegmentLine(s1, s2, l1, l2) {
return Intersection.intersectLineLine(s1, s2, l1, l2, false, true);
}
/**
* Checks if a segment intersects another
* @see {@link intersectLineLine} for line intersection
* @static
* @param {Point} a1 boundary point of segment
* @param {Point} a2 other boundary point of segment
* @param {Point} b1 boundary point of segment
* @param {Point} b2 other boundary point of segment
* @return {Intersection}
*/
static intersectSegmentSegment(a1, a2, b1, b2) {
return Intersection.intersectLineLine(a1, a2, b1, b2, false, false);
}
/**
* Checks if line intersects polygon
*
* @todo account for stroke
*
* @static
* @see {@link intersectSegmentPolygon} for segment intersection
* @param {Point} a1 point on line
* @param {Point} a2 other point on line
* @param {Point[]} points polygon points
* @param {boolean} [infinite=true] check segment intersection by passing `false`
* @return {Intersection}
*/
static intersectLinePolygon(a1, a2, points) {
let infinite = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
const result = new Intersection();
const length = points.length;
for (let i = 0, b1, b2, inter; i < length; i++) {
b1 = points[i];
b2 = points[(i + 1) % length];
inter = Intersection.intersectLineLine(a1, a2, b1, b2, infinite, false);
if (inter.status === 'Coincident') {
return inter;
}
result.append(...inter.points);
}
if (result.points.length > 0) {
result.status = 'Intersection';
}
return result;
}
/**
* Checks if segment intersects polygon
* @static
* @see {@link intersectLinePolygon} for line intersection
* @param {Point} a1 boundary point of segment
* @param {Point} a2 other boundary point of segment
* @param {Point[]} points polygon points
* @return {Intersection}
*/
static intersectSegmentPolygon(a1, a2, points) {
return Intersection.intersectLinePolygon(a1, a2, points, false);
}
/**
* Checks if polygon intersects another polygon
*
* @todo account for stroke
*
* @static
* @param {Point[]} points1
* @param {Point[]} points2
* @return {Intersection}
*/
static intersectPolygonPolygon(points1, points2) {
const result = new Intersection(),
length = points1.length;
const coincidences = [];
for (let i = 0; i < length; i++) {
const a1 = points1[i],
a2 = points1[(i + 1) % length],
inter = Intersection.intersectSegmentPolygon(a1, a2, points2);
if (inter.status === 'Coincident') {
coincidences.push(inter);
result.append(a1, a2);
} else {
result.append(...inter.points);
}
}
if (coincidences.length > 0 && coincidences.length === points1.length) {
return new Intersection('Coincident');
} else if (result.points.length > 0) {
result.status = 'Intersection';
}
return result;
}
/**
* Checks if polygon intersects rectangle
* @static
* @see {@link intersectPolygonPolygon} for polygon intersection
* @param {Point[]} points polygon points
* @param {Point} r1 top left point of rect
* @param {Point} r2 bottom right point of rect
* @return {Intersection}
*/
static intersectPolygonRectangle(points, r1, r2) {
const min = r1.min(r2),
max = r1.max(r2),
topRight = new Point(max.x, min.y),
bottomLeft = new Point(min.x, max.y);
return Intersection.intersectPolygonPolygon(points, [min, topRight, max, bottomLeft]);
}
}
export { Intersection };
//# sourceMappingURL=Intersection.mjs.map