toosoon-utils 4.0.6 → 4.1.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/README.md +716 -88
- package/lib/constants.d.ts +2 -0
- package/lib/constants.js +5 -3
- package/lib/extras/_wip/pool.d.ts +56 -0
- package/lib/extras/_wip/pool.js +67 -0
- package/lib/{classes/color-scale.d.ts → extras/color-scale/ColorScale.d.ts} +1 -1
- package/lib/{classes/color-scale.js → extras/color-scale/ColorScale.js} +2 -2
- package/lib/extras/curves/ArcCurve.d.ts +19 -0
- package/lib/extras/curves/ArcCurve.js +21 -0
- package/lib/extras/curves/CatmulRomCurve.d.ts +62 -0
- package/lib/extras/curves/CatmulRomCurve.js +75 -0
- package/lib/extras/curves/CubicBezierCurve.d.ts +62 -0
- package/lib/extras/curves/CubicBezierCurve.js +75 -0
- package/lib/extras/curves/Curve.d.ts +95 -0
- package/lib/extras/curves/Curve.js +174 -0
- package/lib/extras/curves/EllipseCurve.d.ts +63 -0
- package/lib/extras/curves/EllipseCurve.js +76 -0
- package/lib/extras/curves/LineCurve.d.ts +51 -0
- package/lib/extras/curves/LineCurve.js +71 -0
- package/lib/extras/curves/Path.d.ts +164 -0
- package/lib/extras/curves/Path.js +367 -0
- package/lib/extras/curves/PathCurve.d.ts +69 -0
- package/lib/extras/curves/PathCurve.js +148 -0
- package/lib/extras/curves/PathSVG.d.ts +41 -0
- package/lib/extras/curves/PathSVG.js +135 -0
- package/lib/extras/curves/PolylineCurve.d.ts +27 -0
- package/lib/extras/curves/PolylineCurve.js +39 -0
- package/lib/extras/curves/QuadraticBezierCurve.d.ts +52 -0
- package/lib/extras/curves/QuadraticBezierCurve.js +63 -0
- package/lib/extras/curves/SplineCurve.d.ts +24 -0
- package/lib/extras/curves/SplineCurve.js +38 -0
- package/lib/extras/curves/index.d.ts +12 -0
- package/lib/extras/curves/index.js +12 -0
- package/lib/{classes/frame-rate.js → extras/frame-rate/FrameRate.js} +1 -1
- package/lib/geometry.d.ts +87 -68
- package/lib/geometry.js +144 -140
- package/lib/prng.js +2 -1
- package/lib/random.d.ts +13 -13
- package/lib/random.js +14 -14
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/types.d.ts +3 -10
- package/package.json +4 -3
- /package/lib/{classes/_pool.d.ts → extras/_/pool.d.ts} +0 -0
- /package/lib/{classes/_pool.js → extras/_/pool.js} +0 -0
- /package/lib/{classes/frame-rate.d.ts → extras/frame-rate/FrameRate.d.ts} +0 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import { EPSILON, PI } from 'src/constants';
|
|
2
|
+
import { isCoincident, isCollinear } from 'src/geometry';
|
|
3
|
+
import LineCurve from './LineCurve';
|
|
4
|
+
import PolylineCurve from './PolylineCurve';
|
|
5
|
+
import QuadraticBezierCurve from './QuadraticBezierCurve';
|
|
6
|
+
import CubicBezierCurve from './CubicBezierCurve';
|
|
7
|
+
import CatmullRomCurve from './CatmulRomCurve';
|
|
8
|
+
import SplineCurve from './SplineCurve';
|
|
9
|
+
import EllipseCurve from './EllipseCurve';
|
|
10
|
+
import ArcCurve from './ArcCurve';
|
|
11
|
+
import PathCurve from './PathCurve';
|
|
12
|
+
/**
|
|
13
|
+
* Utility class for manipulating connected curves
|
|
14
|
+
*
|
|
15
|
+
* It works by providing methods for creating curves similar to the 2D Canvas API
|
|
16
|
+
*
|
|
17
|
+
*
|
|
18
|
+
* @exports
|
|
19
|
+
* @class CurvePath
|
|
20
|
+
* @extends PathCurve
|
|
21
|
+
*/
|
|
22
|
+
export default class Path extends PathCurve {
|
|
23
|
+
/**
|
|
24
|
+
* Path current offset
|
|
25
|
+
*/
|
|
26
|
+
currentPosition = [NaN, NaN];
|
|
27
|
+
/**
|
|
28
|
+
* Create a path from the given list of points
|
|
29
|
+
*
|
|
30
|
+
* @param {Point[]} points Array of points defining the path
|
|
31
|
+
* @param {Point[]} type Type of curve used for creating the path
|
|
32
|
+
* @return {this}
|
|
33
|
+
*/
|
|
34
|
+
setFromPoints(points, type) {
|
|
35
|
+
this.moveTo(...points[0]);
|
|
36
|
+
if (type === 'polyline')
|
|
37
|
+
return this.polylineTo(points);
|
|
38
|
+
if (type === 'spline')
|
|
39
|
+
return this.splineTo(points);
|
|
40
|
+
for (let i = 1, l = points.length; i < l; i++) {
|
|
41
|
+
this.lineTo(...points[i]);
|
|
42
|
+
}
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Move {@link Path#currentPosition} to the coordinates specified by `x` and `y`
|
|
47
|
+
*
|
|
48
|
+
* @param {number} x X-axis coordinate of the point
|
|
49
|
+
* @param {number} y Y-axis coordinate of the point
|
|
50
|
+
* @return {this}
|
|
51
|
+
*/
|
|
52
|
+
moveTo(x, y) {
|
|
53
|
+
this._setCurrentPosition(x, y);
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Draw a line from the current position to the position specified by `x` and `y`
|
|
58
|
+
* Add an instance of {@link LineCurve} to the path
|
|
59
|
+
*
|
|
60
|
+
* @param {number} x X-axis coordinate of the point
|
|
61
|
+
* @param {number} y Y-axis coordinate of the point
|
|
62
|
+
* @return {this}
|
|
63
|
+
*/
|
|
64
|
+
lineTo(x, y) {
|
|
65
|
+
if (this.currentPosition.every(isNaN))
|
|
66
|
+
return this.moveTo(x, y);
|
|
67
|
+
const curve = new LineCurve(...this.currentPosition, x, y);
|
|
68
|
+
this.add(curve);
|
|
69
|
+
this._setCurrentPosition(x, y);
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Draw a Polyline curve from the current position through the given points
|
|
74
|
+
* Add an instance of {@link PolylineCurve} to the path
|
|
75
|
+
*
|
|
76
|
+
* @param {Point[]} points Array of points defining the curve
|
|
77
|
+
* @returns {this}
|
|
78
|
+
*/
|
|
79
|
+
polylineTo(points) {
|
|
80
|
+
if (this.currentPosition.every(isNaN))
|
|
81
|
+
this.moveTo(...points[0]);
|
|
82
|
+
const curve = new PolylineCurve([this.currentPosition].concat(points));
|
|
83
|
+
this.add(curve);
|
|
84
|
+
this._setCurrentPosition(...points[points.length - 1]);
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Draw an Arc curve from the current position, tangential to the 2 segments created by both control points
|
|
89
|
+
* Add an instance of {@link ArcCurve} to the path
|
|
90
|
+
*
|
|
91
|
+
* @param {number} x1 X-axis coordinate of the first control point
|
|
92
|
+
* @param {number} y1 Y-axis coordinate of the first control point
|
|
93
|
+
* @param {number} x2 X-axis coordinate of the second control point
|
|
94
|
+
* @param {number} y2 Y-axis coordinate of the second control point
|
|
95
|
+
* @param {number} radius Arc radius (Must be non-negative)
|
|
96
|
+
* @returns {this}
|
|
97
|
+
*/
|
|
98
|
+
arcTo(x1, y1, x2, y2, radius) {
|
|
99
|
+
if (this.currentPosition.every(isNaN))
|
|
100
|
+
this.moveTo(x1, y1);
|
|
101
|
+
if (radius < 0) {
|
|
102
|
+
console.warn(`Path.arcTo()`, `Arc radius must be non-negative.`, radius);
|
|
103
|
+
return this.lineTo(x2, y2);
|
|
104
|
+
}
|
|
105
|
+
const x0 = this.currentPosition[0];
|
|
106
|
+
const y0 = this.currentPosition[1];
|
|
107
|
+
// (x1, y1) is coincident with (x0, y0)
|
|
108
|
+
if (isCoincident(x0, y0, x1, y1)) {
|
|
109
|
+
return this;
|
|
110
|
+
}
|
|
111
|
+
// (x0, y0), (x1 ,y1) and (x2, y2) are collinear
|
|
112
|
+
if (isCollinear(x0, y0, x1, y1, x2, y2) || radius <= EPSILON) {
|
|
113
|
+
return this.lineTo(x2, y2);
|
|
114
|
+
}
|
|
115
|
+
const dx01 = x0 - x1;
|
|
116
|
+
const dy01 = y2 - y1;
|
|
117
|
+
const dx20 = x2 - x0;
|
|
118
|
+
const dy20 = y2 - y0;
|
|
119
|
+
const dx21 = x2 - x1;
|
|
120
|
+
const dy21 = y2 - y1;
|
|
121
|
+
const l20 = Math.hypot(dx20, dy20);
|
|
122
|
+
const l21 = Math.hypot(dx21, dy21);
|
|
123
|
+
const l01 = Math.hypot(dx01, dy01);
|
|
124
|
+
const l = radius * Math.tan((PI - Math.acos((l21 + l01 - l20) / (2 * l21 * l01))) / 2);
|
|
125
|
+
const t01 = l / l01;
|
|
126
|
+
const t21 = l / l21;
|
|
127
|
+
if (Math.abs(t01 - 1) > EPSILON) {
|
|
128
|
+
return this.lineTo(x1 + t01 * dx01, y1 + t01 * dy01);
|
|
129
|
+
}
|
|
130
|
+
const startX = x1 + t01 * dx01;
|
|
131
|
+
const startY = y1 + t01 * dy01;
|
|
132
|
+
const endX = x1 + t21 * dx21;
|
|
133
|
+
const endY = y1 + t21 * dy21;
|
|
134
|
+
const normalX = dy01 * -1;
|
|
135
|
+
const normalY = dx01;
|
|
136
|
+
const centerX = (startX + endX) / 2;
|
|
137
|
+
const centerY = (startY + endY) / 2;
|
|
138
|
+
const d = Math.sqrt(radius ** 2 - l ** 2);
|
|
139
|
+
const cx = centerX + (normalX / Math.hypot(normalX, normalY)) * d;
|
|
140
|
+
const cy = centerY + (normalY / Math.hypot(normalX, normalY)) * d;
|
|
141
|
+
const startAngle = Math.atan2(startY - cy, startX - cx);
|
|
142
|
+
const endAngle = Math.atan2(endY - cy, endX - cx);
|
|
143
|
+
const counterclockwise = dy01 * dx21 < dx01 * dy21;
|
|
144
|
+
this.arc(x1 + t21 * dx21, y1 + t21 * dy21, radius, startAngle, endAngle, counterclockwise);
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Draw a Quadratic Bézier curve from the current position to the end point specified by `x` and `y`, using the control point specified by `cpx` and `cpy`
|
|
149
|
+
* Add an instance of {@link QuadraticBezierCurve} to the path
|
|
150
|
+
*
|
|
151
|
+
* @param {number} cpx X-axis coordinate of the control point
|
|
152
|
+
* @param {number} cpy Y-axis coordinate of the control point
|
|
153
|
+
* @param {number} x2 X-axis coordinate of the end point
|
|
154
|
+
* @param {number} y2 Y-axis coordinate of the end point
|
|
155
|
+
* @return {this}
|
|
156
|
+
*/
|
|
157
|
+
quadraticCurveTo(cpx, cpy, x2, y2) {
|
|
158
|
+
if (this.currentPosition.every(isNaN))
|
|
159
|
+
this.moveTo(cpx, cpy);
|
|
160
|
+
const curve = new QuadraticBezierCurve(...this.currentPosition, cpx, cpy, x2, y2);
|
|
161
|
+
this.add(curve);
|
|
162
|
+
this._setCurrentPosition(x2, y2);
|
|
163
|
+
return this;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Draw a Cubic Bézier curve from the current position to the end point specified by `x` and `y`, using the control point specified by (`cp1x`, `cp1y`) and (`cp2x`, `cp2y`)
|
|
167
|
+
* Add an instance of {@link CubicBezierCurve} to the path
|
|
168
|
+
*
|
|
169
|
+
* @param {number} cp1x X-axis coordinate of the first control point
|
|
170
|
+
* @param {number} cp1y Y-axis coordinate of the first control point
|
|
171
|
+
* @param {number} cp2x X-axis coordinate of the second control point
|
|
172
|
+
* @param {number} cp2y Y-axis coordinate of the second control point
|
|
173
|
+
* @param {number} x2 X-axis coordinate of the end point
|
|
174
|
+
* @param {number} y2 Y-axis coordinate of the end point
|
|
175
|
+
* @return {this}
|
|
176
|
+
*/
|
|
177
|
+
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x2, y2) {
|
|
178
|
+
if (this.currentPosition.every(isNaN))
|
|
179
|
+
this.moveTo(cp1x, cp1y);
|
|
180
|
+
const curve = new CubicBezierCurve(...this.currentPosition, cp1x, cp1y, cp2x, cp2y, x2, y2);
|
|
181
|
+
this.add(curve);
|
|
182
|
+
this._setCurrentPosition(x2, y2);
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Draw a Catmul-Rom curve from the current position to the end point specified by `x` and `y`, using the control points specified by (`cp1x`, `cp1y`) and (`cp2x`, `cp2y`)
|
|
187
|
+
* Add an instance of {@link CatmullRomCurve} to the path
|
|
188
|
+
*
|
|
189
|
+
* @param {number} cp1x X-axis coordinate of the first control point
|
|
190
|
+
* @param {number} cp1y Y-axis coordinate of the first control point
|
|
191
|
+
* @param {number} cp2x X-axis coordinate of the second control point
|
|
192
|
+
* @param {number} cp2y Y-axis coordinate of the second control point
|
|
193
|
+
* @param {number} x2 X-axis coordinate of the end point
|
|
194
|
+
* @param {number} y2 Y-axis coordinate of the end point
|
|
195
|
+
* @return {this}
|
|
196
|
+
*/
|
|
197
|
+
catmulRomCurveTo(cp1x, cp1y, cp2x, cp2y, x2, y2) {
|
|
198
|
+
if (this.currentPosition.every(isNaN))
|
|
199
|
+
this.moveTo(cp1x, cp1y);
|
|
200
|
+
const curve = new CatmullRomCurve(...this.currentPosition, cp1x, cp1y, cp2x, cp2y, x2, y2);
|
|
201
|
+
this.add(curve);
|
|
202
|
+
this._setCurrentPosition(x2, y2);
|
|
203
|
+
return this;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Draw a Spline curve from the current position through the given points
|
|
207
|
+
* Add an instance of {@link SplineCurve} to the path
|
|
208
|
+
*
|
|
209
|
+
* @param {Point[]} points Array of points defining the curve
|
|
210
|
+
* @return {this}
|
|
211
|
+
*/
|
|
212
|
+
splineTo(points) {
|
|
213
|
+
if (this.currentPosition.every(isNaN))
|
|
214
|
+
this.moveTo(...points[0]);
|
|
215
|
+
const curve = new SplineCurve([this.currentPosition].concat(points));
|
|
216
|
+
this.add(curve);
|
|
217
|
+
this._setCurrentPosition(...points[points.length - 1]);
|
|
218
|
+
return this;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Draw an Ellispe curve which is centered at (`cx`, `cy`) position
|
|
222
|
+
* Add an instance of {@link EllipseCurve} to the path
|
|
223
|
+
*
|
|
224
|
+
* @param {number} cx X-axis coordinate of the center of the circle
|
|
225
|
+
* @param {number} cy Y-axis coordinate of the center of the circle
|
|
226
|
+
* @param {number} rx X-radius of the ellipse
|
|
227
|
+
* @param {number} ry Y-radius of the ellipse
|
|
228
|
+
* @param {number} [rotation] Rotation angle of the ellipse (in radians), counterclockwise from the positive X-axis
|
|
229
|
+
* @param {number} [startAngle] Start angle of the arc (in radians)
|
|
230
|
+
* @param {number} [endAngle] End angle of the arc (in radians)
|
|
231
|
+
* @param {boolean} [counterclockwise] Flag indicating the direction of the arc
|
|
232
|
+
* @return {this}
|
|
233
|
+
*/
|
|
234
|
+
ellipse(cx, cy, rx, ry, rotation, startAngle, endAngle, counterclockwise) {
|
|
235
|
+
if (this.currentPosition.every(isNaN))
|
|
236
|
+
this.moveTo(cx, cy);
|
|
237
|
+
else if (!isCoincident(...this.currentPosition, cx, cy))
|
|
238
|
+
this.lineTo(cx, cy);
|
|
239
|
+
const curve = new EllipseCurve(cx, cy, rx, ry, rotation, startAngle, endAngle, counterclockwise);
|
|
240
|
+
this.add(curve);
|
|
241
|
+
this._setCurrentPosition(...curve.getPoint(1));
|
|
242
|
+
return this;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Draw an Arc curve which is centered at (`cx`, `cy`) position
|
|
246
|
+
* Add an instance of {@link ArcCurve} to the path
|
|
247
|
+
*
|
|
248
|
+
* @param {number} cx X-axis coordinate of the center of the circle
|
|
249
|
+
* @param {number} cy Y-axis coordinate of the center of the circle
|
|
250
|
+
* @param {number} radius Radius of the circle
|
|
251
|
+
* @param {number} [startAngle] Start angle of the arc (in radians)
|
|
252
|
+
* @param {number} [endAngle] End angle of the arc (in radians)
|
|
253
|
+
* @param {boolean} [counterclockwise] Flag indicating the direction of the arc
|
|
254
|
+
* @return {this}
|
|
255
|
+
*/
|
|
256
|
+
arc(cx, cy, radius, startAngle, endAngle, counterclockwise) {
|
|
257
|
+
if (this.currentPosition.every(isNaN))
|
|
258
|
+
this.moveTo(cx, cy);
|
|
259
|
+
else if (!isCoincident(...this.currentPosition, cx, cy))
|
|
260
|
+
this.lineTo(cx, cy);
|
|
261
|
+
const curve = new ArcCurve(cx, cy, radius, startAngle, endAngle, counterclockwise);
|
|
262
|
+
this.add(curve);
|
|
263
|
+
this._setCurrentPosition(...curve.getPoint(1));
|
|
264
|
+
return this;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Draw a rectangular path from the start position specified by `x` and `y` to the end position using `width` and `height`
|
|
268
|
+
* Add an instance of {@link PolylineCurve} to the path
|
|
269
|
+
*
|
|
270
|
+
* @param {number} x X-axis coordinate of the rectangle starting point
|
|
271
|
+
* @param {number} y Y-axis coordinate of the rectangle starting point
|
|
272
|
+
* @param {number} width Rectangle width (Positive values are to the right and negative to the left)
|
|
273
|
+
* @param {number} height Rectangle height (Positive values are down, and negative are up)
|
|
274
|
+
* @return {this}
|
|
275
|
+
*/
|
|
276
|
+
rect(x, y, width, height) {
|
|
277
|
+
if (this.currentPosition.every(isNaN))
|
|
278
|
+
this.moveTo(x, y);
|
|
279
|
+
else if (!isCoincident(...this.currentPosition, x, y))
|
|
280
|
+
this.lineTo(x, y);
|
|
281
|
+
const curve = new PolylineCurve([
|
|
282
|
+
[x + width, y],
|
|
283
|
+
[x + width, y + height],
|
|
284
|
+
[x, y + height],
|
|
285
|
+
[x, y]
|
|
286
|
+
]);
|
|
287
|
+
this.add(curve);
|
|
288
|
+
this._setCurrentPosition(x, y);
|
|
289
|
+
return this;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Draw a rounded rectangular path from the start position specified by `x` and `y` to the end position using `width` and `height`
|
|
293
|
+
*
|
|
294
|
+
* @param {number} x X-axis coordinate of the rectangle starting point
|
|
295
|
+
* @param {number} y Y-axis coordinate of the rectangle starting point
|
|
296
|
+
* @param {number} width Rectangle width (Positive values are to the right and negative to the left)
|
|
297
|
+
* @param {number} height Rectangle height (Positive values are down, and negative are up)
|
|
298
|
+
* @param {number|number[]} radius Radius of the circular arc to be used for the corners of the rectangle
|
|
299
|
+
* @return {this}
|
|
300
|
+
*/
|
|
301
|
+
roundRect(x, y, width, height, radius) {
|
|
302
|
+
let topLeftRadius = typeof radius === 'number' ? radius : radius[0];
|
|
303
|
+
let topRightRadius = typeof radius === 'number' ? radius : radius[1] ?? radius[0];
|
|
304
|
+
let bottomRightRadius = typeof radius === 'number' ? radius : radius[2] ?? topLeftRadius;
|
|
305
|
+
let bottomLeftRadius = typeof radius === 'number' ? radius : radius[3] ?? topRightRadius;
|
|
306
|
+
const maxRadius = Math.min(width / 2, height / 2);
|
|
307
|
+
topLeftRadius = Math.min(topLeftRadius, maxRadius);
|
|
308
|
+
topRightRadius = Math.min(topRightRadius, maxRadius);
|
|
309
|
+
bottomRightRadius = Math.min(bottomRightRadius, maxRadius);
|
|
310
|
+
bottomLeftRadius = Math.min(bottomLeftRadius, maxRadius);
|
|
311
|
+
if (this.currentPosition.every(isNaN))
|
|
312
|
+
this.moveTo(x + topLeftRadius, y);
|
|
313
|
+
else if (!isCoincident(...this.currentPosition, x, y))
|
|
314
|
+
this.lineTo(x + topLeftRadius, y);
|
|
315
|
+
// Top-Right corner
|
|
316
|
+
if (topRightRadius > 0) {
|
|
317
|
+
this.lineTo(x + width - topRightRadius, y);
|
|
318
|
+
this.arcTo(x + width, y, x + width, y + topRightRadius, topRightRadius);
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
this.lineTo(x + width, y);
|
|
322
|
+
}
|
|
323
|
+
// Bottom-Right corner
|
|
324
|
+
if (bottomRightRadius > 0) {
|
|
325
|
+
this.lineTo(x + width, y + height - bottomRightRadius);
|
|
326
|
+
this.arcTo(x + width, y + height, x + width - bottomRightRadius, y + height, bottomRightRadius);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
this.lineTo(x + width, y + height);
|
|
330
|
+
}
|
|
331
|
+
// Bottom-Left corner
|
|
332
|
+
if (bottomLeftRadius > 0) {
|
|
333
|
+
this.lineTo(x + bottomLeftRadius, y + height);
|
|
334
|
+
this.arcTo(x, y + height, x, y + height - bottomLeftRadius, bottomLeftRadius);
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
this.lineTo(x, y + height);
|
|
338
|
+
}
|
|
339
|
+
// Top-Left corner
|
|
340
|
+
if (topLeftRadius > 0) {
|
|
341
|
+
this.lineTo(x, y + topLeftRadius);
|
|
342
|
+
this.arcTo(x, y, x + topLeftRadius, y, topLeftRadius);
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
this.lineTo(x, y);
|
|
346
|
+
}
|
|
347
|
+
return this;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Add a line curve to close the curve path
|
|
351
|
+
* Add an instance of {@link LineCurve} to the path
|
|
352
|
+
*/
|
|
353
|
+
closePath() {
|
|
354
|
+
super.closePath();
|
|
355
|
+
const endPoint = this.curves[this.curves.length - 1]?.getPoint(1);
|
|
356
|
+
this._setCurrentPosition(...endPoint);
|
|
357
|
+
return this;
|
|
358
|
+
}
|
|
359
|
+
// public transform() {}
|
|
360
|
+
// public translate(x: number, y: number) {}
|
|
361
|
+
// public rotate(angle: number) {}
|
|
362
|
+
// public restore() {}
|
|
363
|
+
_setCurrentPosition(x, y) {
|
|
364
|
+
this.currentPosition[0] = x;
|
|
365
|
+
this.currentPosition[1] = y;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Point } from 'src/types';
|
|
2
|
+
import Curve from './Curve';
|
|
3
|
+
/**
|
|
4
|
+
* Utility class for manipulating connected curves
|
|
5
|
+
*
|
|
6
|
+
* @exports
|
|
7
|
+
* @class PathCurve
|
|
8
|
+
* @extends Curve
|
|
9
|
+
*/
|
|
10
|
+
export default class PathCurve extends Curve {
|
|
11
|
+
readonly type: string;
|
|
12
|
+
/**
|
|
13
|
+
* Array of curves composing the path
|
|
14
|
+
*/
|
|
15
|
+
curves: Curve[];
|
|
16
|
+
/**
|
|
17
|
+
* Array of points composing the path
|
|
18
|
+
*/
|
|
19
|
+
points: Point[];
|
|
20
|
+
autoClose: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Add a curve to this curve path
|
|
23
|
+
*
|
|
24
|
+
* @param {Curve} curve Curve to add
|
|
25
|
+
*/
|
|
26
|
+
add(curve: Curve): void;
|
|
27
|
+
/**
|
|
28
|
+
* Interpolate a point on the curve path
|
|
29
|
+
*
|
|
30
|
+
* @param {number} t Normalized time value to interpolate
|
|
31
|
+
* @returns {Point|null} Interpolated coordinates on the curve
|
|
32
|
+
*/
|
|
33
|
+
getPoint(t: number): Point;
|
|
34
|
+
/**
|
|
35
|
+
* Compute the curve shape into an array of points
|
|
36
|
+
*
|
|
37
|
+
* @param {number} [divisions=40] Number of divisions
|
|
38
|
+
* @returns {Point[]}
|
|
39
|
+
*/
|
|
40
|
+
getPoints(divisions?: number): Point[];
|
|
41
|
+
/**
|
|
42
|
+
* Compute the curve shape into an array of equi-spaced points across the entire curve path
|
|
43
|
+
*
|
|
44
|
+
* @param {number} [divisions=40] Number of divisions
|
|
45
|
+
* @returns {Point[]}
|
|
46
|
+
*/
|
|
47
|
+
getSpacedPoints(divisions?: number): Point[];
|
|
48
|
+
/**
|
|
49
|
+
* Compute the total arc length of the curve path
|
|
50
|
+
*
|
|
51
|
+
* @returns {number}
|
|
52
|
+
*/
|
|
53
|
+
getLength(): number;
|
|
54
|
+
/**
|
|
55
|
+
* Compute the cumulative curve lengths of the curve path
|
|
56
|
+
*
|
|
57
|
+
* @returns {number[]}
|
|
58
|
+
*/
|
|
59
|
+
getCurveLengths(): number[];
|
|
60
|
+
/**
|
|
61
|
+
* Update the cached cumulative segment lengths
|
|
62
|
+
*/
|
|
63
|
+
updateArcLengths(): void;
|
|
64
|
+
/**
|
|
65
|
+
* Add a line curve to close the curve path
|
|
66
|
+
* Add an instance of {@link LineCurve} to the path
|
|
67
|
+
*/
|
|
68
|
+
closePath(): void;
|
|
69
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { isCoincident } from 'src/geometry';
|
|
2
|
+
import Curve from './Curve';
|
|
3
|
+
import LineCurve from './LineCurve';
|
|
4
|
+
/**
|
|
5
|
+
* Utility class for manipulating connected curves
|
|
6
|
+
*
|
|
7
|
+
* @exports
|
|
8
|
+
* @class PathCurve
|
|
9
|
+
* @extends Curve
|
|
10
|
+
*/
|
|
11
|
+
export default class PathCurve extends Curve {
|
|
12
|
+
type = 'PathCurve';
|
|
13
|
+
/**
|
|
14
|
+
* Array of curves composing the path
|
|
15
|
+
*/
|
|
16
|
+
curves = [];
|
|
17
|
+
/**
|
|
18
|
+
* Array of points composing the path
|
|
19
|
+
*/
|
|
20
|
+
points = [];
|
|
21
|
+
autoClose = false;
|
|
22
|
+
/**
|
|
23
|
+
* Add a curve to this curve path
|
|
24
|
+
*
|
|
25
|
+
* @param {Curve} curve Curve to add
|
|
26
|
+
*/
|
|
27
|
+
add(curve) {
|
|
28
|
+
this.curves.push(curve);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Interpolate a point on the curve path
|
|
32
|
+
*
|
|
33
|
+
* @param {number} t Normalized time value to interpolate
|
|
34
|
+
* @returns {Point|null} Interpolated coordinates on the curve
|
|
35
|
+
*/
|
|
36
|
+
getPoint(t) {
|
|
37
|
+
const d = t * this.getLength();
|
|
38
|
+
const curveLengths = this.getCurveLengths();
|
|
39
|
+
let i = 0;
|
|
40
|
+
while (i < curveLengths.length) {
|
|
41
|
+
if (curveLengths[i] >= d) {
|
|
42
|
+
const delta = curveLengths[i] - d;
|
|
43
|
+
const curve = this.curves[i];
|
|
44
|
+
const segmentLength = curve.getLength();
|
|
45
|
+
const u = segmentLength === 0 ? 0 : 1 - delta / segmentLength;
|
|
46
|
+
return curve.getPointAt(u);
|
|
47
|
+
}
|
|
48
|
+
i++;
|
|
49
|
+
}
|
|
50
|
+
console.warn(`PathCurve.getPoint()`, `No point found in curve.`, this);
|
|
51
|
+
return [0, 0];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Compute the curve shape into an array of points
|
|
55
|
+
*
|
|
56
|
+
* @param {number} [divisions=40] Number of divisions
|
|
57
|
+
* @returns {Point[]}
|
|
58
|
+
*/
|
|
59
|
+
getPoints(divisions = 40) {
|
|
60
|
+
const curves = this.curves;
|
|
61
|
+
const points = [];
|
|
62
|
+
let lastPoint = null;
|
|
63
|
+
for (let i = 0; i < curves.length; i++) {
|
|
64
|
+
const curve = curves[i];
|
|
65
|
+
const resolution = curve.type === 'EllipseCurve'
|
|
66
|
+
? divisions * 2
|
|
67
|
+
: curve.type === 'LineCurve'
|
|
68
|
+
? 1
|
|
69
|
+
: curve.type === 'SplineCurve'
|
|
70
|
+
? divisions * curve.points.length
|
|
71
|
+
: divisions;
|
|
72
|
+
const points = curve.getPoints(resolution);
|
|
73
|
+
for (let j = 0; j < points.length; j++) {
|
|
74
|
+
const point = points[j];
|
|
75
|
+
if (lastPoint && isCoincident(...lastPoint, ...point))
|
|
76
|
+
continue;
|
|
77
|
+
points.push(point);
|
|
78
|
+
lastPoint = point;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (this.autoClose && !Curve.isClosed(points)) {
|
|
82
|
+
points.push(points[0]);
|
|
83
|
+
}
|
|
84
|
+
return points;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Compute the curve shape into an array of equi-spaced points across the entire curve path
|
|
88
|
+
*
|
|
89
|
+
* @param {number} [divisions=40] Number of divisions
|
|
90
|
+
* @returns {Point[]}
|
|
91
|
+
*/
|
|
92
|
+
getSpacedPoints(divisions = 40) {
|
|
93
|
+
const points = [];
|
|
94
|
+
for (let i = 0; i <= divisions; i++) {
|
|
95
|
+
points.push(this.getPoint(i / divisions));
|
|
96
|
+
}
|
|
97
|
+
if (this.autoClose && !Curve.isClosed(points)) {
|
|
98
|
+
points.push(points[0]);
|
|
99
|
+
}
|
|
100
|
+
return points;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Compute the total arc length of the curve path
|
|
104
|
+
*
|
|
105
|
+
* @returns {number}
|
|
106
|
+
*/
|
|
107
|
+
getLength() {
|
|
108
|
+
const lengths = this.getCurveLengths();
|
|
109
|
+
return lengths[lengths.length - 1];
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Compute the cumulative curve lengths of the curve path
|
|
113
|
+
*
|
|
114
|
+
* @returns {number[]}
|
|
115
|
+
*/
|
|
116
|
+
getCurveLengths() {
|
|
117
|
+
if (this._cacheArcLengths.length === this.curves.length) {
|
|
118
|
+
return this._cacheArcLengths;
|
|
119
|
+
}
|
|
120
|
+
const lengths = [];
|
|
121
|
+
let sums = 0;
|
|
122
|
+
for (let i = 0, l = this.curves.length; i < l; i++) {
|
|
123
|
+
sums += this.curves[i].getLength();
|
|
124
|
+
lengths.push(sums);
|
|
125
|
+
}
|
|
126
|
+
this._cacheArcLengths = lengths;
|
|
127
|
+
return lengths;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Update the cached cumulative segment lengths
|
|
131
|
+
*/
|
|
132
|
+
updateArcLengths() {
|
|
133
|
+
this.needsUpdate = true;
|
|
134
|
+
this._cacheArcLengths = [];
|
|
135
|
+
this.getCurveLengths();
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Add a line curve to close the curve path
|
|
139
|
+
* Add an instance of {@link LineCurve} to the path
|
|
140
|
+
*/
|
|
141
|
+
closePath() {
|
|
142
|
+
const startPoint = this.curves[0]?.getPoint(0);
|
|
143
|
+
const endPoint = this.curves[this.curves.length - 1]?.getPoint(1);
|
|
144
|
+
if (!isCoincident(...startPoint, ...endPoint)) {
|
|
145
|
+
this.add(new LineCurve(...endPoint, ...startPoint));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Point } from 'src/types';
|
|
2
|
+
import Path from './Path';
|
|
3
|
+
/**
|
|
4
|
+
* Utility class for manipulating connected curves and generating SVG path
|
|
5
|
+
*
|
|
6
|
+
* It works by serializing 2D Canvas API to SVG path data
|
|
7
|
+
*
|
|
8
|
+
* @exports
|
|
9
|
+
* @class PathSVG
|
|
10
|
+
* @extends Path
|
|
11
|
+
*/
|
|
12
|
+
export default class PathSVG extends Path {
|
|
13
|
+
/**
|
|
14
|
+
* Resolution used for Catmul-Rom curve and Spline curve approximations
|
|
15
|
+
*/
|
|
16
|
+
readonly curveResolution: number;
|
|
17
|
+
constructor({ curveResolution }: {
|
|
18
|
+
curveResolution?: number;
|
|
19
|
+
});
|
|
20
|
+
private _commands;
|
|
21
|
+
moveTo(x: number, y: number): this;
|
|
22
|
+
lineTo(x: number, y: number): this;
|
|
23
|
+
polylineTo(points: Point[]): this;
|
|
24
|
+
quadraticCurveTo(cpx: number, cpy: number, x2: number, y2: number): this;
|
|
25
|
+
bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x2: number, y2: number): this;
|
|
26
|
+
catmulRomCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x2: number, y2: number): this;
|
|
27
|
+
splineTo(points: Point[]): this;
|
|
28
|
+
ellipse(cx: number, cy: number, rx: number, ry: number, rotation?: number, startAngle?: number, endAngle?: number, counterclockwise?: boolean): this;
|
|
29
|
+
arc(cx: number, cy: number, radius: number, startAngle?: number, endAngle?: number, counterclockwise?: boolean): this;
|
|
30
|
+
rect(x: number, y: number, width: number, height: number): this;
|
|
31
|
+
closePath(): this;
|
|
32
|
+
private _writeLineTo;
|
|
33
|
+
private _writeArc;
|
|
34
|
+
private _write;
|
|
35
|
+
/**
|
|
36
|
+
* Return SVG path data string
|
|
37
|
+
*
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
toString(): string;
|
|
41
|
+
}
|