@excalidraw/excalidraw 0.17.1-1d71f84 → 0.17.1-4689a6b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -0
- package/dist/browser/dev/excalidraw-assets-dev/{chunk-AK7SWNLN.js → chunk-23CKV3WP.js} +4 -2
- package/dist/browser/dev/excalidraw-assets-dev/chunk-23CKV3WP.js.map +7 -0
- package/dist/browser/dev/excalidraw-assets-dev/{chunk-RWZVJAQU.js → chunk-7D5BMEAB.js} +2227 -1976
- package/dist/browser/dev/excalidraw-assets-dev/chunk-7D5BMEAB.js.map +7 -0
- package/dist/browser/dev/excalidraw-assets-dev/{en-5TCZHGGJ.js → en-W7TECCRB.js} +2 -2
- package/dist/browser/dev/excalidraw-assets-dev/{image-EDKQZH7Z.js → image-JKT6GXZD.js} +2 -2
- package/dist/browser/dev/index.css +20 -0
- package/dist/browser/dev/index.css.map +2 -2
- package/dist/browser/dev/index.js +770 -585
- package/dist/browser/dev/index.js.map +4 -4
- package/dist/browser/prod/excalidraw-assets/chunk-DWOM5R6H.js +55 -0
- package/dist/browser/prod/excalidraw-assets/{chunk-CTYINSWT.js → chunk-SK23VHAR.js} +2 -2
- package/dist/browser/prod/excalidraw-assets/{en-LROPV2RN.js → en-SMMH575S.js} +1 -1
- package/dist/browser/prod/excalidraw-assets/image-WDEQS5RL.js +1 -0
- package/dist/browser/prod/index.css +1 -1
- package/dist/browser/prod/index.js +22 -22
- package/dist/{prod/en-II4GK66F.json → dev/en-CVBEBUBY.json} +3 -1
- package/dist/dev/index.css +20 -0
- package/dist/dev/index.css.map +2 -2
- package/dist/dev/index.js +2383 -2074
- package/dist/dev/index.js.map +4 -4
- package/dist/excalidraw/actions/actionBoundText.js +4 -1
- package/dist/excalidraw/actions/actionCanvas.js +3 -1
- package/dist/excalidraw/actions/actionDuplicateSelection.js +4 -0
- package/dist/excalidraw/actions/actionExport.d.ts +1 -1
- package/dist/excalidraw/actions/actionFinalize.d.ts +1 -1
- package/dist/excalidraw/actions/actionFinalize.js +3 -3
- package/dist/excalidraw/actions/actionFlip.d.ts +3 -3
- package/dist/excalidraw/actions/actionFlip.js +6 -6
- package/dist/excalidraw/actions/actionGroup.js +4 -2
- package/dist/excalidraw/actions/actionHistory.js +3 -0
- package/dist/excalidraw/actions/actionZindex.d.ts +11 -11
- package/dist/excalidraw/actions/shortcuts.js +1 -1
- package/dist/excalidraw/analytics.js +1 -1
- package/dist/excalidraw/components/App.d.ts +13 -3
- package/dist/excalidraw/components/App.js +212 -83
- package/dist/excalidraw/components/CommandPalette/CommandPalette.js +24 -10
- package/dist/excalidraw/components/DarkModeToggle.js +3 -1
- package/dist/excalidraw/components/HelpDialog.js +8 -6
- package/dist/excalidraw/components/RadioGroup.d.ts +2 -1
- package/dist/excalidraw/components/RadioGroup.js +1 -1
- package/dist/excalidraw/components/TTDDialog/MermaidToExcalidraw.js +6 -2
- package/dist/excalidraw/components/dropdownMenu/DropdownMenuItemContentRadio.d.ts +18 -0
- package/dist/excalidraw/components/dropdownMenu/DropdownMenuItemContentRadio.js +9 -0
- package/dist/excalidraw/components/hyperlink/Hyperlink.js +3 -3
- package/dist/excalidraw/components/hyperlink/helpers.js +2 -3
- package/dist/excalidraw/components/icons.d.ts +3 -0
- package/dist/excalidraw/components/icons.js +5 -1
- package/dist/excalidraw/components/main-menu/DefaultItems.d.ts +12 -2
- package/dist/excalidraw/components/main-menu/DefaultItems.js +38 -7
- package/dist/excalidraw/constants.d.ts +0 -3
- package/dist/excalidraw/constants.js +0 -3
- package/dist/excalidraw/data/magic.js +2 -1
- package/dist/excalidraw/data/reconcile.d.ts +6 -0
- package/dist/excalidraw/data/reconcile.js +49 -0
- package/dist/excalidraw/data/restore.d.ts +3 -3
- package/dist/excalidraw/data/restore.js +5 -6
- package/dist/excalidraw/data/transform.d.ts +1 -1
- package/dist/excalidraw/data/transform.js +12 -3
- package/dist/excalidraw/element/binding.d.ts +22 -9
- package/dist/excalidraw/element/binding.js +403 -26
- package/dist/excalidraw/element/bounds.d.ts +0 -1
- package/dist/excalidraw/element/bounds.js +0 -3
- package/dist/excalidraw/element/collision.d.ts +14 -19
- package/dist/excalidraw/element/collision.js +36 -713
- package/dist/excalidraw/element/embeddable.js +18 -43
- package/dist/excalidraw/element/index.d.ts +0 -1
- package/dist/excalidraw/element/index.js +0 -1
- package/dist/excalidraw/element/linearElementEditor.d.ts +10 -10
- package/dist/excalidraw/element/linearElementEditor.js +6 -4
- package/dist/excalidraw/element/newElement.d.ts +1 -1
- package/dist/excalidraw/element/newElement.js +2 -1
- package/dist/excalidraw/element/textElement.d.ts +0 -1
- package/dist/excalidraw/element/textElement.js +0 -30
- package/dist/excalidraw/element/types.d.ts +17 -2
- package/dist/excalidraw/errors.d.ts +3 -0
- package/dist/excalidraw/errors.js +3 -0
- package/dist/excalidraw/fractionalIndex.d.ts +40 -0
- package/dist/excalidraw/fractionalIndex.js +241 -0
- package/dist/excalidraw/frame.d.ts +1 -1
- package/dist/excalidraw/hooks/useCreatePortalContainer.js +2 -1
- package/dist/excalidraw/locales/en.json +3 -1
- package/dist/excalidraw/renderer/helpers.js +2 -2
- package/dist/excalidraw/renderer/interactiveScene.js +1 -1
- package/dist/excalidraw/renderer/renderElement.js +3 -3
- package/dist/excalidraw/renderer/renderSnaps.js +2 -1
- package/dist/excalidraw/scene/Scene.d.ts +7 -6
- package/dist/excalidraw/scene/Scene.js +28 -13
- package/dist/excalidraw/scene/export.js +4 -3
- package/dist/excalidraw/types.d.ts +4 -3
- package/dist/excalidraw/utils.d.ts +1 -0
- package/dist/excalidraw/utils.js +1 -0
- package/dist/excalidraw/zindex.d.ts +2 -2
- package/dist/excalidraw/zindex.js +9 -13
- package/dist/{dev/en-II4GK66F.json → prod/en-CVBEBUBY.json} +3 -1
- package/dist/prod/index.css +1 -1
- package/dist/prod/index.js +36 -36
- package/dist/utils/collision.d.ts +4 -0
- package/dist/utils/collision.js +48 -0
- package/dist/utils/geometry/geometry.d.ts +71 -0
- package/dist/utils/geometry/geometry.js +674 -0
- package/dist/utils/geometry/shape.d.ts +55 -0
- package/dist/utils/geometry/shape.js +149 -0
- package/package.json +2 -1
- package/dist/browser/dev/excalidraw-assets-dev/chunk-AK7SWNLN.js.map +0 -7
- package/dist/browser/dev/excalidraw-assets-dev/chunk-RWZVJAQU.js.map +0 -7
- package/dist/browser/prod/excalidraw-assets/chunk-LL4GORAM.js +0 -55
- package/dist/browser/prod/excalidraw-assets/image-EFCJDJH3.js +0 -1
- /package/dist/browser/dev/excalidraw-assets-dev/{en-5TCZHGGJ.js.map → en-W7TECCRB.js.map} +0 -0
- /package/dist/browser/dev/excalidraw-assets-dev/{image-EDKQZH7Z.js.map → image-JKT6GXZD.js.map} +0 -0
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
import { distance2d } from "../../excalidraw/math";
|
|
2
|
+
const DEFAULT_THRESHOLD = 10e-5;
|
|
3
|
+
/**
|
|
4
|
+
* utils
|
|
5
|
+
*/
|
|
6
|
+
// the two vectors are ao and bo
|
|
7
|
+
export const cross = (a, b, o) => {
|
|
8
|
+
return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
|
|
9
|
+
};
|
|
10
|
+
export const isClosed = (polygon) => {
|
|
11
|
+
const first = polygon[0];
|
|
12
|
+
const last = polygon[polygon.length - 1];
|
|
13
|
+
return first[0] === last[0] && first[1] === last[1];
|
|
14
|
+
};
|
|
15
|
+
export const close = (polygon) => {
|
|
16
|
+
return isClosed(polygon) ? polygon : [...polygon, polygon[0]];
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* angles
|
|
20
|
+
*/
|
|
21
|
+
// convert radians to degress
|
|
22
|
+
export const angleToDegrees = (angle) => {
|
|
23
|
+
return (angle * 180) / Math.PI;
|
|
24
|
+
};
|
|
25
|
+
// convert degrees to radians
|
|
26
|
+
export const angleToRadians = (angle) => {
|
|
27
|
+
return (angle / 180) * Math.PI;
|
|
28
|
+
};
|
|
29
|
+
// return the angle of reflection given an angle of incidence and a surface angle in degrees
|
|
30
|
+
export const angleReflect = (incidenceAngle, surfaceAngle) => {
|
|
31
|
+
const a = surfaceAngle * 2 - incidenceAngle;
|
|
32
|
+
return a >= 360 ? a - 360 : a < 0 ? a + 360 : a;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* points
|
|
36
|
+
*/
|
|
37
|
+
const rotate = (point, angle) => {
|
|
38
|
+
return [
|
|
39
|
+
point[0] * Math.cos(angle) - point[1] * Math.sin(angle),
|
|
40
|
+
point[0] * Math.sin(angle) + point[1] * Math.cos(angle),
|
|
41
|
+
];
|
|
42
|
+
};
|
|
43
|
+
const isOrigin = (point) => {
|
|
44
|
+
return point[0] === 0 && point[1] === 0;
|
|
45
|
+
};
|
|
46
|
+
// rotate a given point about a given origin at the given angle
|
|
47
|
+
export const pointRotate = (point, angle, origin) => {
|
|
48
|
+
const r = angleToRadians(angle);
|
|
49
|
+
if (!origin || isOrigin(origin)) {
|
|
50
|
+
return rotate(point, r);
|
|
51
|
+
}
|
|
52
|
+
return rotate(point.map((c, i) => c - origin[i]), r).map((c, i) => c + origin[i]);
|
|
53
|
+
};
|
|
54
|
+
// translate a point by an angle (in degrees) and distance
|
|
55
|
+
export const pointTranslate = (point, angle = 0, distance = 0) => {
|
|
56
|
+
const r = angleToRadians(angle);
|
|
57
|
+
return [
|
|
58
|
+
point[0] + distance * Math.cos(r),
|
|
59
|
+
point[1] + distance * Math.sin(r),
|
|
60
|
+
];
|
|
61
|
+
};
|
|
62
|
+
export const pointInverse = (point) => {
|
|
63
|
+
return [-point[0], -point[1]];
|
|
64
|
+
};
|
|
65
|
+
export const pointAdd = (pointA, pointB) => {
|
|
66
|
+
return [pointA[0] + pointB[0], pointA[1] + pointB[1]];
|
|
67
|
+
};
|
|
68
|
+
export const distanceToPoint = (p1, p2) => {
|
|
69
|
+
return distance2d(...p1, ...p2);
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* lines
|
|
73
|
+
*/
|
|
74
|
+
// return the angle of a line, in degrees
|
|
75
|
+
export const lineAngle = (line) => {
|
|
76
|
+
return angleToDegrees(Math.atan2(line[1][1] - line[0][1], line[1][0] - line[0][0]));
|
|
77
|
+
};
|
|
78
|
+
// get the distance between the endpoints of a line segment
|
|
79
|
+
export const lineLength = (line) => {
|
|
80
|
+
return Math.sqrt(Math.pow(line[1][0] - line[0][0], 2) + Math.pow(line[1][1] - line[0][1], 2));
|
|
81
|
+
};
|
|
82
|
+
// get the midpoint of a line segment
|
|
83
|
+
export const lineMidpoint = (line) => {
|
|
84
|
+
return [
|
|
85
|
+
(line[0][0] + line[1][0]) / 2,
|
|
86
|
+
(line[0][1] + line[1][1]) / 2,
|
|
87
|
+
];
|
|
88
|
+
};
|
|
89
|
+
// return the coordinates resulting from rotating the given line about an origin by an angle in degrees
|
|
90
|
+
// note that when the origin is not given, the midpoint of the given line is used as the origin
|
|
91
|
+
export const lineRotate = (line, angle, origin) => {
|
|
92
|
+
return line.map((point) => pointRotate(point, angle, origin || lineMidpoint(line)));
|
|
93
|
+
};
|
|
94
|
+
// returns the coordinates resulting from translating a line by an angle in degrees and a distance.
|
|
95
|
+
export const lineTranslate = (line, angle, distance) => {
|
|
96
|
+
return line.map((point) => pointTranslate(point, angle, distance));
|
|
97
|
+
};
|
|
98
|
+
export const lineInterpolate = (line, clamp = false) => {
|
|
99
|
+
const [[x1, y1], [x2, y2]] = line;
|
|
100
|
+
return (t) => {
|
|
101
|
+
const t0 = clamp ? (t < 0 ? 0 : t > 1 ? 1 : t) : t;
|
|
102
|
+
return [(x2 - x1) * t0 + x1, (y2 - y1) * t0 + y1];
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* curves
|
|
107
|
+
*/
|
|
108
|
+
function clone(p) {
|
|
109
|
+
return [...p];
|
|
110
|
+
}
|
|
111
|
+
export const curveToBezier = (pointsIn, curveTightness = 0) => {
|
|
112
|
+
const len = pointsIn.length;
|
|
113
|
+
if (len < 3) {
|
|
114
|
+
throw new Error("A curve must have at least three points.");
|
|
115
|
+
}
|
|
116
|
+
const out = [];
|
|
117
|
+
if (len === 3) {
|
|
118
|
+
out.push(clone(pointsIn[0]), clone(pointsIn[1]), clone(pointsIn[2]), clone(pointsIn[2]));
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
const points = [];
|
|
122
|
+
points.push(pointsIn[0], pointsIn[0]);
|
|
123
|
+
for (let i = 1; i < pointsIn.length; i++) {
|
|
124
|
+
points.push(pointsIn[i]);
|
|
125
|
+
if (i === pointsIn.length - 1) {
|
|
126
|
+
points.push(pointsIn[i]);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const b = [];
|
|
130
|
+
const s = 1 - curveTightness;
|
|
131
|
+
out.push(clone(points[0]));
|
|
132
|
+
for (let i = 1; i + 2 < points.length; i++) {
|
|
133
|
+
const cachedVertArray = points[i];
|
|
134
|
+
b[0] = [cachedVertArray[0], cachedVertArray[1]];
|
|
135
|
+
b[1] = [
|
|
136
|
+
cachedVertArray[0] + (s * points[i + 1][0] - s * points[i - 1][0]) / 6,
|
|
137
|
+
cachedVertArray[1] + (s * points[i + 1][1] - s * points[i - 1][1]) / 6,
|
|
138
|
+
];
|
|
139
|
+
b[2] = [
|
|
140
|
+
points[i + 1][0] + (s * points[i][0] - s * points[i + 2][0]) / 6,
|
|
141
|
+
points[i + 1][1] + (s * points[i][1] - s * points[i + 2][1]) / 6,
|
|
142
|
+
];
|
|
143
|
+
b[3] = [points[i + 1][0], points[i + 1][1]];
|
|
144
|
+
out.push(b[1], b[2], b[3]);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
};
|
|
149
|
+
export const curveRotate = (curve, angle, origin) => {
|
|
150
|
+
return curve.map((p) => pointRotate(p, angle, origin));
|
|
151
|
+
};
|
|
152
|
+
export const cubicBezierPoint = (t, controlPoints) => {
|
|
153
|
+
const [p0, p1, p2, p3] = controlPoints;
|
|
154
|
+
const x = Math.pow(1 - t, 3) * p0[0] +
|
|
155
|
+
3 * Math.pow(1 - t, 2) * t * p1[0] +
|
|
156
|
+
3 * (1 - t) * Math.pow(t, 2) * p2[0] +
|
|
157
|
+
Math.pow(t, 3) * p3[0];
|
|
158
|
+
const y = Math.pow(1 - t, 3) * p0[1] +
|
|
159
|
+
3 * Math.pow(1 - t, 2) * t * p1[1] +
|
|
160
|
+
3 * (1 - t) * Math.pow(t, 2) * p2[1] +
|
|
161
|
+
Math.pow(t, 3) * p3[1];
|
|
162
|
+
return [x, y];
|
|
163
|
+
};
|
|
164
|
+
const solveCubicEquation = (a, b, c, d) => {
|
|
165
|
+
// This function solves the cubic equation ax^3 + bx^2 + cx + d = 0
|
|
166
|
+
const roots = [];
|
|
167
|
+
const discriminant = 18 * a * b * c * d -
|
|
168
|
+
4 * Math.pow(b, 3) * d +
|
|
169
|
+
Math.pow(b, 2) * Math.pow(c, 2) -
|
|
170
|
+
4 * a * Math.pow(c, 3) -
|
|
171
|
+
27 * Math.pow(a, 2) * Math.pow(d, 2);
|
|
172
|
+
if (discriminant >= 0) {
|
|
173
|
+
const C = Math.cbrt((discriminant + Math.sqrt(discriminant)) / 2);
|
|
174
|
+
const D = Math.cbrt((discriminant - Math.sqrt(discriminant)) / 2);
|
|
175
|
+
const root1 = (-b - C - D) / (3 * a);
|
|
176
|
+
const root2 = (-b + (C + D) / 2) / (3 * a);
|
|
177
|
+
const root3 = (-b + (C + D) / 2) / (3 * a);
|
|
178
|
+
roots.push(root1, root2, root3);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
const realPart = -b / (3 * a);
|
|
182
|
+
const root1 = 2 * Math.sqrt(-b / (3 * a)) * Math.cos(Math.acos(realPart) / 3);
|
|
183
|
+
const root2 = 2 *
|
|
184
|
+
Math.sqrt(-b / (3 * a)) *
|
|
185
|
+
Math.cos((Math.acos(realPart) + 2 * Math.PI) / 3);
|
|
186
|
+
const root3 = 2 *
|
|
187
|
+
Math.sqrt(-b / (3 * a)) *
|
|
188
|
+
Math.cos((Math.acos(realPart) + 4 * Math.PI) / 3);
|
|
189
|
+
roots.push(root1, root2, root3);
|
|
190
|
+
}
|
|
191
|
+
return roots;
|
|
192
|
+
};
|
|
193
|
+
const findClosestParameter = (point, controlPoints) => {
|
|
194
|
+
// This function finds the parameter t that minimizes the distance between the point
|
|
195
|
+
// and any point on the cubic Bezier curve.
|
|
196
|
+
const [p0, p1, p2, p3] = controlPoints;
|
|
197
|
+
// Use the direct formula to find the parameter t
|
|
198
|
+
const a = p3[0] - 3 * p2[0] + 3 * p1[0] - p0[0];
|
|
199
|
+
const b = 3 * p2[0] - 6 * p1[0] + 3 * p0[0];
|
|
200
|
+
const c = 3 * p1[0] - 3 * p0[0];
|
|
201
|
+
const d = p0[0] - point[0];
|
|
202
|
+
const rootsX = solveCubicEquation(a, b, c, d);
|
|
203
|
+
// Do the same for the y-coordinate
|
|
204
|
+
const e = p3[1] - 3 * p2[1] + 3 * p1[1] - p0[1];
|
|
205
|
+
const f = 3 * p2[1] - 6 * p1[1] + 3 * p0[1];
|
|
206
|
+
const g = 3 * p1[1] - 3 * p0[1];
|
|
207
|
+
const h = p0[1] - point[1];
|
|
208
|
+
const rootsY = solveCubicEquation(e, f, g, h);
|
|
209
|
+
// Select the real root that is between 0 and 1 (inclusive)
|
|
210
|
+
const validRootsX = rootsX.filter((root) => root >= 0 && root <= 1);
|
|
211
|
+
const validRootsY = rootsY.filter((root) => root >= 0 && root <= 1);
|
|
212
|
+
if (validRootsX.length === 0 || validRootsY.length === 0) {
|
|
213
|
+
// No valid roots found, use the midpoint as a fallback
|
|
214
|
+
return 0.5;
|
|
215
|
+
}
|
|
216
|
+
// Choose the parameter t that minimizes the distance
|
|
217
|
+
let minDistance = Infinity;
|
|
218
|
+
let closestT = 0;
|
|
219
|
+
for (const rootX of validRootsX) {
|
|
220
|
+
for (const rootY of validRootsY) {
|
|
221
|
+
const distance = Math.sqrt((rootX - point[0]) ** 2 + (rootY - point[1]) ** 2);
|
|
222
|
+
if (distance < minDistance) {
|
|
223
|
+
minDistance = distance;
|
|
224
|
+
closestT = (rootX + rootY) / 2; // Use the average for a smoother result
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return closestT;
|
|
229
|
+
};
|
|
230
|
+
export const cubicBezierDistance = (point, controlPoints) => {
|
|
231
|
+
// Calculate the closest point on the Bezier curve to the given point
|
|
232
|
+
const t = findClosestParameter(point, controlPoints);
|
|
233
|
+
// Calculate the coordinates of the closest point on the curve
|
|
234
|
+
const [closestX, closestY] = cubicBezierPoint(t, controlPoints);
|
|
235
|
+
// Calculate the distance between the given point and the closest point on the curve
|
|
236
|
+
const distance = Math.sqrt((point[0] - closestX) ** 2 + (point[1] - closestY) ** 2);
|
|
237
|
+
return distance;
|
|
238
|
+
};
|
|
239
|
+
/**
|
|
240
|
+
* polygons
|
|
241
|
+
*/
|
|
242
|
+
export const polygonRotate = (polygon, angle, origin) => {
|
|
243
|
+
return polygon.map((p) => pointRotate(p, angle, origin));
|
|
244
|
+
};
|
|
245
|
+
export const polygonBounds = (polygon) => {
|
|
246
|
+
let xMin = Infinity;
|
|
247
|
+
let xMax = -Infinity;
|
|
248
|
+
let yMin = Infinity;
|
|
249
|
+
let yMax = -Infinity;
|
|
250
|
+
for (let i = 0, l = polygon.length; i < l; i++) {
|
|
251
|
+
const p = polygon[i];
|
|
252
|
+
const x = p[0];
|
|
253
|
+
const y = p[1];
|
|
254
|
+
if (x != null && isFinite(x) && y != null && isFinite(y)) {
|
|
255
|
+
if (x < xMin) {
|
|
256
|
+
xMin = x;
|
|
257
|
+
}
|
|
258
|
+
if (x > xMax) {
|
|
259
|
+
xMax = x;
|
|
260
|
+
}
|
|
261
|
+
if (y < yMin) {
|
|
262
|
+
yMin = y;
|
|
263
|
+
}
|
|
264
|
+
if (y > yMax) {
|
|
265
|
+
yMax = y;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return [
|
|
270
|
+
[xMin, yMin],
|
|
271
|
+
[xMax, yMax],
|
|
272
|
+
];
|
|
273
|
+
};
|
|
274
|
+
export const polygonCentroid = (vertices) => {
|
|
275
|
+
let a = 0;
|
|
276
|
+
let x = 0;
|
|
277
|
+
let y = 0;
|
|
278
|
+
const l = vertices.length;
|
|
279
|
+
for (let i = 0; i < l; i++) {
|
|
280
|
+
const s = i === l - 1 ? 0 : i + 1;
|
|
281
|
+
const v0 = vertices[i];
|
|
282
|
+
const v1 = vertices[s];
|
|
283
|
+
const f = v0[0] * v1[1] - v1[0] * v0[1];
|
|
284
|
+
a += f;
|
|
285
|
+
x += (v0[0] + v1[0]) * f;
|
|
286
|
+
y += (v0[1] + v1[1]) * f;
|
|
287
|
+
}
|
|
288
|
+
const d = a * 3;
|
|
289
|
+
return [x / d, y / d];
|
|
290
|
+
};
|
|
291
|
+
export const polygonScale = (polygon, scale, origin) => {
|
|
292
|
+
if (!origin) {
|
|
293
|
+
origin = polygonCentroid(polygon);
|
|
294
|
+
}
|
|
295
|
+
const p = [];
|
|
296
|
+
for (let i = 0, l = polygon.length; i < l; i++) {
|
|
297
|
+
const v = polygon[i];
|
|
298
|
+
const d = lineLength([origin, v]);
|
|
299
|
+
const a = lineAngle([origin, v]);
|
|
300
|
+
p[i] = pointTranslate(origin, a, d * scale);
|
|
301
|
+
}
|
|
302
|
+
return p;
|
|
303
|
+
};
|
|
304
|
+
export const polygonScaleX = (polygon, scale, origin) => {
|
|
305
|
+
if (!origin) {
|
|
306
|
+
origin = polygonCentroid(polygon);
|
|
307
|
+
}
|
|
308
|
+
const p = [];
|
|
309
|
+
for (let i = 0, l = polygon.length; i < l; i++) {
|
|
310
|
+
const v = polygon[i];
|
|
311
|
+
const d = lineLength([origin, v]);
|
|
312
|
+
const a = lineAngle([origin, v]);
|
|
313
|
+
const t = pointTranslate(origin, a, d * scale);
|
|
314
|
+
p[i] = [t[0], v[1]];
|
|
315
|
+
}
|
|
316
|
+
return p;
|
|
317
|
+
};
|
|
318
|
+
export const polygonScaleY = (polygon, scale, origin) => {
|
|
319
|
+
if (!origin) {
|
|
320
|
+
origin = polygonCentroid(polygon);
|
|
321
|
+
}
|
|
322
|
+
const p = [];
|
|
323
|
+
for (let i = 0, l = polygon.length; i < l; i++) {
|
|
324
|
+
const v = polygon[i];
|
|
325
|
+
const d = lineLength([origin, v]);
|
|
326
|
+
const a = lineAngle([origin, v]);
|
|
327
|
+
const t = pointTranslate(origin, a, d * scale);
|
|
328
|
+
p[i] = [v[0], t[1]];
|
|
329
|
+
}
|
|
330
|
+
return p;
|
|
331
|
+
};
|
|
332
|
+
export const polygonReflectX = (polygon, reflectFactor = 1) => {
|
|
333
|
+
const [[min], [max]] = polygonBounds(polygon);
|
|
334
|
+
const p = [];
|
|
335
|
+
for (let i = 0, l = polygon.length; i < l; i++) {
|
|
336
|
+
const [x, y] = polygon[i];
|
|
337
|
+
const r = [min + max - x, y];
|
|
338
|
+
if (reflectFactor === 0) {
|
|
339
|
+
p[i] = [x, y];
|
|
340
|
+
}
|
|
341
|
+
else if (reflectFactor === 1) {
|
|
342
|
+
p[i] = r;
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
const t = lineInterpolate([[x, y], r]);
|
|
346
|
+
p[i] = t(Math.max(Math.min(reflectFactor, 1), 0));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return p;
|
|
350
|
+
};
|
|
351
|
+
export const polygonReflectY = (polygon, reflectFactor = 1) => {
|
|
352
|
+
const [[, min], [, max]] = polygonBounds(polygon);
|
|
353
|
+
const p = [];
|
|
354
|
+
for (let i = 0, l = polygon.length; i < l; i++) {
|
|
355
|
+
const [x, y] = polygon[i];
|
|
356
|
+
const r = [x, min + max - y];
|
|
357
|
+
if (reflectFactor === 0) {
|
|
358
|
+
p[i] = [x, y];
|
|
359
|
+
}
|
|
360
|
+
else if (reflectFactor === 1) {
|
|
361
|
+
p[i] = r;
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
const t = lineInterpolate([[x, y], r]);
|
|
365
|
+
p[i] = t(Math.max(Math.min(reflectFactor, 1), 0));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return p;
|
|
369
|
+
};
|
|
370
|
+
export const polygonTranslate = (polygon, angle, distance) => {
|
|
371
|
+
return polygon.map((p) => pointTranslate(p, angle, distance));
|
|
372
|
+
};
|
|
373
|
+
/**
|
|
374
|
+
* ellipses
|
|
375
|
+
*/
|
|
376
|
+
export const ellipseAxes = (ellipse) => {
|
|
377
|
+
const widthGreaterThanHeight = ellipse.halfWidth > ellipse.halfHeight;
|
|
378
|
+
const majorAxis = widthGreaterThanHeight
|
|
379
|
+
? ellipse.halfWidth * 2
|
|
380
|
+
: ellipse.halfHeight * 2;
|
|
381
|
+
const minorAxis = widthGreaterThanHeight
|
|
382
|
+
? ellipse.halfHeight * 2
|
|
383
|
+
: ellipse.halfWidth * 2;
|
|
384
|
+
return {
|
|
385
|
+
majorAxis,
|
|
386
|
+
minorAxis,
|
|
387
|
+
};
|
|
388
|
+
};
|
|
389
|
+
export const ellipseFocusToCenter = (ellipse) => {
|
|
390
|
+
const { majorAxis, minorAxis } = ellipseAxes(ellipse);
|
|
391
|
+
return Math.sqrt(majorAxis ** 2 - minorAxis ** 2);
|
|
392
|
+
};
|
|
393
|
+
export const ellipseExtremes = (ellipse) => {
|
|
394
|
+
const { center, angle } = ellipse;
|
|
395
|
+
const { majorAxis, minorAxis } = ellipseAxes(ellipse);
|
|
396
|
+
const cos = Math.cos(angle);
|
|
397
|
+
const sin = Math.sin(angle);
|
|
398
|
+
const sqSum = majorAxis ** 2 + minorAxis ** 2;
|
|
399
|
+
const sqDiff = (majorAxis ** 2 - minorAxis ** 2) * Math.cos(2 * angle);
|
|
400
|
+
const yMax = Math.sqrt((sqSum - sqDiff) / 2);
|
|
401
|
+
const xAtYMax = (yMax * sqSum * sin * cos) /
|
|
402
|
+
(majorAxis ** 2 * sin ** 2 + minorAxis ** 2 * cos ** 2);
|
|
403
|
+
const xMax = Math.sqrt((sqSum + sqDiff) / 2);
|
|
404
|
+
const yAtXMax = (xMax * sqSum * sin * cos) /
|
|
405
|
+
(majorAxis ** 2 * cos ** 2 + minorAxis ** 2 * sin ** 2);
|
|
406
|
+
return [
|
|
407
|
+
pointAdd([xAtYMax, yMax], center),
|
|
408
|
+
pointAdd(pointInverse([xAtYMax, yMax]), center),
|
|
409
|
+
pointAdd([xMax, yAtXMax], center),
|
|
410
|
+
pointAdd([xMax, yAtXMax], center),
|
|
411
|
+
];
|
|
412
|
+
};
|
|
413
|
+
export const pointRelativeToCenter = (point, center, angle) => {
|
|
414
|
+
const translated = pointAdd(point, pointInverse(center));
|
|
415
|
+
const rotated = pointRotate(translated, -angleToDegrees(angle));
|
|
416
|
+
return rotated;
|
|
417
|
+
};
|
|
418
|
+
/**
|
|
419
|
+
* relationships
|
|
420
|
+
*/
|
|
421
|
+
const topPointFirst = (line) => {
|
|
422
|
+
return line[1][1] > line[0][1] ? line : [line[1], line[0]];
|
|
423
|
+
};
|
|
424
|
+
export const pointLeftofLine = (point, line) => {
|
|
425
|
+
const t = topPointFirst(line);
|
|
426
|
+
return cross(point, t[1], t[0]) < 0;
|
|
427
|
+
};
|
|
428
|
+
export const pointRightofLine = (point, line) => {
|
|
429
|
+
const t = topPointFirst(line);
|
|
430
|
+
return cross(point, t[1], t[0]) > 0;
|
|
431
|
+
};
|
|
432
|
+
export const distanceToSegment = (point, line) => {
|
|
433
|
+
const [x, y] = point;
|
|
434
|
+
const [[x1, y1], [x2, y2]] = line;
|
|
435
|
+
const A = x - x1;
|
|
436
|
+
const B = y - y1;
|
|
437
|
+
const C = x2 - x1;
|
|
438
|
+
const D = y2 - y1;
|
|
439
|
+
const dot = A * C + B * D;
|
|
440
|
+
const len_sq = C * C + D * D;
|
|
441
|
+
let param = -1;
|
|
442
|
+
if (len_sq !== 0) {
|
|
443
|
+
param = dot / len_sq;
|
|
444
|
+
}
|
|
445
|
+
let xx;
|
|
446
|
+
let yy;
|
|
447
|
+
if (param < 0) {
|
|
448
|
+
xx = x1;
|
|
449
|
+
yy = y1;
|
|
450
|
+
}
|
|
451
|
+
else if (param > 1) {
|
|
452
|
+
xx = x2;
|
|
453
|
+
yy = y2;
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
xx = x1 + param * C;
|
|
457
|
+
yy = y1 + param * D;
|
|
458
|
+
}
|
|
459
|
+
const dx = x - xx;
|
|
460
|
+
const dy = y - yy;
|
|
461
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
462
|
+
};
|
|
463
|
+
export const pointOnLine = (point, line, threshold = DEFAULT_THRESHOLD) => {
|
|
464
|
+
const distance = distanceToSegment(point, line);
|
|
465
|
+
if (distance === 0) {
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
return distance < threshold;
|
|
469
|
+
};
|
|
470
|
+
export const pointOnPolyline = (point, polyline, threshold = DEFAULT_THRESHOLD) => {
|
|
471
|
+
return polyline.some((line) => pointOnLine(point, line, threshold));
|
|
472
|
+
};
|
|
473
|
+
export const lineIntersectsLine = (lineA, lineB) => {
|
|
474
|
+
const [[a0x, a0y], [a1x, a1y]] = lineA;
|
|
475
|
+
const [[b0x, b0y], [b1x, b1y]] = lineB;
|
|
476
|
+
// shared points
|
|
477
|
+
if (a0x === b0x && a0y === b0y) {
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
if (a1x === b1x && a1y === b1y) {
|
|
481
|
+
return true;
|
|
482
|
+
}
|
|
483
|
+
// point on line
|
|
484
|
+
if (pointOnLine(lineA[0], lineB) || pointOnLine(lineA[1], lineB)) {
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
if (pointOnLine(lineB[0], lineA) || pointOnLine(lineB[1], lineA)) {
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
const denom = (b1y - b0y) * (a1x - a0x) - (b1x - b0x) * (a1y - a0y);
|
|
491
|
+
if (denom === 0) {
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
const deltaY = a0y - b0y;
|
|
495
|
+
const deltaX = a0x - b0x;
|
|
496
|
+
const numer0 = (b1x - b0x) * deltaY - (b1y - b0y) * deltaX;
|
|
497
|
+
const numer1 = (a1x - a0x) * deltaY - (a1y - a0y) * deltaX;
|
|
498
|
+
const quotA = numer0 / denom;
|
|
499
|
+
const quotB = numer1 / denom;
|
|
500
|
+
return quotA > 0 && quotA < 1 && quotB > 0 && quotB < 1;
|
|
501
|
+
};
|
|
502
|
+
export const lineIntersectsPolygon = (line, polygon) => {
|
|
503
|
+
let intersects = false;
|
|
504
|
+
const closed = close(polygon);
|
|
505
|
+
for (let i = 0, l = closed.length - 1; i < l; i++) {
|
|
506
|
+
const v0 = closed[i];
|
|
507
|
+
const v1 = closed[i + 1];
|
|
508
|
+
if (lineIntersectsLine(line, [v0, v1]) ||
|
|
509
|
+
(pointOnLine(v0, line) && pointOnLine(v1, line))) {
|
|
510
|
+
intersects = true;
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return intersects;
|
|
515
|
+
};
|
|
516
|
+
export const pointInBezierEquation = (p0, p1, p2, p3, [mx, my], lineThreshold) => {
|
|
517
|
+
// B(t) = p0 * (1-t)^3 + 3p1 * t * (1-t)^2 + 3p2 * t^2 * (1-t) + p3 * t^3
|
|
518
|
+
const equation = (t, idx) => Math.pow(1 - t, 3) * p3[idx] +
|
|
519
|
+
3 * t * Math.pow(1 - t, 2) * p2[idx] +
|
|
520
|
+
3 * Math.pow(t, 2) * (1 - t) * p1[idx] +
|
|
521
|
+
p0[idx] * Math.pow(t, 3);
|
|
522
|
+
const lineSegmentPoints = [];
|
|
523
|
+
let t = 0;
|
|
524
|
+
while (t <= 1.0) {
|
|
525
|
+
const tx = equation(t, 0);
|
|
526
|
+
const ty = equation(t, 1);
|
|
527
|
+
const diff = Math.sqrt(Math.pow(tx - mx, 2) + Math.pow(ty - my, 2));
|
|
528
|
+
if (diff < lineThreshold) {
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
lineSegmentPoints.push([tx, ty]);
|
|
532
|
+
t += 0.1;
|
|
533
|
+
}
|
|
534
|
+
// check the distance from line segments to the given point
|
|
535
|
+
return false;
|
|
536
|
+
};
|
|
537
|
+
export const cubicBezierEquation = (curve) => {
|
|
538
|
+
const [p0, p1, p2, p3] = curve;
|
|
539
|
+
// B(t) = p0 * (1-t)^3 + 3p1 * t * (1-t)^2 + 3p2 * t^2 * (1-t) + p3 * t^3
|
|
540
|
+
return (t, idx) => Math.pow(1 - t, 3) * p3[idx] +
|
|
541
|
+
3 * t * Math.pow(1 - t, 2) * p2[idx] +
|
|
542
|
+
3 * Math.pow(t, 2) * (1 - t) * p1[idx] +
|
|
543
|
+
p0[idx] * Math.pow(t, 3);
|
|
544
|
+
};
|
|
545
|
+
export const polyLineFromCurve = (curve, segments = 10) => {
|
|
546
|
+
const equation = cubicBezierEquation(curve);
|
|
547
|
+
let startingPoint = [equation(0, 0), equation(0, 1)];
|
|
548
|
+
const lineSegments = [];
|
|
549
|
+
let t = 0;
|
|
550
|
+
const increment = 1 / segments;
|
|
551
|
+
for (let i = 0; i < segments; i++) {
|
|
552
|
+
t += increment;
|
|
553
|
+
if (t <= 1) {
|
|
554
|
+
const nextPoint = [equation(t, 0), equation(t, 1)];
|
|
555
|
+
lineSegments.push([startingPoint, nextPoint]);
|
|
556
|
+
startingPoint = nextPoint;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return lineSegments;
|
|
560
|
+
};
|
|
561
|
+
export const pointOnCurve = (point, curve, threshold = DEFAULT_THRESHOLD) => {
|
|
562
|
+
return pointOnPolyline(point, polyLineFromCurve(curve), threshold);
|
|
563
|
+
};
|
|
564
|
+
export const pointOnPolycurve = (point, polycurve, threshold = DEFAULT_THRESHOLD) => {
|
|
565
|
+
return polycurve.some((curve) => pointOnCurve(point, curve, threshold));
|
|
566
|
+
};
|
|
567
|
+
export const pointInPolygon = (point, polygon) => {
|
|
568
|
+
const x = point[0];
|
|
569
|
+
const y = point[1];
|
|
570
|
+
let inside = false;
|
|
571
|
+
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
572
|
+
const xi = polygon[i][0];
|
|
573
|
+
const yi = polygon[i][1];
|
|
574
|
+
const xj = polygon[j][0];
|
|
575
|
+
const yj = polygon[j][1];
|
|
576
|
+
if (((yi > y && yj <= y) || (yi <= y && yj > y)) &&
|
|
577
|
+
x < ((xj - xi) * (y - yi)) / (yj - yi) + xi) {
|
|
578
|
+
inside = !inside;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return inside;
|
|
582
|
+
};
|
|
583
|
+
export const pointOnPolygon = (point, polygon, threshold = DEFAULT_THRESHOLD) => {
|
|
584
|
+
let on = false;
|
|
585
|
+
const closed = close(polygon);
|
|
586
|
+
for (let i = 0, l = closed.length - 1; i < l; i++) {
|
|
587
|
+
if (pointOnLine(point, [closed[i], closed[i + 1]], threshold)) {
|
|
588
|
+
on = true;
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return on;
|
|
593
|
+
};
|
|
594
|
+
export const polygonInPolygon = (polygonA, polygonB) => {
|
|
595
|
+
let inside = true;
|
|
596
|
+
const closed = close(polygonA);
|
|
597
|
+
for (let i = 0, l = closed.length - 1; i < l; i++) {
|
|
598
|
+
const v0 = closed[i];
|
|
599
|
+
// Points test
|
|
600
|
+
if (!pointInPolygon(v0, polygonB)) {
|
|
601
|
+
inside = false;
|
|
602
|
+
break;
|
|
603
|
+
}
|
|
604
|
+
// Lines test
|
|
605
|
+
if (lineIntersectsPolygon([v0, closed[i + 1]], polygonB)) {
|
|
606
|
+
inside = false;
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return inside;
|
|
611
|
+
};
|
|
612
|
+
export const polygonIntersectPolygon = (polygonA, polygonB) => {
|
|
613
|
+
let intersects = false;
|
|
614
|
+
let onCount = 0;
|
|
615
|
+
const closed = close(polygonA);
|
|
616
|
+
for (let i = 0, l = closed.length - 1; i < l; i++) {
|
|
617
|
+
const v0 = closed[i];
|
|
618
|
+
const v1 = closed[i + 1];
|
|
619
|
+
if (lineIntersectsPolygon([v0, v1], polygonB)) {
|
|
620
|
+
intersects = true;
|
|
621
|
+
break;
|
|
622
|
+
}
|
|
623
|
+
if (pointOnPolygon(v0, polygonB)) {
|
|
624
|
+
++onCount;
|
|
625
|
+
}
|
|
626
|
+
if (onCount === 2) {
|
|
627
|
+
intersects = true;
|
|
628
|
+
break;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return intersects;
|
|
632
|
+
};
|
|
633
|
+
const distanceToEllipse = (point, ellipse) => {
|
|
634
|
+
const { angle, halfWidth, halfHeight, center } = ellipse;
|
|
635
|
+
const a = halfWidth;
|
|
636
|
+
const b = halfHeight;
|
|
637
|
+
const [rotatedPointX, rotatedPointY] = pointRelativeToCenter(point, center, angle);
|
|
638
|
+
const px = Math.abs(rotatedPointX);
|
|
639
|
+
const py = Math.abs(rotatedPointY);
|
|
640
|
+
let tx = 0.707;
|
|
641
|
+
let ty = 0.707;
|
|
642
|
+
for (let i = 0; i < 3; i++) {
|
|
643
|
+
const x = a * tx;
|
|
644
|
+
const y = b * ty;
|
|
645
|
+
const ex = ((a * a - b * b) * tx ** 3) / a;
|
|
646
|
+
const ey = ((b * b - a * a) * ty ** 3) / b;
|
|
647
|
+
const rx = x - ex;
|
|
648
|
+
const ry = y - ey;
|
|
649
|
+
const qx = px - ex;
|
|
650
|
+
const qy = py - ey;
|
|
651
|
+
const r = Math.hypot(ry, rx);
|
|
652
|
+
const q = Math.hypot(qy, qx);
|
|
653
|
+
tx = Math.min(1, Math.max(0, ((qx * r) / q + ex) / a));
|
|
654
|
+
ty = Math.min(1, Math.max(0, ((qy * r) / q + ey) / b));
|
|
655
|
+
const t = Math.hypot(ty, tx);
|
|
656
|
+
tx /= t;
|
|
657
|
+
ty /= t;
|
|
658
|
+
}
|
|
659
|
+
const [minX, minY] = [
|
|
660
|
+
a * tx * Math.sign(rotatedPointX),
|
|
661
|
+
b * ty * Math.sign(rotatedPointY),
|
|
662
|
+
];
|
|
663
|
+
return distanceToPoint([rotatedPointX, rotatedPointY], [minX, minY]);
|
|
664
|
+
};
|
|
665
|
+
export const pointOnEllipse = (point, ellipse, threshold = DEFAULT_THRESHOLD) => {
|
|
666
|
+
return distanceToEllipse(point, ellipse) <= threshold;
|
|
667
|
+
};
|
|
668
|
+
export const pointInEllipse = (point, ellipse) => {
|
|
669
|
+
const { center, angle, halfWidth, halfHeight } = ellipse;
|
|
670
|
+
const [rotatedPointX, rotatedPointY] = pointRelativeToCenter(point, center, angle);
|
|
671
|
+
return ((rotatedPointX / halfWidth) * (rotatedPointX / halfWidth) +
|
|
672
|
+
(rotatedPointY / halfHeight) * (rotatedPointY / halfHeight) <=
|
|
673
|
+
1);
|
|
674
|
+
};
|