@joint/core 4.0.0
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/LICENSE +376 -0
- package/README.md +49 -0
- package/dist/geometry.js +6486 -0
- package/dist/geometry.min.js +8 -0
- package/dist/joint.d.ts +5536 -0
- package/dist/joint.js +39629 -0
- package/dist/joint.min.js +8 -0
- package/dist/joint.nowrap.js +39626 -0
- package/dist/joint.nowrap.min.js +8 -0
- package/dist/vectorizer.js +9135 -0
- package/dist/vectorizer.min.js +8 -0
- package/dist/version.mjs +3 -0
- package/index.js +3 -0
- package/joint.mjs +27 -0
- package/package.json +192 -0
- package/src/V/annotation.mjs +0 -0
- package/src/V/index.mjs +2642 -0
- package/src/anchors/index.mjs +123 -0
- package/src/config/index.mjs +12 -0
- package/src/connectionPoints/index.mjs +202 -0
- package/src/connectionStrategies/index.mjs +73 -0
- package/src/connectors/curve.mjs +553 -0
- package/src/connectors/index.mjs +6 -0
- package/src/connectors/jumpover.mjs +452 -0
- package/src/connectors/normal.mjs +12 -0
- package/src/connectors/rounded.mjs +17 -0
- package/src/connectors/smooth.mjs +44 -0
- package/src/connectors/straight.mjs +110 -0
- package/src/dia/Cell.mjs +945 -0
- package/src/dia/CellView.mjs +1316 -0
- package/src/dia/Element.mjs +519 -0
- package/src/dia/ElementView.mjs +859 -0
- package/src/dia/Graph.mjs +1112 -0
- package/src/dia/HighlighterView.mjs +319 -0
- package/src/dia/Link.mjs +565 -0
- package/src/dia/LinkView.mjs +2207 -0
- package/src/dia/Paper.mjs +3171 -0
- package/src/dia/PaperLayer.mjs +75 -0
- package/src/dia/ToolView.mjs +69 -0
- package/src/dia/ToolsView.mjs +128 -0
- package/src/dia/attributes/calc.mjs +128 -0
- package/src/dia/attributes/connection.mjs +75 -0
- package/src/dia/attributes/defs.mjs +76 -0
- package/src/dia/attributes/eval.mjs +64 -0
- package/src/dia/attributes/index.mjs +69 -0
- package/src/dia/attributes/legacy.mjs +148 -0
- package/src/dia/attributes/offset.mjs +53 -0
- package/src/dia/attributes/props.mjs +30 -0
- package/src/dia/attributes/shape.mjs +92 -0
- package/src/dia/attributes/text.mjs +180 -0
- package/src/dia/index.mjs +13 -0
- package/src/dia/layers/GridLayer.mjs +176 -0
- package/src/dia/ports.mjs +874 -0
- package/src/elementTools/Control.mjs +153 -0
- package/src/elementTools/HoverConnect.mjs +37 -0
- package/src/elementTools/index.mjs +5 -0
- package/src/env/index.mjs +43 -0
- package/src/g/bezier.mjs +175 -0
- package/src/g/curve.mjs +956 -0
- package/src/g/ellipse.mjs +245 -0
- package/src/g/extend.mjs +64 -0
- package/src/g/geometry.helpers.mjs +58 -0
- package/src/g/index.mjs +17 -0
- package/src/g/intersection.mjs +511 -0
- package/src/g/line.bearing.mjs +30 -0
- package/src/g/line.length.mjs +5 -0
- package/src/g/line.mjs +356 -0
- package/src/g/line.squaredLength.mjs +10 -0
- package/src/g/path.mjs +2260 -0
- package/src/g/point.mjs +375 -0
- package/src/g/points.mjs +247 -0
- package/src/g/polygon.mjs +51 -0
- package/src/g/polyline.mjs +523 -0
- package/src/g/rect.mjs +556 -0
- package/src/g/types.mjs +10 -0
- package/src/highlighters/addClass.mjs +27 -0
- package/src/highlighters/index.mjs +5 -0
- package/src/highlighters/list.mjs +111 -0
- package/src/highlighters/mask.mjs +220 -0
- package/src/highlighters/opacity.mjs +17 -0
- package/src/highlighters/stroke.mjs +100 -0
- package/src/layout/index.mjs +4 -0
- package/src/layout/ports/port.mjs +188 -0
- package/src/layout/ports/portLabel.mjs +224 -0
- package/src/linkAnchors/index.mjs +76 -0
- package/src/linkTools/Anchor.mjs +235 -0
- package/src/linkTools/Arrowhead.mjs +103 -0
- package/src/linkTools/Boundary.mjs +48 -0
- package/src/linkTools/Button.mjs +121 -0
- package/src/linkTools/Connect.mjs +85 -0
- package/src/linkTools/HoverConnect.mjs +161 -0
- package/src/linkTools/Segments.mjs +393 -0
- package/src/linkTools/Vertices.mjs +253 -0
- package/src/linkTools/helpers.mjs +33 -0
- package/src/linkTools/index.mjs +8 -0
- package/src/mvc/Collection.mjs +560 -0
- package/src/mvc/Data.mjs +46 -0
- package/src/mvc/Dom/Dom.mjs +587 -0
- package/src/mvc/Dom/Event.mjs +130 -0
- package/src/mvc/Dom/animations.mjs +122 -0
- package/src/mvc/Dom/events.mjs +69 -0
- package/src/mvc/Dom/index.mjs +13 -0
- package/src/mvc/Dom/methods.mjs +392 -0
- package/src/mvc/Dom/props.mjs +77 -0
- package/src/mvc/Dom/vars.mjs +5 -0
- package/src/mvc/Events.mjs +337 -0
- package/src/mvc/Listener.mjs +33 -0
- package/src/mvc/Model.mjs +239 -0
- package/src/mvc/View.mjs +323 -0
- package/src/mvc/ViewBase.mjs +182 -0
- package/src/mvc/index.mjs +9 -0
- package/src/mvc/mvcUtils.mjs +90 -0
- package/src/polyfills/array.js +4 -0
- package/src/polyfills/base64.js +68 -0
- package/src/polyfills/index.mjs +5 -0
- package/src/polyfills/number.js +3 -0
- package/src/polyfills/string.js +3 -0
- package/src/polyfills/typedArray.js +47 -0
- package/src/routers/index.mjs +6 -0
- package/src/routers/manhattan.mjs +856 -0
- package/src/routers/metro.mjs +91 -0
- package/src/routers/normal.mjs +6 -0
- package/src/routers/oneSide.mjs +60 -0
- package/src/routers/orthogonal.mjs +323 -0
- package/src/routers/rightAngle.mjs +1056 -0
- package/src/shapes/index.mjs +3 -0
- package/src/shapes/standard.mjs +755 -0
- package/src/util/cloneCells.mjs +67 -0
- package/src/util/getRectPoint.mjs +65 -0
- package/src/util/index.mjs +5 -0
- package/src/util/svgTagTemplate.mjs +110 -0
- package/src/util/util.mjs +1754 -0
- package/src/util/utilHelpers.mjs +2402 -0
- package/src/util/wrappers.mjs +56 -0
- package/types/geometry.d.ts +815 -0
- package/types/index.d.ts +53 -0
- package/types/joint.d.ts +4391 -0
- package/types/joint.head.d.ts +12 -0
- package/types/vectorizer.d.ts +327 -0
package/src/g/curve.mjs
ADDED
|
@@ -0,0 +1,956 @@
|
|
|
1
|
+
import { Point } from './point.mjs';
|
|
2
|
+
import { Rect } from './rect.mjs';
|
|
3
|
+
import { Line } from './line.mjs';
|
|
4
|
+
import { Polyline } from './polyline.mjs';
|
|
5
|
+
import { types } from './types.mjs';
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
abs,
|
|
9
|
+
sqrt,
|
|
10
|
+
min,
|
|
11
|
+
max,
|
|
12
|
+
pow
|
|
13
|
+
} = Math;
|
|
14
|
+
|
|
15
|
+
export const Curve = function(p1, p2, p3, p4) {
|
|
16
|
+
|
|
17
|
+
if (!(this instanceof Curve)) {
|
|
18
|
+
return new Curve(p1, p2, p3, p4);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (p1 instanceof Curve) {
|
|
22
|
+
return new Curve(p1.start, p1.controlPoint1, p1.controlPoint2, p1.end);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this.start = new Point(p1);
|
|
26
|
+
this.controlPoint1 = new Point(p2);
|
|
27
|
+
this.controlPoint2 = new Point(p3);
|
|
28
|
+
this.end = new Point(p4);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Curve passing through points.
|
|
32
|
+
// Ported from C# implementation by Oleg V. Polikarpotchkin and Peter Lee (http://www.codeproject.com/KB/graphics/BezierSpline.aspx).
|
|
33
|
+
// @param {array} points Array of points through which the smooth line will go.
|
|
34
|
+
// @return {array} curves.
|
|
35
|
+
Curve.throughPoints = (function() {
|
|
36
|
+
|
|
37
|
+
// Get open-ended Bezier Spline Control Points.
|
|
38
|
+
// @param knots Input Knot Bezier spline points (At least two points!).
|
|
39
|
+
// @param firstControlPoints Output First Control points. Array of knots.length - 1 length.
|
|
40
|
+
// @param secondControlPoints Output Second Control points. Array of knots.length - 1 length.
|
|
41
|
+
function getCurveControlPoints(knots) {
|
|
42
|
+
|
|
43
|
+
var firstControlPoints = [];
|
|
44
|
+
var secondControlPoints = [];
|
|
45
|
+
var n = knots.length - 1;
|
|
46
|
+
var i;
|
|
47
|
+
|
|
48
|
+
// Special case: Bezier curve should be a straight line.
|
|
49
|
+
if (n == 1) {
|
|
50
|
+
// 3P1 = 2P0 + P3
|
|
51
|
+
firstControlPoints[0] = new Point(
|
|
52
|
+
(2 * knots[0].x + knots[1].x) / 3,
|
|
53
|
+
(2 * knots[0].y + knots[1].y) / 3
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// P2 = 2P1 – P0
|
|
57
|
+
secondControlPoints[0] = new Point(
|
|
58
|
+
2 * firstControlPoints[0].x - knots[0].x,
|
|
59
|
+
2 * firstControlPoints[0].y - knots[0].y
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
return [firstControlPoints, secondControlPoints];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Calculate first Bezier control points.
|
|
66
|
+
// Right hand side vector.
|
|
67
|
+
var rhs = [];
|
|
68
|
+
|
|
69
|
+
// Set right hand side X values.
|
|
70
|
+
for (i = 1; i < n - 1; i++) {
|
|
71
|
+
rhs[i] = 4 * knots[i].x + 2 * knots[i + 1].x;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
rhs[0] = knots[0].x + 2 * knots[1].x;
|
|
75
|
+
rhs[n - 1] = (8 * knots[n - 1].x + knots[n].x) / 2.0;
|
|
76
|
+
|
|
77
|
+
// Get first control points X-values.
|
|
78
|
+
var x = getFirstControlPoints(rhs);
|
|
79
|
+
|
|
80
|
+
// Set right hand side Y values.
|
|
81
|
+
for (i = 1; i < n - 1; ++i) {
|
|
82
|
+
rhs[i] = 4 * knots[i].y + 2 * knots[i + 1].y;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
rhs[0] = knots[0].y + 2 * knots[1].y;
|
|
86
|
+
rhs[n - 1] = (8 * knots[n - 1].y + knots[n].y) / 2.0;
|
|
87
|
+
|
|
88
|
+
// Get first control points Y-values.
|
|
89
|
+
var y = getFirstControlPoints(rhs);
|
|
90
|
+
|
|
91
|
+
// Fill output arrays.
|
|
92
|
+
for (i = 0; i < n; i++) {
|
|
93
|
+
// First control point.
|
|
94
|
+
firstControlPoints.push(new Point(x[i], y[i]));
|
|
95
|
+
|
|
96
|
+
// Second control point.
|
|
97
|
+
if (i < n - 1) {
|
|
98
|
+
secondControlPoints.push(new Point(
|
|
99
|
+
2 * knots [i + 1].x - x[i + 1],
|
|
100
|
+
2 * knots[i + 1].y - y[i + 1]
|
|
101
|
+
));
|
|
102
|
+
|
|
103
|
+
} else {
|
|
104
|
+
secondControlPoints.push(new Point(
|
|
105
|
+
(knots[n].x + x[n - 1]) / 2,
|
|
106
|
+
(knots[n].y + y[n - 1]) / 2
|
|
107
|
+
));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return [firstControlPoints, secondControlPoints];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Solves a tridiagonal system for one of coordinates (x or y) of first Bezier control points.
|
|
115
|
+
// @param rhs Right hand side vector.
|
|
116
|
+
// @return Solution vector.
|
|
117
|
+
function getFirstControlPoints(rhs) {
|
|
118
|
+
|
|
119
|
+
var n = rhs.length;
|
|
120
|
+
// `x` is a solution vector.
|
|
121
|
+
var x = [];
|
|
122
|
+
var tmp = [];
|
|
123
|
+
var b = 2.0;
|
|
124
|
+
|
|
125
|
+
x[0] = rhs[0] / b;
|
|
126
|
+
|
|
127
|
+
// Decomposition and forward substitution.
|
|
128
|
+
for (var i = 1; i < n; i++) {
|
|
129
|
+
tmp[i] = 1 / b;
|
|
130
|
+
b = (i < n - 1 ? 4.0 : 3.5) - tmp[i];
|
|
131
|
+
x[i] = (rhs[i] - x[i - 1]) / b;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (i = 1; i < n; i++) {
|
|
135
|
+
// Backsubstitution.
|
|
136
|
+
x[n - i - 1] -= tmp[n - i] * x[n - i];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return x;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return function(points) {
|
|
143
|
+
|
|
144
|
+
if (!points || (Array.isArray(points) && points.length < 2)) {
|
|
145
|
+
throw new Error('At least 2 points are required');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
var controlPoints = getCurveControlPoints(points);
|
|
149
|
+
|
|
150
|
+
var curves = [];
|
|
151
|
+
var n = controlPoints[0].length;
|
|
152
|
+
for (var i = 0; i < n; i++) {
|
|
153
|
+
|
|
154
|
+
var controlPoint1 = new Point(controlPoints[0][i].x, controlPoints[0][i].y);
|
|
155
|
+
var controlPoint2 = new Point(controlPoints[1][i].x, controlPoints[1][i].y);
|
|
156
|
+
|
|
157
|
+
curves.push(new Curve(points[i], controlPoint1, controlPoint2, points[i + 1]));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return curves;
|
|
161
|
+
};
|
|
162
|
+
})();
|
|
163
|
+
|
|
164
|
+
Curve.prototype = {
|
|
165
|
+
|
|
166
|
+
type: types.Curve,
|
|
167
|
+
|
|
168
|
+
// Returns a bbox that tightly envelops the curve.
|
|
169
|
+
bbox: function() {
|
|
170
|
+
|
|
171
|
+
var start = this.start;
|
|
172
|
+
var controlPoint1 = this.controlPoint1;
|
|
173
|
+
var controlPoint2 = this.controlPoint2;
|
|
174
|
+
var end = this.end;
|
|
175
|
+
|
|
176
|
+
var x0 = start.x;
|
|
177
|
+
var y0 = start.y;
|
|
178
|
+
var x1 = controlPoint1.x;
|
|
179
|
+
var y1 = controlPoint1.y;
|
|
180
|
+
var x2 = controlPoint2.x;
|
|
181
|
+
var y2 = controlPoint2.y;
|
|
182
|
+
var x3 = end.x;
|
|
183
|
+
var y3 = end.y;
|
|
184
|
+
|
|
185
|
+
var points = new Array(); // local extremes
|
|
186
|
+
var tvalues = new Array(); // t values of local extremes
|
|
187
|
+
var bounds = [new Array(), new Array()];
|
|
188
|
+
|
|
189
|
+
var a, b, c, t;
|
|
190
|
+
var t1, t2;
|
|
191
|
+
var b2ac, sqrtb2ac;
|
|
192
|
+
|
|
193
|
+
for (var i = 0; i < 2; ++i) {
|
|
194
|
+
|
|
195
|
+
if (i === 0) {
|
|
196
|
+
b = 6 * x0 - 12 * x1 + 6 * x2;
|
|
197
|
+
a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
|
|
198
|
+
c = 3 * x1 - 3 * x0;
|
|
199
|
+
|
|
200
|
+
} else {
|
|
201
|
+
b = 6 * y0 - 12 * y1 + 6 * y2;
|
|
202
|
+
a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
|
|
203
|
+
c = 3 * y1 - 3 * y0;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (abs(a) < 1e-12) { // Numerical robustness
|
|
207
|
+
if (abs(b) < 1e-12) { // Numerical robustness
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
t = -c / b;
|
|
212
|
+
if ((0 < t) && (t < 1)) tvalues.push(t);
|
|
213
|
+
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
b2ac = b * b - 4 * c * a;
|
|
218
|
+
sqrtb2ac = sqrt(b2ac);
|
|
219
|
+
|
|
220
|
+
if (b2ac < 0) continue;
|
|
221
|
+
|
|
222
|
+
t1 = (-b + sqrtb2ac) / (2 * a);
|
|
223
|
+
if ((0 < t1) && (t1 < 1)) tvalues.push(t1);
|
|
224
|
+
|
|
225
|
+
t2 = (-b - sqrtb2ac) / (2 * a);
|
|
226
|
+
if ((0 < t2) && (t2 < 1)) tvalues.push(t2);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
var j = tvalues.length;
|
|
230
|
+
var jlen = j;
|
|
231
|
+
var mt;
|
|
232
|
+
var x, y;
|
|
233
|
+
|
|
234
|
+
while (j--) {
|
|
235
|
+
t = tvalues[j];
|
|
236
|
+
mt = 1 - t;
|
|
237
|
+
|
|
238
|
+
x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
|
|
239
|
+
bounds[0][j] = x;
|
|
240
|
+
|
|
241
|
+
y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
|
|
242
|
+
bounds[1][j] = y;
|
|
243
|
+
|
|
244
|
+
points[j] = { X: x, Y: y };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
tvalues[jlen] = 0;
|
|
248
|
+
tvalues[jlen + 1] = 1;
|
|
249
|
+
|
|
250
|
+
points[jlen] = { X: x0, Y: y0 };
|
|
251
|
+
points[jlen + 1] = { X: x3, Y: y3 };
|
|
252
|
+
|
|
253
|
+
bounds[0][jlen] = x0;
|
|
254
|
+
bounds[1][jlen] = y0;
|
|
255
|
+
|
|
256
|
+
bounds[0][jlen + 1] = x3;
|
|
257
|
+
bounds[1][jlen + 1] = y3;
|
|
258
|
+
|
|
259
|
+
tvalues.length = jlen + 2;
|
|
260
|
+
bounds[0].length = jlen + 2;
|
|
261
|
+
bounds[1].length = jlen + 2;
|
|
262
|
+
points.length = jlen + 2;
|
|
263
|
+
|
|
264
|
+
var left = min.apply(null, bounds[0]);
|
|
265
|
+
var top = min.apply(null, bounds[1]);
|
|
266
|
+
var right = max.apply(null, bounds[0]);
|
|
267
|
+
var bottom = max.apply(null, bounds[1]);
|
|
268
|
+
|
|
269
|
+
return new Rect(left, top, (right - left), (bottom - top));
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
clone: function() {
|
|
273
|
+
|
|
274
|
+
return new Curve(this.start, this.controlPoint1, this.controlPoint2, this.end);
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
// Returns the point on the curve closest to point `p`
|
|
278
|
+
closestPoint: function(p, opt) {
|
|
279
|
+
|
|
280
|
+
return this.pointAtT(this.closestPointT(p, opt));
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
closestPointLength: function(p, opt) {
|
|
284
|
+
|
|
285
|
+
opt = opt || {};
|
|
286
|
+
var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
|
|
287
|
+
var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions;
|
|
288
|
+
var localOpt = { precision: precision, subdivisions: subdivisions };
|
|
289
|
+
|
|
290
|
+
return this.lengthAtT(this.closestPointT(p, localOpt), localOpt);
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
closestPointNormalizedLength: function(p, opt) {
|
|
294
|
+
|
|
295
|
+
opt = opt || {};
|
|
296
|
+
var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
|
|
297
|
+
var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions;
|
|
298
|
+
var localOpt = { precision: precision, subdivisions: subdivisions };
|
|
299
|
+
|
|
300
|
+
var cpLength = this.closestPointLength(p, localOpt);
|
|
301
|
+
if (!cpLength) return 0;
|
|
302
|
+
|
|
303
|
+
var length = this.length(localOpt);
|
|
304
|
+
if (length === 0) return 0;
|
|
305
|
+
|
|
306
|
+
return cpLength / length;
|
|
307
|
+
},
|
|
308
|
+
|
|
309
|
+
// Returns `t` of the point on the curve closest to point `p`
|
|
310
|
+
closestPointT: function(p, opt) {
|
|
311
|
+
|
|
312
|
+
opt = opt || {};
|
|
313
|
+
var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
|
|
314
|
+
var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions;
|
|
315
|
+
// does not use localOpt
|
|
316
|
+
|
|
317
|
+
// identify the subdivision that contains the point:
|
|
318
|
+
var investigatedSubdivision;
|
|
319
|
+
var investigatedSubdivisionStartT; // assume that subdivisions are evenly spaced
|
|
320
|
+
var investigatedSubdivisionEndT;
|
|
321
|
+
var distFromStart; // distance of point from start of baseline
|
|
322
|
+
var distFromEnd; // distance of point from end of baseline
|
|
323
|
+
var chordLength; // distance between start and end of the subdivision
|
|
324
|
+
var minSumDist; // lowest observed sum of the two distances
|
|
325
|
+
var n = subdivisions.length;
|
|
326
|
+
var subdivisionSize = (n ? (1 / n) : 0);
|
|
327
|
+
for (var i = 0; i < n; i++) {
|
|
328
|
+
|
|
329
|
+
var currentSubdivision = subdivisions[i];
|
|
330
|
+
|
|
331
|
+
var startDist = currentSubdivision.start.distance(p);
|
|
332
|
+
var endDist = currentSubdivision.end.distance(p);
|
|
333
|
+
var sumDist = startDist + endDist;
|
|
334
|
+
|
|
335
|
+
// check that the point is closest to current subdivision and not any other
|
|
336
|
+
if (!minSumDist || (sumDist < minSumDist)) {
|
|
337
|
+
investigatedSubdivision = currentSubdivision;
|
|
338
|
+
|
|
339
|
+
investigatedSubdivisionStartT = i * subdivisionSize;
|
|
340
|
+
investigatedSubdivisionEndT = (i + 1) * subdivisionSize;
|
|
341
|
+
|
|
342
|
+
distFromStart = startDist;
|
|
343
|
+
distFromEnd = endDist;
|
|
344
|
+
|
|
345
|
+
chordLength = currentSubdivision.start.distance(currentSubdivision.end);
|
|
346
|
+
|
|
347
|
+
minSumDist = sumDist;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
var precisionRatio = pow(10, -precision);
|
|
352
|
+
|
|
353
|
+
// recursively divide investigated subdivision:
|
|
354
|
+
// until distance between baselinePoint and closest path endpoint is within 10^(-precision)
|
|
355
|
+
// then return the closest endpoint of that final subdivision
|
|
356
|
+
while (true) {
|
|
357
|
+
|
|
358
|
+
// check if we have reached at least one required observed precision
|
|
359
|
+
// - calculated as: the difference in distances from point to start and end divided by the distance
|
|
360
|
+
// - note that this function is not monotonic = it doesn't converge stably but has "teeth"
|
|
361
|
+
// - the function decreases while one of the endpoints is fixed but "jumps" whenever we switch
|
|
362
|
+
// - this criterion works well for points lying far away from the curve
|
|
363
|
+
var startPrecisionRatio = (distFromStart ? (abs(distFromStart - distFromEnd) / distFromStart) : 0);
|
|
364
|
+
var endPrecisionRatio = (distFromEnd ? (abs(distFromStart - distFromEnd) / distFromEnd) : 0);
|
|
365
|
+
var hasRequiredPrecision = ((startPrecisionRatio < precisionRatio) || (endPrecisionRatio < precisionRatio));
|
|
366
|
+
|
|
367
|
+
// check if we have reached at least one required minimal distance
|
|
368
|
+
// - calculated as: the subdivision chord length multiplied by precisionRatio
|
|
369
|
+
// - calculation is relative so it will work for arbitrarily large/small curves and their subdivisions
|
|
370
|
+
// - this is a backup criterion that works well for points lying "almost at" the curve
|
|
371
|
+
var hasMinimalStartDistance = (distFromStart ? (distFromStart < (chordLength * precisionRatio)) : true);
|
|
372
|
+
var hasMinimalEndDistance = (distFromEnd ? (distFromEnd < (chordLength * precisionRatio)) : true);
|
|
373
|
+
var hasMinimalDistance = (hasMinimalStartDistance || hasMinimalEndDistance);
|
|
374
|
+
|
|
375
|
+
// do we stop now?
|
|
376
|
+
if (hasRequiredPrecision || hasMinimalDistance) {
|
|
377
|
+
return ((distFromStart <= distFromEnd) ? investigatedSubdivisionStartT : investigatedSubdivisionEndT);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// otherwise, set up for next iteration
|
|
381
|
+
var divided = investigatedSubdivision.divide(0.5);
|
|
382
|
+
subdivisionSize /= 2;
|
|
383
|
+
|
|
384
|
+
var startDist1 = divided[0].start.distance(p);
|
|
385
|
+
var endDist1 = divided[0].end.distance(p);
|
|
386
|
+
var sumDist1 = startDist1 + endDist1;
|
|
387
|
+
|
|
388
|
+
var startDist2 = divided[1].start.distance(p);
|
|
389
|
+
var endDist2 = divided[1].end.distance(p);
|
|
390
|
+
var sumDist2 = startDist2 + endDist2;
|
|
391
|
+
|
|
392
|
+
if (sumDist1 <= sumDist2) {
|
|
393
|
+
investigatedSubdivision = divided[0];
|
|
394
|
+
|
|
395
|
+
investigatedSubdivisionEndT -= subdivisionSize; // subdivisionSize was already halved
|
|
396
|
+
|
|
397
|
+
distFromStart = startDist1;
|
|
398
|
+
distFromEnd = endDist1;
|
|
399
|
+
|
|
400
|
+
} else {
|
|
401
|
+
investigatedSubdivision = divided[1];
|
|
402
|
+
|
|
403
|
+
investigatedSubdivisionStartT += subdivisionSize; // subdivisionSize was already halved
|
|
404
|
+
|
|
405
|
+
distFromStart = startDist2;
|
|
406
|
+
distFromEnd = endDist2;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
|
|
411
|
+
closestPointTangent: function(p, opt) {
|
|
412
|
+
|
|
413
|
+
return this.tangentAtT(this.closestPointT(p, opt));
|
|
414
|
+
},
|
|
415
|
+
|
|
416
|
+
// Returns `true` if the area surrounded by the curve contains the point `p`.
|
|
417
|
+
// Implements the even-odd algorithm (self-intersections are "outside").
|
|
418
|
+
// Closes open curves (always imagines a closing segment).
|
|
419
|
+
// Precision may be adjusted by passing an `opt` object.
|
|
420
|
+
containsPoint: function(p, opt) {
|
|
421
|
+
|
|
422
|
+
var polyline = this.toPolyline(opt);
|
|
423
|
+
return polyline.containsPoint(p);
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
// Divides the curve into two at requested `ratio` between 0 and 1 with precision better than `opt.precision`; optionally using `opt.subdivisions` provided.
|
|
427
|
+
// For a function that uses `t`, use Curve.divideAtT().
|
|
428
|
+
divideAt: function(ratio, opt) {
|
|
429
|
+
|
|
430
|
+
if (ratio <= 0) return this.divideAtT(0);
|
|
431
|
+
if (ratio >= 1) return this.divideAtT(1);
|
|
432
|
+
|
|
433
|
+
var t = this.tAt(ratio, opt);
|
|
434
|
+
|
|
435
|
+
return this.divideAtT(t);
|
|
436
|
+
},
|
|
437
|
+
|
|
438
|
+
// Divides the curve into two at requested `length` with precision better than requested `opt.precision`; optionally using `opt.subdivisions` provided.
|
|
439
|
+
divideAtLength: function(length, opt) {
|
|
440
|
+
|
|
441
|
+
var t = this.tAtLength(length, opt);
|
|
442
|
+
|
|
443
|
+
return this.divideAtT(t);
|
|
444
|
+
},
|
|
445
|
+
|
|
446
|
+
// Divides the curve into two at point defined by `t` between 0 and 1.
|
|
447
|
+
// Using de Casteljau's algorithm (http://math.stackexchange.com/a/317867).
|
|
448
|
+
// Additional resource: https://pomax.github.io/bezierinfo/#decasteljau
|
|
449
|
+
divideAtT: function(t) {
|
|
450
|
+
|
|
451
|
+
var start = this.start;
|
|
452
|
+
var controlPoint1 = this.controlPoint1;
|
|
453
|
+
var controlPoint2 = this.controlPoint2;
|
|
454
|
+
var end = this.end;
|
|
455
|
+
|
|
456
|
+
// shortcuts for `t` values that are out of range
|
|
457
|
+
if (t <= 0) {
|
|
458
|
+
return [
|
|
459
|
+
new Curve(start, start, start, start),
|
|
460
|
+
new Curve(start, controlPoint1, controlPoint2, end)
|
|
461
|
+
];
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (t >= 1) {
|
|
465
|
+
return [
|
|
466
|
+
new Curve(start, controlPoint1, controlPoint2, end),
|
|
467
|
+
new Curve(end, end, end, end)
|
|
468
|
+
];
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
var dividerPoints = this.getSkeletonPoints(t);
|
|
472
|
+
|
|
473
|
+
var startControl1 = dividerPoints.startControlPoint1;
|
|
474
|
+
var startControl2 = dividerPoints.startControlPoint2;
|
|
475
|
+
var divider = dividerPoints.divider;
|
|
476
|
+
var dividerControl1 = dividerPoints.dividerControlPoint1;
|
|
477
|
+
var dividerControl2 = dividerPoints.dividerControlPoint2;
|
|
478
|
+
|
|
479
|
+
// return array with two new curves
|
|
480
|
+
return [
|
|
481
|
+
new Curve(start, startControl1, startControl2, divider),
|
|
482
|
+
new Curve(divider, dividerControl1, dividerControl2, end)
|
|
483
|
+
];
|
|
484
|
+
},
|
|
485
|
+
|
|
486
|
+
// Returns the distance between the curve's start and end points.
|
|
487
|
+
endpointDistance: function() {
|
|
488
|
+
|
|
489
|
+
return this.start.distance(this.end);
|
|
490
|
+
},
|
|
491
|
+
|
|
492
|
+
// Checks whether two curves are exactly the same.
|
|
493
|
+
equals: function(c) {
|
|
494
|
+
|
|
495
|
+
return !!c &&
|
|
496
|
+
this.start.x === c.start.x &&
|
|
497
|
+
this.start.y === c.start.y &&
|
|
498
|
+
this.controlPoint1.x === c.controlPoint1.x &&
|
|
499
|
+
this.controlPoint1.y === c.controlPoint1.y &&
|
|
500
|
+
this.controlPoint2.x === c.controlPoint2.x &&
|
|
501
|
+
this.controlPoint2.y === c.controlPoint2.y &&
|
|
502
|
+
this.end.x === c.end.x &&
|
|
503
|
+
this.end.y === c.end.y;
|
|
504
|
+
},
|
|
505
|
+
|
|
506
|
+
// Returns five helper points necessary for curve division.
|
|
507
|
+
getSkeletonPoints: function(t) {
|
|
508
|
+
|
|
509
|
+
var start = this.start;
|
|
510
|
+
var control1 = this.controlPoint1;
|
|
511
|
+
var control2 = this.controlPoint2;
|
|
512
|
+
var end = this.end;
|
|
513
|
+
|
|
514
|
+
// shortcuts for `t` values that are out of range
|
|
515
|
+
if (t <= 0) {
|
|
516
|
+
return {
|
|
517
|
+
startControlPoint1: start.clone(),
|
|
518
|
+
startControlPoint2: start.clone(),
|
|
519
|
+
divider: start.clone(),
|
|
520
|
+
dividerControlPoint1: control1.clone(),
|
|
521
|
+
dividerControlPoint2: control2.clone()
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (t >= 1) {
|
|
526
|
+
return {
|
|
527
|
+
startControlPoint1: control1.clone(),
|
|
528
|
+
startControlPoint2: control2.clone(),
|
|
529
|
+
divider: end.clone(),
|
|
530
|
+
dividerControlPoint1: end.clone(),
|
|
531
|
+
dividerControlPoint2: end.clone()
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
var midpoint1 = (new Line(start, control1)).pointAt(t);
|
|
536
|
+
var midpoint2 = (new Line(control1, control2)).pointAt(t);
|
|
537
|
+
var midpoint3 = (new Line(control2, end)).pointAt(t);
|
|
538
|
+
|
|
539
|
+
var subControl1 = (new Line(midpoint1, midpoint2)).pointAt(t);
|
|
540
|
+
var subControl2 = (new Line(midpoint2, midpoint3)).pointAt(t);
|
|
541
|
+
|
|
542
|
+
var divider = (new Line(subControl1, subControl2)).pointAt(t);
|
|
543
|
+
|
|
544
|
+
var output = {
|
|
545
|
+
startControlPoint1: midpoint1,
|
|
546
|
+
startControlPoint2: subControl1,
|
|
547
|
+
divider: divider,
|
|
548
|
+
dividerControlPoint1: subControl2,
|
|
549
|
+
dividerControlPoint2: midpoint3
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
return output;
|
|
553
|
+
},
|
|
554
|
+
|
|
555
|
+
// Returns a list of curves whose flattened length is better than `opt.precision`.
|
|
556
|
+
// That is, observed difference in length between recursions is less than 10^(-3) = 0.001 = 0.1%
|
|
557
|
+
// (Observed difference is not real precision, but close enough as long as special cases are covered)
|
|
558
|
+
// As a rule of thumb, increasing `precision` by 1 requires 2 more iterations (= levels of division operations)
|
|
559
|
+
// - Precision 0 (endpointDistance) - 0 iterations => total of 2^0 - 1 = 0 operations (1 subdivision)
|
|
560
|
+
// - Precision 1 (<10% error) - 2 iterations => total of 2^2 - 1 = 3 operations (4 subdivisions)
|
|
561
|
+
// - Precision 2 (<1% error) - 4 iterations => total of 2^4 - 1 = 15 operations requires 4 division operations on all elements (15 operations total) (16 subdivisions)
|
|
562
|
+
// - Precision 3 (<0.1% error) - 6 iterations => total of 2^6 - 1 = 63 operations - acceptable when drawing (64 subdivisions)
|
|
563
|
+
// - Precision 4 (<0.01% error) - 8 iterations => total of 2^8 - 1 = 255 operations - high resolution, can be used to interpolate `t` (256 subdivisions)
|
|
564
|
+
// (Variation of 1 recursion worse or better is possible depending on the curve, doubling/halving the number of operations accordingly)
|
|
565
|
+
getSubdivisions: function(opt) {
|
|
566
|
+
|
|
567
|
+
opt = opt || {};
|
|
568
|
+
var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
|
|
569
|
+
// not using opt.subdivisions
|
|
570
|
+
// not using localOpt
|
|
571
|
+
|
|
572
|
+
var start = this.start;
|
|
573
|
+
var control1 = this.controlPoint1;
|
|
574
|
+
var control2 = this.controlPoint2;
|
|
575
|
+
var end = this.end;
|
|
576
|
+
|
|
577
|
+
var subdivisions = [new Curve(start, control1, control2, end)];
|
|
578
|
+
if (precision === 0) return subdivisions;
|
|
579
|
+
|
|
580
|
+
// special case #1: point-like curves
|
|
581
|
+
// - no need to calculate subdivisions, they would all be identical
|
|
582
|
+
var isPoint = !this.isDifferentiable();
|
|
583
|
+
if (isPoint) return subdivisions;
|
|
584
|
+
|
|
585
|
+
var previousLength = this.endpointDistance();
|
|
586
|
+
|
|
587
|
+
var precisionRatio = pow(10, -precision);
|
|
588
|
+
|
|
589
|
+
// special case #2: sine-like curves may have the same observed length in iteration 0 and 1 - skip iteration 1
|
|
590
|
+
// - not a problem for further iterations because cubic curves cannot have more than two local extrema
|
|
591
|
+
// - (i.e. cubic curves cannot intersect the baseline more than once)
|
|
592
|
+
// - therefore starting from iteration = 2 ensures that subsequent iterations do not produce sampling with equal length
|
|
593
|
+
// - (unless it's a straight-line curve, see below)
|
|
594
|
+
var minIterations = 2; // = 2*1
|
|
595
|
+
|
|
596
|
+
// special case #3: straight-line curves have the same observed length in all iterations
|
|
597
|
+
// - this causes observed precision ratio to always be 0 (= lower than `precisionRatio`, which is our exit condition)
|
|
598
|
+
// - we enforce the expected number of iterations = 2 * precision
|
|
599
|
+
var isLine = ((control1.cross(start, end) === 0) && (control2.cross(start, end) === 0));
|
|
600
|
+
if (isLine) {
|
|
601
|
+
minIterations = (2 * precision);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// recursively divide curve at `t = 0.5`
|
|
605
|
+
// until we reach `minIterations`
|
|
606
|
+
// and until the difference between observed length at subsequent iterations is lower than `precision`
|
|
607
|
+
var iteration = 0;
|
|
608
|
+
while (true) {
|
|
609
|
+
iteration += 1;
|
|
610
|
+
|
|
611
|
+
// divide all subdivisions
|
|
612
|
+
var newSubdivisions = [];
|
|
613
|
+
var numSubdivisions = subdivisions.length;
|
|
614
|
+
for (var i = 0; i < numSubdivisions; i++) {
|
|
615
|
+
|
|
616
|
+
var currentSubdivision = subdivisions[i];
|
|
617
|
+
var divided = currentSubdivision.divide(0.5); // dividing at t = 0.5 (not at middle length!)
|
|
618
|
+
newSubdivisions.push(divided[0], divided[1]);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// measure new length
|
|
622
|
+
var length = 0;
|
|
623
|
+
var numNewSubdivisions = newSubdivisions.length;
|
|
624
|
+
for (var j = 0; j < numNewSubdivisions; j++) {
|
|
625
|
+
|
|
626
|
+
var currentNewSubdivision = newSubdivisions[j];
|
|
627
|
+
length += currentNewSubdivision.endpointDistance();
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// check if we have reached minimum number of iterations
|
|
631
|
+
if (iteration >= minIterations) {
|
|
632
|
+
|
|
633
|
+
// check if we have reached required observed precision
|
|
634
|
+
var observedPrecisionRatio = ((length !== 0) ? ((length - previousLength) / length) : 0);
|
|
635
|
+
if (observedPrecisionRatio < precisionRatio) {
|
|
636
|
+
return newSubdivisions;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// otherwise, set up for next iteration
|
|
641
|
+
subdivisions = newSubdivisions;
|
|
642
|
+
previousLength = length;
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
|
|
646
|
+
isDifferentiable: function() {
|
|
647
|
+
|
|
648
|
+
var start = this.start;
|
|
649
|
+
var control1 = this.controlPoint1;
|
|
650
|
+
var control2 = this.controlPoint2;
|
|
651
|
+
var end = this.end;
|
|
652
|
+
|
|
653
|
+
return !(start.equals(control1) && control1.equals(control2) && control2.equals(end));
|
|
654
|
+
},
|
|
655
|
+
|
|
656
|
+
// Returns flattened length of the curve with precision better than `opt.precision`; or using `opt.subdivisions` provided.
|
|
657
|
+
length: function(opt) {
|
|
658
|
+
|
|
659
|
+
opt = opt || {};
|
|
660
|
+
var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; // opt.precision only used in getSubdivisions() call
|
|
661
|
+
var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions;
|
|
662
|
+
// not using localOpt
|
|
663
|
+
|
|
664
|
+
var length = 0;
|
|
665
|
+
var n = subdivisions.length;
|
|
666
|
+
for (var i = 0; i < n; i++) {
|
|
667
|
+
|
|
668
|
+
var currentSubdivision = subdivisions[i];
|
|
669
|
+
length += currentSubdivision.endpointDistance();
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
return length;
|
|
673
|
+
},
|
|
674
|
+
|
|
675
|
+
// Returns distance along the curve up to `t` with precision better than requested `opt.precision`. (Not using `opt.subdivisions`.)
|
|
676
|
+
lengthAtT: function(t, opt) {
|
|
677
|
+
|
|
678
|
+
if (t <= 0) return 0;
|
|
679
|
+
|
|
680
|
+
opt = opt || {};
|
|
681
|
+
var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
|
|
682
|
+
// not using opt.subdivisions
|
|
683
|
+
// not using localOpt
|
|
684
|
+
|
|
685
|
+
var subCurve = this.divide(t)[0];
|
|
686
|
+
var subCurveLength = subCurve.length({ precision: precision });
|
|
687
|
+
|
|
688
|
+
return subCurveLength;
|
|
689
|
+
},
|
|
690
|
+
|
|
691
|
+
// Returns point at requested `ratio` between 0 and 1 with precision better than `opt.precision`; optionally using `opt.subdivisions` provided.
|
|
692
|
+
// Mirrors Line.pointAt() function.
|
|
693
|
+
// For a function that tracks `t`, use Curve.pointAtT().
|
|
694
|
+
pointAt: function(ratio, opt) {
|
|
695
|
+
|
|
696
|
+
if (ratio <= 0) return this.start.clone();
|
|
697
|
+
if (ratio >= 1) return this.end.clone();
|
|
698
|
+
|
|
699
|
+
var t = this.tAt(ratio, opt);
|
|
700
|
+
|
|
701
|
+
return this.pointAtT(t);
|
|
702
|
+
},
|
|
703
|
+
|
|
704
|
+
// Returns point at requested `length` with precision better than requested `opt.precision`; optionally using `opt.subdivisions` provided.
|
|
705
|
+
pointAtLength: function(length, opt) {
|
|
706
|
+
|
|
707
|
+
var t = this.tAtLength(length, opt);
|
|
708
|
+
|
|
709
|
+
return this.pointAtT(t);
|
|
710
|
+
},
|
|
711
|
+
|
|
712
|
+
// Returns the point at provided `t` between 0 and 1.
|
|
713
|
+
// `t` does not track distance along curve as it does in Line objects.
|
|
714
|
+
// Non-linear relationship, speeds up and slows down as curve warps!
|
|
715
|
+
// For linear length-based solution, use Curve.pointAt().
|
|
716
|
+
pointAtT: function(t) {
|
|
717
|
+
|
|
718
|
+
if (t <= 0) return this.start.clone();
|
|
719
|
+
if (t >= 1) return this.end.clone();
|
|
720
|
+
|
|
721
|
+
return this.getSkeletonPoints(t).divider;
|
|
722
|
+
},
|
|
723
|
+
|
|
724
|
+
// Default precision
|
|
725
|
+
PRECISION: 3,
|
|
726
|
+
|
|
727
|
+
round: function(precision) {
|
|
728
|
+
|
|
729
|
+
this.start.round(precision);
|
|
730
|
+
this.controlPoint1.round(precision);
|
|
731
|
+
this.controlPoint2.round(precision);
|
|
732
|
+
this.end.round(precision);
|
|
733
|
+
return this;
|
|
734
|
+
},
|
|
735
|
+
|
|
736
|
+
scale: function(sx, sy, origin) {
|
|
737
|
+
|
|
738
|
+
this.start.scale(sx, sy, origin);
|
|
739
|
+
this.controlPoint1.scale(sx, sy, origin);
|
|
740
|
+
this.controlPoint2.scale(sx, sy, origin);
|
|
741
|
+
this.end.scale(sx, sy, origin);
|
|
742
|
+
return this;
|
|
743
|
+
},
|
|
744
|
+
|
|
745
|
+
// Returns a tangent line at requested `ratio` with precision better than requested `opt.precision`; or using `opt.subdivisions` provided.
|
|
746
|
+
tangentAt: function(ratio, opt) {
|
|
747
|
+
|
|
748
|
+
if (!this.isDifferentiable()) return null;
|
|
749
|
+
|
|
750
|
+
if (ratio < 0) ratio = 0;
|
|
751
|
+
else if (ratio > 1) ratio = 1;
|
|
752
|
+
|
|
753
|
+
var t = this.tAt(ratio, opt);
|
|
754
|
+
|
|
755
|
+
return this.tangentAtT(t);
|
|
756
|
+
},
|
|
757
|
+
|
|
758
|
+
// Returns a tangent line at requested `length` with precision better than requested `opt.precision`; or using `opt.subdivisions` provided.
|
|
759
|
+
tangentAtLength: function(length, opt) {
|
|
760
|
+
|
|
761
|
+
if (!this.isDifferentiable()) return null;
|
|
762
|
+
|
|
763
|
+
var t = this.tAtLength(length, opt);
|
|
764
|
+
|
|
765
|
+
return this.tangentAtT(t);
|
|
766
|
+
},
|
|
767
|
+
|
|
768
|
+
// Returns a tangent line at requested `t`.
|
|
769
|
+
tangentAtT: function(t) {
|
|
770
|
+
|
|
771
|
+
if (!this.isDifferentiable()) return null;
|
|
772
|
+
|
|
773
|
+
if (t < 0) t = 0;
|
|
774
|
+
else if (t > 1) t = 1;
|
|
775
|
+
|
|
776
|
+
var skeletonPoints = this.getSkeletonPoints(t);
|
|
777
|
+
|
|
778
|
+
var p1 = skeletonPoints.startControlPoint2;
|
|
779
|
+
var p2 = skeletonPoints.dividerControlPoint1;
|
|
780
|
+
|
|
781
|
+
var tangentStart = skeletonPoints.divider;
|
|
782
|
+
|
|
783
|
+
var tangentLine = new Line(p1, p2);
|
|
784
|
+
tangentLine.translate(tangentStart.x - p1.x, tangentStart.y - p1.y); // move so that tangent line starts at the point requested
|
|
785
|
+
|
|
786
|
+
return tangentLine;
|
|
787
|
+
},
|
|
788
|
+
|
|
789
|
+
// Returns `t` at requested `ratio` with precision better than requested `opt.precision`; optionally using `opt.subdivisions` provided.
|
|
790
|
+
tAt: function(ratio, opt) {
|
|
791
|
+
|
|
792
|
+
if (ratio <= 0) return 0;
|
|
793
|
+
if (ratio >= 1) return 1;
|
|
794
|
+
|
|
795
|
+
opt = opt || {};
|
|
796
|
+
var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
|
|
797
|
+
var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions;
|
|
798
|
+
var localOpt = { precision: precision, subdivisions: subdivisions };
|
|
799
|
+
|
|
800
|
+
var curveLength = this.length(localOpt);
|
|
801
|
+
var length = curveLength * ratio;
|
|
802
|
+
|
|
803
|
+
return this.tAtLength(length, localOpt);
|
|
804
|
+
},
|
|
805
|
+
|
|
806
|
+
// Returns `t` at requested `length` with precision better than requested `opt.precision`; optionally using `opt.subdivisions` provided.
|
|
807
|
+
// Uses `precision` to approximate length within `precision` (always underestimates)
|
|
808
|
+
// Then uses a binary search to find the `t` of a subdivision endpoint that is close (within `precision`) to the `length`, if the curve was as long as approximated
|
|
809
|
+
// As a rule of thumb, increasing `precision` by 1 causes the algorithm to go 2^(precision - 1) deeper
|
|
810
|
+
// - Precision 0 (chooses one of the two endpoints) - 0 levels
|
|
811
|
+
// - Precision 1 (chooses one of 5 points, <10% error) - 1 level
|
|
812
|
+
// - Precision 2 (<1% error) - 3 levels
|
|
813
|
+
// - Precision 3 (<0.1% error) - 7 levels
|
|
814
|
+
// - Precision 4 (<0.01% error) - 15 levels
|
|
815
|
+
tAtLength: function(length, opt) {
|
|
816
|
+
|
|
817
|
+
var fromStart = true;
|
|
818
|
+
if (length < 0) {
|
|
819
|
+
fromStart = false; // negative lengths mean start calculation from end point
|
|
820
|
+
length = -length; // absolute value
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
opt = opt || {};
|
|
824
|
+
var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
|
|
825
|
+
var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions;
|
|
826
|
+
var localOpt = { precision: precision, subdivisions: subdivisions };
|
|
827
|
+
|
|
828
|
+
// identify the subdivision that contains the point at requested `length`:
|
|
829
|
+
var investigatedSubdivision;
|
|
830
|
+
var investigatedSubdivisionStartT; // assume that subdivisions are evenly spaced
|
|
831
|
+
var investigatedSubdivisionEndT;
|
|
832
|
+
//var baseline; // straightened version of subdivision to investigate
|
|
833
|
+
//var baselinePoint; // point on the baseline that is the requested distance away from start
|
|
834
|
+
var baselinePointDistFromStart; // distance of baselinePoint from start of baseline
|
|
835
|
+
var baselinePointDistFromEnd; // distance of baselinePoint from end of baseline
|
|
836
|
+
var l = 0; // length so far
|
|
837
|
+
var n = subdivisions.length;
|
|
838
|
+
var subdivisionSize = 1 / n;
|
|
839
|
+
for (var i = 0; i < n; i++) {
|
|
840
|
+
var index = (fromStart ? i : (n - 1 - i));
|
|
841
|
+
|
|
842
|
+
var currentSubdivision = subdivisions[i];
|
|
843
|
+
var d = currentSubdivision.endpointDistance(); // length of current subdivision
|
|
844
|
+
|
|
845
|
+
if (length <= (l + d)) {
|
|
846
|
+
investigatedSubdivision = currentSubdivision;
|
|
847
|
+
|
|
848
|
+
investigatedSubdivisionStartT = index * subdivisionSize;
|
|
849
|
+
investigatedSubdivisionEndT = (index + 1) * subdivisionSize;
|
|
850
|
+
|
|
851
|
+
baselinePointDistFromStart = (fromStart ? (length - l) : ((d + l) - length));
|
|
852
|
+
baselinePointDistFromEnd = (fromStart ? ((d + l) - length) : (length - l));
|
|
853
|
+
|
|
854
|
+
break;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
l += d;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
if (!investigatedSubdivision) return (fromStart ? 1 : 0); // length requested is out of range - return maximum t
|
|
861
|
+
// note that precision affects what length is recorded
|
|
862
|
+
// (imprecise measurements underestimate length by up to 10^(-precision) of the precise length)
|
|
863
|
+
// e.g. at precision 1, the length may be underestimated by up to 10% and cause this function to return 1
|
|
864
|
+
|
|
865
|
+
var curveLength = this.length(localOpt);
|
|
866
|
+
|
|
867
|
+
var precisionRatio = pow(10, -precision);
|
|
868
|
+
|
|
869
|
+
// recursively divide investigated subdivision:
|
|
870
|
+
// until distance between baselinePoint and closest path endpoint is within 10^(-precision)
|
|
871
|
+
// then return the closest endpoint of that final subdivision
|
|
872
|
+
while (true) {
|
|
873
|
+
|
|
874
|
+
// check if we have reached required observed precision
|
|
875
|
+
var observedPrecisionRatio;
|
|
876
|
+
|
|
877
|
+
observedPrecisionRatio = ((curveLength !== 0) ? (baselinePointDistFromStart / curveLength) : 0);
|
|
878
|
+
if (observedPrecisionRatio < precisionRatio) return investigatedSubdivisionStartT;
|
|
879
|
+
observedPrecisionRatio = ((curveLength !== 0) ? (baselinePointDistFromEnd / curveLength) : 0);
|
|
880
|
+
if (observedPrecisionRatio < precisionRatio) return investigatedSubdivisionEndT;
|
|
881
|
+
|
|
882
|
+
// otherwise, set up for next iteration
|
|
883
|
+
var newBaselinePointDistFromStart;
|
|
884
|
+
var newBaselinePointDistFromEnd;
|
|
885
|
+
|
|
886
|
+
var divided = investigatedSubdivision.divide(0.5);
|
|
887
|
+
subdivisionSize /= 2;
|
|
888
|
+
|
|
889
|
+
var baseline1Length = divided[0].endpointDistance();
|
|
890
|
+
var baseline2Length = divided[1].endpointDistance();
|
|
891
|
+
|
|
892
|
+
if (baselinePointDistFromStart <= baseline1Length) { // point at requested length is inside divided[0]
|
|
893
|
+
investigatedSubdivision = divided[0];
|
|
894
|
+
|
|
895
|
+
investigatedSubdivisionEndT -= subdivisionSize; // sudivisionSize was already halved
|
|
896
|
+
|
|
897
|
+
newBaselinePointDistFromStart = baselinePointDistFromStart;
|
|
898
|
+
newBaselinePointDistFromEnd = baseline1Length - newBaselinePointDistFromStart;
|
|
899
|
+
|
|
900
|
+
} else { // point at requested length is inside divided[1]
|
|
901
|
+
investigatedSubdivision = divided[1];
|
|
902
|
+
|
|
903
|
+
investigatedSubdivisionStartT += subdivisionSize; // subdivisionSize was already halved
|
|
904
|
+
|
|
905
|
+
newBaselinePointDistFromStart = baselinePointDistFromStart - baseline1Length;
|
|
906
|
+
newBaselinePointDistFromEnd = baseline2Length - newBaselinePointDistFromStart;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
baselinePointDistFromStart = newBaselinePointDistFromStart;
|
|
910
|
+
baselinePointDistFromEnd = newBaselinePointDistFromEnd;
|
|
911
|
+
}
|
|
912
|
+
},
|
|
913
|
+
|
|
914
|
+
// Returns an array of points that represents the curve when flattened, up to `opt.precision`; or using `opt.subdivisions` provided.
|
|
915
|
+
// Flattened length is no more than 10^(-precision) away from real curve length.
|
|
916
|
+
toPoints: function(opt) {
|
|
917
|
+
|
|
918
|
+
opt = opt || {};
|
|
919
|
+
var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; // opt.precision only used in getSubdivisions() call
|
|
920
|
+
var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions;
|
|
921
|
+
// not using localOpt
|
|
922
|
+
|
|
923
|
+
var points = [subdivisions[0].start.clone()];
|
|
924
|
+
var n = subdivisions.length;
|
|
925
|
+
for (var i = 0; i < n; i++) {
|
|
926
|
+
|
|
927
|
+
var currentSubdivision = subdivisions[i];
|
|
928
|
+
points.push(currentSubdivision.end.clone());
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
return points;
|
|
932
|
+
},
|
|
933
|
+
|
|
934
|
+
// Returns a polyline that represents the curve when flattened, up to `opt.precision`; or using `opt.subdivisions` provided.
|
|
935
|
+
// Flattened length is no more than 10^(-precision) away from real curve length.
|
|
936
|
+
toPolyline: function(opt) {
|
|
937
|
+
|
|
938
|
+
return new Polyline(this.toPoints(opt));
|
|
939
|
+
},
|
|
940
|
+
|
|
941
|
+
toString: function() {
|
|
942
|
+
|
|
943
|
+
return this.start + ' ' + this.controlPoint1 + ' ' + this.controlPoint2 + ' ' + this.end;
|
|
944
|
+
},
|
|
945
|
+
|
|
946
|
+
translate: function(tx, ty) {
|
|
947
|
+
|
|
948
|
+
this.start.translate(tx, ty);
|
|
949
|
+
this.controlPoint1.translate(tx, ty);
|
|
950
|
+
this.controlPoint2.translate(tx, ty);
|
|
951
|
+
this.end.translate(tx, ty);
|
|
952
|
+
return this;
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
|
|
956
|
+
Curve.prototype.divide = Curve.prototype.divideAtT;
|