Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore(TS): migrate Intersection #8121

Merged
merged 27 commits into from
Aug 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d4bcf4e
Update point.class.ts
ShaMan123 Aug 4, 2022
5a19804
import point
ShaMan123 Aug 4, 2022
20d3e77
migrate
ShaMan123 Aug 4, 2022
59a6107
iports
ShaMan123 Aug 4, 2022
8f492ae
rename
ShaMan123 Aug 4, 2022
7c17d24
Update intersection.class.ts
ShaMan123 Aug 4, 2022
7cec891
rename
ShaMan123 Aug 5, 2022
3b5f60d
Merge branch 'master' into ts-intersection
ShaMan123 Aug 5, 2022
7aa20fa
intersectLineLine
ShaMan123 Aug 5, 2022
91fb5eb
fix(): polygon line coincident
ShaMan123 Aug 5, 2022
d3ac3f3
Update intersection.js
ShaMan123 Aug 5, 2022
578717e
fixed!
ShaMan123 Aug 5, 2022
5574cb2
more work
ShaMan123 Aug 5, 2022
4ff2483
fix intersectLinePolygon
ShaMan123 Aug 5, 2022
46182f2
move unique points logic to appendPoint
ShaMan123 Aug 5, 2022
6c1120c
Update intersection.class.ts
ShaMan123 Aug 5, 2022
3a3e902
Update intersection.class.ts
ShaMan123 Aug 5, 2022
627c73d
dep(): `appendPoint` `appendPoints` => `append`
ShaMan123 Aug 5, 2022
26fb94a
fix polygon coincident edge case
ShaMan123 Aug 8, 2022
89f13c4
Update intersection.js
ShaMan123 Aug 8, 2022
e94d62e
JSDOC
ShaMan123 Aug 17, 2022
1df3ec2
Update object_geometry.mixin.ts
ShaMan123 Aug 17, 2022
ecdb285
Update intersection.class.ts
ShaMan123 Aug 17, 2022
dd5ce7d
Merge branch 'master' into ts-intersection
ShaMan123 Aug 17, 2022
4fb0413
Merge branch 'master' into ts-intersection
ShaMan123 Aug 24, 2022
b1a8bef
Merge branch 'master' into ts-intersection
ShaMan123 Aug 25, 2022
e1fa4a8
Merge branch 'master' into ts-intersection
ShaMan123 Aug 26, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
283 changes: 176 additions & 107 deletions src/intersection.class.ts
Original file line number Diff line number Diff line change
@@ -1,166 +1,235 @@
//@ts-nocheck
import { Point } from './point.class';
import { Point } from "./point.class";
import { fabric } from '../HEADER';

(function(global) {
/* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */
var fabric = global.fabric || (global.fabric = { });
/**
* Intersection class
* @class fabric.Intersection
* @memberOf fabric
* @constructor
*/
function Intersection(status) {
/* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */

type IntersectionType = 'Intersection' | 'Coincident' | 'Parallel';

/**
* **Assuming `T`, `A`, `B` are points on the same line**,
* check if `T` is contained in `[A, B]` by comparing the direction of the vectors from `T` to `A` and `B`
* @param T
* @param A
* @param B
* @returns true if `T` is contained
*/
const isContainedInInterval = (T: Point, A: Point, B: Point) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am afraid future misuse of the method because it is meant to be used in a very specific context as mentioned in the comment

Any suggestions?

const TA = new Point(T).subtract(A);
const TB = new Point(T).subtract(B);
return Math.sign(TA.x) !== Math.sign(TB.x) || Math.sign(TA.y) !== Math.sign(TB.y);
}

export class Intersection {

points: Point[]

status?: IntersectionType

constructor(status?: IntersectionType) {
this.status = status;
this.points = [];
}

fabric.Intersection = Intersection;

fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ {

constructor: Intersection,

/**
* Appends a point to intersection
* @param {Point} point
* @return {fabric.Intersection} thisArg
* @chainable
*/
appendPoint: function (point) {
this.points.push(point);
return this;
},

/**
* Appends points to intersection
* @param {Array} points
* @return {fabric.Intersection} thisArg
* @chainable
*/
appendPoints: function (points) {
this.points = this.points.concat(points);
return this;
}
};
/**
*
* @param {Point} point
* @returns
*/
contains(point) {
return this.points.some(p => p.eq(point));
}

/**
* Checks if one line intersects another
* TODO: rename in intersectSegmentSegment
* Appends points of intersection
* @param {...Point[]} points
* @return {Intersection} thisArg
* @chainable
*/
private append(...points) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactored appendPoint appendPoints to append es6!
I don't consider it breaking as this method is private

this.points = this.points.concat(points.filter(point => {
return !this.contains(point);
Copy link
Contributor Author

@ShaMan123 ShaMan123 Aug 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

append only unique points

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this hot code? why does it matters? If this is used during render i don't really want to do it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hot code? What's that?

Copy link
Contributor Author

@ShaMan123 ShaMan123 Aug 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How damaging is this loop?
we can make it a map instead (I think it is insignificant)
consider that most case points intersection is <=2

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only 2 polygons that are almost identical will have <= n-1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hot code? What's that?

ahh code used by the dev?
yes, getting intersection points in crucial for ui displaying these points

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with hot code i mean code we use in tight loops during rendering. Goint to check how many intersections we use

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok is fine, the Intersection is just a fancy container for points, the math to find the point didn't change. I'm good with this.

}));
return this;
}

/**
* Checks if a line intersects another
* @static
* @param {Point} a1
* @param {Point} a2
* @param {Point} b1
* @param {Point} b2
* @return {fabric.Intersection}
* @param {boolean} [aIinfinite=true] check segment intersection by passing `false`
* @param {boolean} [bIinfinite=true] check segment intersection by passing `false`
* @return {Intersection}
*/
fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) {
var result,
uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x),
ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x),
uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);
static intersectLineLine(a1, a2, b1, b2, aIinfinite = true, bIinfinite = true) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added option for infinite line check

let result;
const uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x),
ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x),
uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

who ever wrote this knows their algerba
I can't understand it exactly, just the main concept

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some matrix det/cross clever calculation
well done who ever you are

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of those can be probably grouped in smaller const, i m not sure the extra const are worth the subtraction saved.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought of it too.
Wasn't sure how to name what...

if (uB !== 0) {
var ua = uaT / uB,
ub = ubT / uB;
if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
const ua = uaT / uB,
ub = ubT / uB;
if ((aIinfinite || (0 <= ua && ua <= 1)) && (bIinfinite || (0 <= ub && ub <= 1))) {
result = new Intersection('Intersection');
result.appendPoint(new Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y)));
result.append(new Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y)));
}
else {
result = new Intersection();
}
}
else {
if (uaT === 0 || ubT === 0) {
result = new Intersection('Coincident');
const segmentsCoincide = aIinfinite || bIinfinite
|| isContainedInInterval(a1, b1, b2) || isContainedInInterval(a2, b1, b2)
|| isContainedInInterval(b1, a1, a2) || isContainedInInterval(b2, a1, a2);
result = new Intersection(segmentsCoincide ? 'Coincident' : undefined);
Copy link
Contributor Author

@ShaMan123 ShaMan123 Aug 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If segments intersection => check that coincident happens in range
fixed!

}
else {
result = new Intersection('Parallel');
}
}
return result;
};
}

/**
* 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: rename in intersectSegmentPolygon
* fix detection of coincident
*
* @todo account for stroke
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*
* @static
* @param {Point} a1
* @param {Point} a2
* @param {Array} points
* @return {fabric.Intersection}
* @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}
*/
fabric.Intersection.intersectLinePolygon = function(a1, a2, points) {
var result = new Intersection(),
length = points.length,
b1, b2, inter, i;
static intersectLinePolygon(a1, a2, points, infinite = true) {
const result = new Intersection();
const length = points.length;

for (i = 0; i < length; i++) {
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);

result.appendPoints(inter.points);
inter = Intersection.intersectLineLine(a1, a2, b1, b2, infinite, false);
if (inter.status === 'Coincident') {
return inter;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed detection coincident (someone left a note about it)

}
result.append(...inter.points);
}

if (result.points.length > 0) {
result.status = 'Intersection';
}

return result;
};
}

/**
* Checks if polygon intersects another polygon
* Checks if segment intersects polygon
* @static
* @param {Array} points1
* @param {Array} points2
* @return {fabric.Intersection}
* @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}
*/
fabric.Intersection.intersectPolygonPolygon = function (points1, points2) {
var result = new Intersection(),
length = points1.length, i;
static intersectSegmentPolygon(a1, a2, points) {
return Intersection.intersectLinePolygon(a1, a2, points, false);
}

for (i = 0; i < length; i++) {
var a1 = points1[i],
a2 = points1[(i + 1) % length],
inter = Intersection.intersectLinePolygon(a1, a2, points2);
/**
* 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 coincidents = [];

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') {
coincidents.push(inter);
result.append(a1, a2);
}
else {
result.append(...inter.points);
}
}

result.appendPoints(inter.points);
if (coincidents.length > 0 && coincidents.length === points1.length) {
return new Intersection('Coincident');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same fix of TODO comment

}
if (result.points.length > 0) {
else if (result.points.length > 0) {
result.status = 'Intersection';
}

return result;
};
}

/**
* Checks if polygon intersects rectangle
* @static
* @param {Array} points
* @param {Point} r1
* @param {Point} r2
* @return {fabric.Intersection}
* @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}
*/
fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) {
var min = r1.min(r2),
max = r1.max(r2),
topRight = new Point(max.x, min.y),
bottomLeft = new Point(min.x, max.y),
inter1 = Intersection.intersectLinePolygon(min, topRight, points),
inter2 = Intersection.intersectLinePolygon(topRight, max, points),
inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points),
inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points),
result = new Intersection();

result.appendPoints(inter1.points);
result.appendPoints(inter2.points);
result.appendPoints(inter3.points);
result.appendPoints(inter4.points);
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
]);
}

if (result.points.length > 0) {
result.status = 'Intersection';
}
return result;
};
}

})(typeof exports !== 'undefined' ? exports : window);
fabric.Intersection = Intersection;
5 changes: 4 additions & 1 deletion src/mixins/object_geometry.mixin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
//@ts-nocheck

import { Intersection } from '../intersection.class';
import { Point } from '../point.class';

(function(global) {
Expand Down Expand Up @@ -235,12 +237,13 @@ import { Point } from '../point.class';
* @return {Boolean} true if object intersects with another object
*/
intersectsWithObject: function(other, absolute, calculate) {
var intersection = fabric.Intersection.intersectPolygonPolygon(
var intersection = Intersection.intersectPolygonPolygon(
this.getCoords(absolute, calculate),
other.getCoords(absolute, calculate)
);

return intersection.status === 'Intersection'
|| intersection.status === 'Coincident'
|| other.isContainedWithinObject(this, absolute, calculate)
|| this.isContainedWithinObject(other, absolute, calculate);
},
Expand Down