@js-draw/math 1.21.3 → 1.22.0
Sign up to get free protection for your applications and to get access to all the features.
- package/build-config.json +1 -1
- package/dist/cjs/Color4.js +2 -2
- package/dist/cjs/Mat33.d.ts +1 -11
- package/dist/cjs/Mat33.js +8 -24
- package/dist/cjs/Vec3.js +9 -7
- package/dist/cjs/shapes/BezierJSWrapper.js +20 -13
- package/dist/cjs/shapes/LineSegment2.js +13 -17
- package/dist/cjs/shapes/Parameterized2DShape.js +1 -1
- package/dist/cjs/shapes/Path.js +49 -47
- package/dist/cjs/shapes/Rect2.js +13 -15
- package/dist/cjs/shapes/Triangle.js +4 -5
- package/dist/cjs/utils/convexHull2Of.js +3 -3
- package/dist/mjs/Color4.mjs +2 -2
- package/dist/mjs/Mat33.d.ts +1 -11
- package/dist/mjs/Mat33.mjs +8 -24
- package/dist/mjs/Vec3.mjs +9 -7
- package/dist/mjs/shapes/BezierJSWrapper.mjs +20 -13
- package/dist/mjs/shapes/LineSegment2.mjs +13 -17
- package/dist/mjs/shapes/Parameterized2DShape.mjs +1 -1
- package/dist/mjs/shapes/Path.mjs +49 -47
- package/dist/mjs/shapes/Rect2.mjs +13 -15
- package/dist/mjs/shapes/Triangle.mjs +4 -5
- package/dist/mjs/utils/convexHull2Of.mjs +3 -3
- package/dist-test/test_imports/test-require.cjs +1 -1
- package/package.json +3 -3
- package/src/Color4.test.ts +16 -21
- package/src/Color4.ts +22 -17
- package/src/Mat33.fromCSSMatrix.test.ts +31 -45
- package/src/Mat33.test.ts +58 -96
- package/src/Mat33.ts +61 -104
- package/src/Vec2.test.ts +3 -3
- package/src/Vec3.test.ts +2 -3
- package/src/Vec3.ts +34 -58
- package/src/lib.ts +0 -2
- package/src/polynomial/solveQuadratic.test.ts +39 -13
- package/src/polynomial/solveQuadratic.ts +5 -6
- package/src/rounding/cleanUpNumber.test.ts +1 -1
- package/src/rounding/constants.ts +1 -3
- package/src/rounding/getLenAfterDecimal.ts +1 -2
- package/src/rounding/lib.ts +1 -2
- package/src/rounding/toRoundedString.test.ts +1 -1
- package/src/rounding/toStringOfSamePrecision.test.ts +1 -2
- package/src/rounding/toStringOfSamePrecision.ts +1 -1
- package/src/shapes/BezierJSWrapper.ts +54 -37
- package/src/shapes/CubicBezier.ts +3 -3
- package/src/shapes/LineSegment2.test.ts +24 -17
- package/src/shapes/LineSegment2.ts +26 -29
- package/src/shapes/Parameterized2DShape.ts +5 -4
- package/src/shapes/Path.fromString.test.ts +5 -5
- package/src/shapes/Path.test.ts +122 -120
- package/src/shapes/Path.toString.test.ts +7 -7
- package/src/shapes/Path.ts +378 -352
- package/src/shapes/PointShape2D.ts +3 -3
- package/src/shapes/QuadraticBezier.test.ts +27 -21
- package/src/shapes/QuadraticBezier.ts +4 -9
- package/src/shapes/Rect2.test.ts +44 -75
- package/src/shapes/Rect2.ts +30 -35
- package/src/shapes/Triangle.test.ts +31 -29
- package/src/shapes/Triangle.ts +17 -18
- package/src/utils/convexHull2Of.test.ts +54 -15
- package/src/utils/convexHull2Of.ts +9 -7
- package/tsconfig.json +1 -3
- package/typedoc.json +2 -2
package/src/shapes/Path.test.ts
CHANGED
@@ -35,10 +35,15 @@ describe('Path', () => {
|
|
35
35
|
|
36
36
|
// Make sure the control points (and start/end points) match what was set
|
37
37
|
expect(firstItem.getPoints()).toMatchObject([
|
38
|
-
{ x: 0, y: 0 },
|
38
|
+
{ x: 0, y: 0 },
|
39
|
+
{ x: 1, y: 1 },
|
40
|
+
{ x: -1, y: -1 },
|
41
|
+
{ x: 3, y: 3 },
|
39
42
|
]);
|
40
43
|
expect(secondItem.getPoints()).toMatchObject([
|
41
|
-
{ x: 3, y: 3 },
|
44
|
+
{ x: 3, y: 3 },
|
45
|
+
{ x: 1, y: 1 },
|
46
|
+
{ x: 0, y: 0 },
|
42
47
|
]);
|
43
48
|
});
|
44
49
|
|
@@ -55,23 +60,21 @@ describe('Path', () => {
|
|
55
60
|
|
56
61
|
expect(path.geometry.length).toBe(1);
|
57
62
|
expect(path.geometry[0]).toBeInstanceOf(LineSegment2);
|
58
|
-
expect(path.geometry[0]).toMatchObject(
|
59
|
-
new LineSegment2(lineStart, lineEnd)
|
60
|
-
);
|
63
|
+
expect(path.geometry[0]).toMatchObject(new LineSegment2(lineStart, lineEnd));
|
61
64
|
});
|
62
65
|
|
63
66
|
it.each([
|
64
|
-
[
|
65
|
-
[
|
66
|
-
[
|
67
|
-
[
|
68
|
-
[
|
69
|
-
[
|
70
|
-
[
|
71
|
-
[
|
72
|
-
[
|
73
|
-
[
|
74
|
-
[
|
67
|
+
['m0,0 L1,1', 'M0,0 L1,1', true],
|
68
|
+
['m0,0 L1,1', 'M1,1 L0,0', false],
|
69
|
+
['m0,0 L1,1 Q2,3 4,5', 'M1,1 L0,0', false],
|
70
|
+
['m0,0 L1,1 Q2,3 4,5', 'M1,1 L0,0 Q2,3 4,5', false],
|
71
|
+
['m0,0 L1,1 Q2,3 4,5', 'M0,0 L1,1 Q2,3 4,5', true],
|
72
|
+
['m0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9', 'M0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9', true],
|
73
|
+
['m0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9Z', 'M0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9', false],
|
74
|
+
['m0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9', 'M0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9Z', false],
|
75
|
+
['m0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9', 'M0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9.01', false],
|
76
|
+
['m0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9', 'M0,0 L1,1 Q2,3 4,5 C4,5 6,7.01 8,9', false],
|
77
|
+
['m0,0 L1,1 Q2,3 4,5 C4,5 6,7 8,9', 'M0,0 L1,1 Q2,3 4,5 C4,5.01 6,7 8,9', false],
|
75
78
|
])('.eq should check equality', (path1Str, path2Str, shouldEqual) => {
|
76
79
|
expect(Path.fromString(path1Str)).objEq(Path.fromString(path1Str));
|
77
80
|
expect(Path.fromString(path2Str)).objEq(Path.fromString(path2Str));
|
@@ -97,7 +100,7 @@ describe('Path', () => {
|
|
97
100
|
]);
|
98
101
|
|
99
102
|
const intersections = path.intersection(
|
100
|
-
new LineSegment2(Vec2.of(-50, 200), Vec2.of(-50, -200))
|
103
|
+
new LineSegment2(Vec2.of(-50, 200), Vec2.of(-50, -200)),
|
101
104
|
);
|
102
105
|
|
103
106
|
// Should only have intersections in quadrants II and III.
|
@@ -143,7 +146,8 @@ describe('Path', () => {
|
|
143
146
|
|
144
147
|
const strokeWidth = 5;
|
145
148
|
let intersections = path.intersection(
|
146
|
-
new LineSegment2(Vec2.of(2000, 200), Vec2.of(2000, 400)),
|
149
|
+
new LineSegment2(Vec2.of(2000, 200), Vec2.of(2000, 400)),
|
150
|
+
strokeWidth,
|
147
151
|
);
|
148
152
|
expect(intersections.length).toBe(0);
|
149
153
|
|
@@ -158,7 +162,8 @@ describe('Path', () => {
|
|
158
162
|
// ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
|
159
163
|
|
160
164
|
intersections = path.intersection(
|
161
|
-
new LineSegment2(Vec2.of(-50, 200), Vec2.of(-50, 100)),
|
165
|
+
new LineSegment2(Vec2.of(-50, 200), Vec2.of(-50, 100)),
|
166
|
+
strokeWidth,
|
162
167
|
);
|
163
168
|
expect(intersections.length).toBe(1);
|
164
169
|
expect(intersections[0].point.xy).toMatchObject({
|
@@ -166,10 +171,10 @@ describe('Path', () => {
|
|
166
171
|
y: 105,
|
167
172
|
});
|
168
173
|
|
169
|
-
|
170
174
|
// Changing the order of the end points on the line should not change the result
|
171
175
|
intersections = path.intersection(
|
172
|
-
new LineSegment2(Vec2.of(-50, 100), Vec2.of(-50, 200)),
|
176
|
+
new LineSegment2(Vec2.of(-50, 100), Vec2.of(-50, 200)),
|
177
|
+
strokeWidth,
|
173
178
|
);
|
174
179
|
expect(intersections.length).toBe(1);
|
175
180
|
expect(intersections[0].point.xy).toMatchObject({
|
@@ -181,7 +186,8 @@ describe('Path', () => {
|
|
181
186
|
// intersections — one entering and one leaving for each intersection with the
|
182
187
|
// centers.
|
183
188
|
intersections = path.intersection(
|
184
|
-
new LineSegment2(Vec2.of(-50, 200), Vec2.of(-50, -200)),
|
189
|
+
new LineSegment2(Vec2.of(-50, 200), Vec2.of(-50, -200)),
|
190
|
+
strokeWidth,
|
185
191
|
);
|
186
192
|
expect(intersections.length).toBe(4);
|
187
193
|
|
@@ -204,7 +210,7 @@ describe('Path', () => {
|
|
204
210
|
kind: PathCommandType.QuadraticBezierTo,
|
205
211
|
controlPoint: Vec2.unitX,
|
206
212
|
endPoint: Vec2.unitY,
|
207
|
-
}
|
213
|
+
},
|
208
214
|
]);
|
209
215
|
|
210
216
|
const strokeWidth = 5;
|
@@ -212,13 +218,15 @@ describe('Path', () => {
|
|
212
218
|
// Should be no intersections for a line contained entirely within the stroke
|
213
219
|
// (including stroke width).
|
214
220
|
let intersections = path.intersection(
|
215
|
-
new LineSegment2(Vec2.of(-1, 0.5), Vec2.of(2, 0.5)),
|
221
|
+
new LineSegment2(Vec2.of(-1, 0.5), Vec2.of(2, 0.5)),
|
222
|
+
strokeWidth,
|
216
223
|
);
|
217
224
|
expect(intersections).toHaveLength(0);
|
218
225
|
|
219
226
|
// Should be an intersection when exiting/entering the edge of the stroke
|
220
227
|
intersections = path.intersection(
|
221
|
-
new LineSegment2(Vec2.of(0, 0.5), Vec2.of(8, 0.5)),
|
228
|
+
new LineSegment2(Vec2.of(0, 0.5), Vec2.of(8, 0.5)),
|
229
|
+
strokeWidth,
|
222
230
|
);
|
223
231
|
expect(intersections).toHaveLength(1);
|
224
232
|
});
|
@@ -226,10 +234,7 @@ describe('Path', () => {
|
|
226
234
|
it('should correctly report intersections near the cap of a line-like Bézier', () => {
|
227
235
|
const path = Path.fromString('M0,0Q14,0 27,0');
|
228
236
|
expect(
|
229
|
-
path.intersection(
|
230
|
-
new LineSegment2(Vec2.of(0, -100), Vec2.of(0, 100)),
|
231
|
-
10,
|
232
|
-
),
|
237
|
+
path.intersection(new LineSegment2(Vec2.of(0, -100), Vec2.of(0, 100)), 10),
|
233
238
|
|
234
239
|
// Should have intersections, despite being at the cap of the Bézier
|
235
240
|
// curve.
|
@@ -237,22 +242,31 @@ describe('Path', () => {
|
|
237
242
|
});
|
238
243
|
|
239
244
|
it.each([
|
240
|
-
[new LineSegment2(Vec2.of(43.5
|
241
|
-
[new LineSegment2(Vec2.of(35.5,19.5), Vec2.of(38.5
|
242
|
-
])(
|
243
|
-
|
244
|
-
|
245
|
-
|
245
|
+
[new LineSegment2(Vec2.of(43.5, -12.5), Vec2.of(40.5, 24.5)), 0],
|
246
|
+
[new LineSegment2(Vec2.of(35.5, 19.5), Vec2.of(38.5, -17.5)), 0],
|
247
|
+
])(
|
248
|
+
'should correctly report positive intersections with a line-like Bézier',
|
249
|
+
(line, strokeRadius) => {
|
250
|
+
const bezier = Path.fromString('M0,0 Q50,0 100,0');
|
251
|
+
expect(bezier.intersection(line, strokeRadius).length).toBeGreaterThan(0);
|
252
|
+
},
|
253
|
+
);
|
246
254
|
|
247
255
|
it('should handle near-vertical lines', () => {
|
248
|
-
const intersections = Path.fromString('M0,0 Q50,0 100,0').intersection(
|
256
|
+
const intersections = Path.fromString('M0,0 Q50,0 100,0').intersection(
|
257
|
+
new LineSegment2(Vec2.of(44, -12), Vec2.of(39, 25)),
|
258
|
+
);
|
249
259
|
expect(intersections).toHaveLength(1);
|
250
260
|
});
|
251
261
|
|
252
262
|
it('should handle single-point strokes', () => {
|
253
263
|
const stroke = new Path(Vec2.zero, []);
|
254
|
-
expect(
|
255
|
-
|
264
|
+
expect(
|
265
|
+
stroke.intersection(new LineSegment2(Vec2.of(-2, -20), Vec2.of(-2, -1)), 1),
|
266
|
+
).toHaveLength(0);
|
267
|
+
expect(stroke.intersection(new LineSegment2(Vec2.of(-2, -2), Vec2.of(2, 2)), 1)).toHaveLength(
|
268
|
+
2,
|
269
|
+
);
|
256
270
|
});
|
257
271
|
});
|
258
272
|
|
@@ -272,18 +286,12 @@ describe('Path', () => {
|
|
272
286
|
describe('roughlyIntersectsClosed', () => {
|
273
287
|
it('small, line-only path', () => {
|
274
288
|
const path = Path.fromString('m0,0 l10,10 L0,10 z');
|
275
|
-
expect(
|
276
|
-
|
277
|
-
)
|
278
|
-
expect(
|
279
|
-
|
280
|
-
).toBe(true);
|
281
|
-
expect(
|
282
|
-
path.closedRoughlyIntersects(new Rect2(10, 1, 1, 1))
|
283
|
-
).toBe(false);
|
284
|
-
expect(
|
285
|
-
path.closedRoughlyIntersects(new Rect2(1, 5, 1, 1))
|
286
|
-
).toBe(true);
|
289
|
+
expect(path.closedRoughlyIntersects(Rect2.fromCorners(Vec2.zero, Vec2.of(20, 20)))).toBe(
|
290
|
+
true,
|
291
|
+
);
|
292
|
+
expect(path.closedRoughlyIntersects(Rect2.fromCorners(Vec2.zero, Vec2.of(2, 2)))).toBe(true);
|
293
|
+
expect(path.closedRoughlyIntersects(new Rect2(10, 1, 1, 1))).toBe(false);
|
294
|
+
expect(path.closedRoughlyIntersects(new Rect2(1, 5, 1, 1))).toBe(true);
|
287
295
|
});
|
288
296
|
|
289
297
|
it('path with Bézier curves', () => {
|
@@ -296,15 +304,9 @@ describe('Path', () => {
|
|
296
304
|
Q670,470 960,980
|
297
305
|
Q1230,1370 1090,2560
|
298
306
|
`);
|
299
|
-
expect(
|
300
|
-
|
301
|
-
).toBe(
|
302
|
-
expect(
|
303
|
-
path.closedRoughlyIntersects(new Rect2(0, 0, 5, 5))
|
304
|
-
).toBe(true);
|
305
|
-
expect(
|
306
|
-
path.closedRoughlyIntersects(new Rect2(-10000, 0, 500, 500))
|
307
|
-
).toBe(false);
|
307
|
+
expect(path.closedRoughlyIntersects(new Rect2(0, 0, 500, 500))).toBe(true);
|
308
|
+
expect(path.closedRoughlyIntersects(new Rect2(0, 0, 5, 5))).toBe(true);
|
309
|
+
expect(path.closedRoughlyIntersects(new Rect2(-10000, 0, 500, 500))).toBe(false);
|
308
310
|
});
|
309
311
|
});
|
310
312
|
|
@@ -315,21 +317,11 @@ describe('Path', () => {
|
|
315
317
|
L20,20
|
316
318
|
L100,21
|
317
319
|
`);
|
318
|
-
expect(
|
319
|
-
|
320
|
-
).toBe(
|
321
|
-
expect(
|
322
|
-
|
323
|
-
).toBe(false);
|
324
|
-
expect(
|
325
|
-
path.roughlyIntersects(new Rect2(8, 22, 1, 1))
|
326
|
-
).toBe(false);
|
327
|
-
expect(
|
328
|
-
path.roughlyIntersects(new Rect2(21, 11, 1, 1))
|
329
|
-
).toBe(false);
|
330
|
-
expect(
|
331
|
-
path.roughlyIntersects(new Rect2(50, 19, 1, 2))
|
332
|
-
).toBe(true);
|
320
|
+
expect(path.roughlyIntersects(new Rect2(0, 0, 50, 50))).toBe(true);
|
321
|
+
expect(path.roughlyIntersects(new Rect2(0, 0, 5, 5))).toBe(false);
|
322
|
+
expect(path.roughlyIntersects(new Rect2(8, 22, 1, 1))).toBe(false);
|
323
|
+
expect(path.roughlyIntersects(new Rect2(21, 11, 1, 1))).toBe(false);
|
324
|
+
expect(path.roughlyIntersects(new Rect2(50, 19, 1, 2))).toBe(true);
|
333
325
|
});
|
334
326
|
});
|
335
327
|
|
@@ -358,9 +350,7 @@ describe('Path', () => {
|
|
358
350
|
});
|
359
351
|
|
360
352
|
describe('splitAt', () => {
|
361
|
-
it.each([
|
362
|
-
2, 3, 4, 5,
|
363
|
-
])('should split a line into %d sections', (numSections) => {
|
353
|
+
it.each([2, 3, 4, 5])('should split a line into %d sections', (numSections) => {
|
364
354
|
const path = Path.fromString('m0,0 l1,0');
|
365
355
|
|
366
356
|
const splitIndices: CurveIndexRecord[] = [];
|
@@ -371,7 +361,7 @@ describe('Path', () => {
|
|
371
361
|
|
372
362
|
expect(split).toHaveLength(numSections + 1);
|
373
363
|
expect(split[numSections].getEndPoint()).objEq(Vec2.unitX);
|
374
|
-
for (let i = 0; i < numSections; i
|
364
|
+
for (let i = 0; i < numSections; i++) {
|
375
365
|
expect(split[i].geometry).toHaveLength(1);
|
376
366
|
const geom = split[i].geometry[0] as LineSegment2;
|
377
367
|
expect(geom.p1.y).toBeCloseTo(0);
|
@@ -432,13 +422,13 @@ describe('Path', () => {
|
|
432
422
|
original: 'm0,0 Q4,0 8,0 Q8,4 8,8',
|
433
423
|
near: Vec2.of(8, 4),
|
434
424
|
map: (p: Point2) => p.plus(Vec2.of(1, 1)),
|
435
|
-
expected: [
|
425
|
+
expected: ['M0,0Q4,0 8,0Q9,3 9,5', 'M9,5Q9,7 9,9'],
|
436
426
|
},
|
437
427
|
{
|
438
428
|
original: 'm0,0 L0,10',
|
439
429
|
near: Vec2.of(0, 5),
|
440
430
|
map: (p: Point2) => p.plus(Vec2.of(100, 0)),
|
441
|
-
expected: [
|
431
|
+
expected: ['M0,0L100,5', 'M100,5L0,10'],
|
442
432
|
},
|
443
433
|
{
|
444
434
|
// Tested using SVG data similar to:
|
@@ -448,13 +438,16 @@ describe('Path', () => {
|
|
448
438
|
original: 'm1,1 C1,2 2,10 4,4 C5,0 9,3 7,7',
|
449
439
|
near: Vec2.of(3, 5),
|
450
440
|
map: (p: Point2) => Vec2.of(Math.round(p.x), Math.round(p.y)),
|
451
|
-
expected: [
|
441
|
+
expected: ['M1,1C1,2 1,6 2,6', 'M2,6C3,6 3,6 4,4C5,0 9,3 7,7'],
|
452
442
|
},
|
453
|
-
])(
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
443
|
+
])(
|
444
|
+
'should support mapping newly-added points while splitting (case %j)',
|
445
|
+
({ original, near, map, expected }) => {
|
446
|
+
const path = Path.fromString(original);
|
447
|
+
const split = path.splitNear(near, { mapNewPoint: map });
|
448
|
+
expect(split.map((p) => p.toString(false))).toMatchObject(expected);
|
449
|
+
},
|
450
|
+
);
|
458
451
|
});
|
459
452
|
|
460
453
|
describe('spliced', () => {
|
@@ -485,23 +478,26 @@ describe('Path', () => {
|
|
485
478
|
insert: 'M1,0 L1,1 L3,1 L3,0',
|
486
479
|
expected: 'M1,0 L3,0 L3,1 L1,1 L1,0',
|
487
480
|
},
|
488
|
-
])(
|
489
|
-
|
490
|
-
|
491
|
-
originalCurve.
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
481
|
+
])(
|
482
|
+
'.spliced should support inserting paths inbetween other paths (case %#)',
|
483
|
+
({ curve, from, to, insert, expected }) => {
|
484
|
+
const originalCurve = Path.fromString(curve);
|
485
|
+
expect(
|
486
|
+
originalCurve.spliced(
|
487
|
+
{ curveIndex: from.i, parameterValue: from.t },
|
488
|
+
{ curveIndex: to.i, parameterValue: to.t },
|
489
|
+
Path.fromString(insert),
|
490
|
+
),
|
491
|
+
).objEq(Path.fromString(expected));
|
492
|
+
},
|
493
|
+
);
|
498
494
|
});
|
499
495
|
|
500
496
|
it.each([
|
501
|
-
[
|
502
|
-
[
|
503
|
-
[
|
504
|
-
[
|
497
|
+
['m0,0 L1,1', 'M1,1 L0,0'],
|
498
|
+
['m0,0 L1,1', 'M1,1 L0,0'],
|
499
|
+
['M0,0 L1,1 Q2,2 3,3', 'M3,3 Q2,2 1,1 L0,0'],
|
500
|
+
['M0,0 L1,1 Q4,2 5,3 C12,13 10,9 8,7', 'M8,7 C 10,9 12,13 5,3 Q 4,2 1,1 L 0,0'],
|
505
501
|
])('.reversed should reverse paths', (original, expected) => {
|
506
502
|
expect(Path.fromString(original).reversed()).objEq(Path.fromString(expected));
|
507
503
|
expect(Path.fromString(expected).reversed()).objEq(Path.fromString(original));
|
@@ -509,29 +505,35 @@ describe('Path', () => {
|
|
509
505
|
});
|
510
506
|
|
511
507
|
it.each([
|
512
|
-
[
|
513
|
-
[
|
514
|
-
[
|
515
|
-
])(
|
516
|
-
|
517
|
-
|
508
|
+
['m0,0 l1,0', Vec2.of(0, 0), Vec2.of(0, 0)],
|
509
|
+
['m0,0 l1,0', Vec2.of(0.5, 0), Vec2.of(0.5, 0)],
|
510
|
+
['m0,0 Q1,0 1,2', Vec2.of(1, 0), Vec2.of(0.6236, 0.299)],
|
511
|
+
])(
|
512
|
+
'.nearestPointTo should return the closest point on a path to the given parameter (case %#)',
|
513
|
+
(path, point, expectedClosest) => {
|
514
|
+
expect(Path.fromString(path).nearestPointTo(point).point).objEq(expectedClosest, 0.002);
|
515
|
+
},
|
516
|
+
);
|
518
517
|
|
519
518
|
it.each([
|
520
519
|
// Polyline
|
521
|
-
[
|
522
|
-
[
|
523
|
-
[
|
524
|
-
[
|
525
|
-
[
|
520
|
+
['m0,0 l1,0 l0,1', [0, 0.5], Vec2.of(1, 0)],
|
521
|
+
['m0,0 l1,0 l0,1', [0, 0.99], Vec2.of(1, 0)],
|
522
|
+
['m0,0 l1,0 l0,1', [1, 0], Vec2.of(0, 1)],
|
523
|
+
['m0,0 l1,0 l0,1', [1, 0.5], Vec2.of(0, 1)],
|
524
|
+
['m0,0 l1,0 l0,1', [1, 1], Vec2.of(0, 1)],
|
526
525
|
|
527
526
|
// Shape with quadratic Bézier curves
|
528
|
-
[
|
529
|
-
[
|
530
|
-
[
|
531
|
-
[
|
532
|
-
])(
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
527
|
+
['M0,0 Q1,0 0,1', [0, 0], Vec2.of(1, 0)],
|
528
|
+
['M0,0 Q1,1 0,1', [0, 1], Vec2.of(-1, 0)],
|
529
|
+
['M0,0 Q1,0 1,1 Q0,1 0,2', [0, 1], Vec2.of(0, 1)],
|
530
|
+
['M0,0 Q1,0 1,1 Q0,1 0,2', [1, 1], Vec2.of(0, 1)],
|
531
|
+
])(
|
532
|
+
'.tangentAt should point in the direction of increasing parameter values, for curve %s at %j',
|
533
|
+
(pathString, evalAt, expected) => {
|
534
|
+
const at: CurveIndexRecord = { curveIndex: evalAt[0], parameterValue: evalAt[1] };
|
535
|
+
const path = Path.fromString(pathString);
|
536
|
+
expect(path.tangentAt(at)).objEq(expected);
|
537
|
+
},
|
538
|
+
);
|
537
539
|
});
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import Path, { PathCommandType } from './Path';
|
2
2
|
import { Vec2 } from '../Vec2';
|
3
3
|
|
4
|
-
|
5
4
|
describe('Path.toString', () => {
|
6
5
|
it('a single-point path should produce a move-to command', () => {
|
7
6
|
const path = new Path(Vec2.of(0, 0), []);
|
@@ -27,8 +26,8 @@ describe('Path.toString', () => {
|
|
27
26
|
},
|
28
27
|
{
|
29
28
|
kind: PathCommandType.LineTo,
|
30
|
-
point: Vec2.of(184.00482359999998, 1)
|
31
|
-
}
|
29
|
+
point: Vec2.of(184.00482359999998, 1),
|
30
|
+
},
|
32
31
|
]);
|
33
32
|
|
34
33
|
expect(path.toString()).toBe('M.1,.2Q9999,-11 .0003,1.4L184.0048236,1');
|
@@ -49,13 +48,14 @@ describe('Path.toString', () => {
|
|
49
48
|
const path1 = Path.fromString('M100,100 L101,101 Q102,102 90.000000001,89.99999999 Z');
|
50
49
|
const ignoreCache = true;
|
51
50
|
|
52
|
-
expect(path1.toString(undefined, ignoreCache)).toBe(
|
53
|
-
'M100,100', 'l1,1', 'q1,1 -11-11', 'l10,10'
|
54
|
-
|
51
|
+
expect(path1.toString(undefined, ignoreCache)).toBe(
|
52
|
+
['M100,100', 'l1,1', 'q1,1 -11-11', 'l10,10'].join(''),
|
53
|
+
);
|
55
54
|
});
|
56
55
|
|
57
56
|
it('should not lose precision when saving', () => {
|
58
|
-
const pathStr =
|
57
|
+
const pathStr =
|
58
|
+
'M184.2,52.3l-.2-.2q-2.7,2.4 -3.2,3.5q-2.8,7 -.9,6.1q4.3-2.6 4.8-6.1q1.2-8.8 .4-8.3q-4.2,5.2 -3.9,3.9q.2-1.6 .3-2.1q.2-1.3 -.2-1q-3.8,6.5 -3.2,3.3q.6-4.1 1.1-5.3q4.1-10 3.3-8.3q-5.3,13.1 -6.6,14.1q-3.3,2.8 -1.8-1.5q2.8-9.7 2.7-8.4q0,.3 0,.4q-1.4,7.1 -2.7,8.5q-2.6,3.2 -2.5,2.9q-.3-1.9 -.7-1.9q-4.1,4.4 -2.9,1.9q1.1-3 .3-2.6q-1.8,2 -2.5,2.4q-4.5,2.8 -4.2,1.9q.3-1.6 .2-1.4q1.5,2.2 1.3,2.9q-.8,3.9 -.5,3.3q.8-7.6 2.5-13.3q2.6-9.2 2.9-6.9q.3,1.4 .3,1.2q-.7-.4 -.9,0q-2.2,11.6 -7.6,13.6q-3.9,1.6 -2.1-1.3q3-5.5 2.6-3.4q-.2,1.8 -.5,1.8q-3.2,.5 -4.1,1.2q-2.6,2.6 -1.9,2.5q4.7-4.4 3.7-5.5q-1.1-.9 -1.6-.6q-7.2,7.5 -3.9,6.5q.3-.1 .4-.4q.6-5.3 -.2-4.9q-2.8,2.3 -3.1,2.4q-3.7,1.5 -3.5,.5q.3-3.6 1.4-3.3q3.5,.7 1.9,2.4q-1.7,2.3 -1.6,.8q0-3.5 -.9-3.1q-5.1,3.3 -4.9,2.8q.1-4 -.8-3.5q-4.3,3.4 -4.6,2.5q-1-2.1 .5-8.7l-.2,0q-1.6,6.6 -.7,8.9q.7,1.2 5.2-2.3q.4-.5 .2,3.1q.1,1 5.5-2.4q.4-.4 .3,2.7q.1,2 2.4-.4q1.7-2.3 -2.1-3.2q-1.7-.3 -2,3.7q0,1.4 4.1-.1q.3-.1 3.1-2.4q.3-.5 -.4,4.5q0-.1 -.2,0q-2.6,1.2 4.5-5.7q0-.2 .8,.6q.9,.6 -3.7,4.7q-.5,1 2.7-1.7q.6-.7 3.7-1.2q.7-.2 .9-2.2q.1-2.7 -3.4,3.2q-1.8,3.4 2.7,1.9q5.6-2.1 7.8-14q-.1,.1 .3,.4q.6,.1 .3-1.6q-.7-2.8 -3.7,6.7q-1.8,5.8 -2.5,13.5q.1,1.1 1.3-3.1q.2-1 -1.3-3.3q-.5-.5 -1,1.6q-.1,1.3 4.8-1.5q1-1 3-2q.1-.4 -1.1,2q-1.1,3.1 3.7-1.3q-.4,0 -.1,1.5q.3,.8 3.3-2.5q1.3-1.6 2.7-8.9q0-.1 0-.4q-.3-1.9 -3.5,8.2q-1.3,4.9 2.4,2.1q1.4-1.2 6.6-14.3q.8-2.4 -3.9,7.9q-.6,1.3 -1.1,5.5q-.3,3.7 4-3.1q-.2,0 -.6,.6q-.2,.6 -.3,2.3q0,1.8 4.7-3.5q.1-.5 -1.2,7.9q-.5,3.2 -4.6,5.7q-1.3,1 1.5-5.5q.4-1.1 3.01-3.5';
|
59
59
|
|
60
60
|
const path1 = Path.fromString(pathStr);
|
61
61
|
const ignoreCache = true;
|