@js-draw/math 1.11.1 → 1.17.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/dist/cjs/Vec3.d.ts +21 -0
- package/dist/cjs/Vec3.js +28 -0
- package/dist/cjs/lib.d.ts +2 -2
- package/dist/cjs/lib.js +16 -3
- package/dist/cjs/rounding/cleanUpNumber.d.ts +3 -0
- package/dist/cjs/rounding/cleanUpNumber.js +35 -0
- package/dist/cjs/rounding/constants.d.ts +1 -0
- package/dist/cjs/rounding/constants.js +4 -0
- package/dist/cjs/rounding/getLenAfterDecimal.d.ts +10 -0
- package/dist/cjs/rounding/getLenAfterDecimal.js +30 -0
- package/dist/cjs/rounding/lib.d.ts +1 -0
- package/dist/cjs/rounding/lib.js +5 -0
- package/dist/cjs/{rounding.d.ts → rounding/toRoundedString.d.ts} +1 -3
- package/dist/cjs/rounding/toRoundedString.js +54 -0
- package/dist/cjs/rounding/toStringOfSamePrecision.d.ts +2 -0
- package/dist/cjs/rounding/toStringOfSamePrecision.js +58 -0
- package/dist/cjs/rounding/toStringOfSamePrecision.test.d.ts +1 -0
- package/dist/cjs/shapes/Abstract2DShape.d.ts +3 -0
- package/dist/cjs/shapes/BezierJSWrapper.d.ts +15 -5
- package/dist/cjs/shapes/BezierJSWrapper.js +135 -18
- package/dist/cjs/shapes/LineSegment2.d.ts +34 -5
- package/dist/cjs/shapes/LineSegment2.js +63 -10
- package/dist/cjs/shapes/Parameterized2DShape.d.ts +31 -0
- package/dist/cjs/shapes/Parameterized2DShape.js +15 -0
- package/dist/cjs/shapes/Path.d.ts +40 -6
- package/dist/cjs/shapes/Path.js +181 -22
- package/dist/cjs/shapes/PointShape2D.d.ts +14 -3
- package/dist/cjs/shapes/PointShape2D.js +28 -5
- package/dist/cjs/shapes/QuadraticBezier.d.ts +4 -0
- package/dist/cjs/shapes/QuadraticBezier.js +19 -4
- package/dist/cjs/shapes/Rect2.d.ts +3 -0
- package/dist/cjs/shapes/Rect2.js +4 -1
- package/dist/mjs/Vec3.d.ts +21 -0
- package/dist/mjs/Vec3.mjs +28 -0
- package/dist/mjs/lib.d.ts +2 -2
- package/dist/mjs/lib.mjs +1 -1
- package/dist/mjs/rounding/cleanUpNumber.d.ts +3 -0
- package/dist/mjs/rounding/cleanUpNumber.mjs +31 -0
- package/dist/mjs/rounding/cleanUpNumber.test.d.ts +1 -0
- package/dist/mjs/rounding/constants.d.ts +1 -0
- package/dist/mjs/rounding/constants.mjs +1 -0
- package/dist/mjs/rounding/getLenAfterDecimal.d.ts +10 -0
- package/dist/mjs/rounding/getLenAfterDecimal.mjs +26 -0
- package/dist/mjs/rounding/lib.d.ts +1 -0
- package/dist/mjs/rounding/lib.mjs +1 -0
- package/dist/mjs/{rounding.d.ts → rounding/toRoundedString.d.ts} +1 -3
- package/dist/mjs/rounding/toRoundedString.mjs +47 -0
- package/dist/mjs/rounding/toRoundedString.test.d.ts +1 -0
- package/dist/mjs/rounding/toStringOfSamePrecision.d.ts +2 -0
- package/dist/mjs/rounding/toStringOfSamePrecision.mjs +51 -0
- package/dist/mjs/rounding/toStringOfSamePrecision.test.d.ts +1 -0
- package/dist/mjs/shapes/Abstract2DShape.d.ts +3 -0
- package/dist/mjs/shapes/BezierJSWrapper.d.ts +15 -5
- package/dist/mjs/shapes/BezierJSWrapper.mjs +133 -18
- package/dist/mjs/shapes/LineSegment2.d.ts +34 -5
- package/dist/mjs/shapes/LineSegment2.mjs +63 -10
- package/dist/mjs/shapes/Parameterized2DShape.d.ts +31 -0
- package/dist/mjs/shapes/Parameterized2DShape.mjs +8 -0
- package/dist/mjs/shapes/Path.d.ts +40 -6
- package/dist/mjs/shapes/Path.mjs +175 -16
- package/dist/mjs/shapes/PointShape2D.d.ts +14 -3
- package/dist/mjs/shapes/PointShape2D.mjs +28 -5
- package/dist/mjs/shapes/QuadraticBezier.d.ts +4 -0
- package/dist/mjs/shapes/QuadraticBezier.mjs +19 -4
- package/dist/mjs/shapes/Rect2.d.ts +3 -0
- package/dist/mjs/shapes/Rect2.mjs +4 -1
- package/package.json +5 -5
- package/src/Vec3.test.ts +26 -7
- package/src/Vec3.ts +30 -0
- package/src/lib.ts +3 -1
- package/src/rounding/cleanUpNumber.test.ts +15 -0
- package/src/rounding/cleanUpNumber.ts +38 -0
- package/src/rounding/constants.ts +3 -0
- package/src/rounding/getLenAfterDecimal.ts +29 -0
- package/src/rounding/lib.ts +2 -0
- package/src/rounding/toRoundedString.test.ts +32 -0
- package/src/rounding/toRoundedString.ts +57 -0
- package/src/rounding/toStringOfSamePrecision.test.ts +21 -0
- package/src/rounding/toStringOfSamePrecision.ts +63 -0
- package/src/shapes/Abstract2DShape.ts +3 -0
- package/src/shapes/BezierJSWrapper.ts +154 -14
- package/src/shapes/LineSegment2.test.ts +35 -1
- package/src/shapes/LineSegment2.ts +79 -11
- package/src/shapes/Parameterized2DShape.ts +39 -0
- package/src/shapes/Path.test.ts +63 -3
- package/src/shapes/Path.ts +211 -26
- package/src/shapes/PointShape2D.ts +33 -6
- package/src/shapes/QuadraticBezier.test.ts +48 -12
- package/src/shapes/QuadraticBezier.ts +23 -5
- package/src/shapes/Rect2.ts +4 -1
- package/dist/cjs/rounding.js +0 -146
- package/dist/mjs/rounding.mjs +0 -139
- package/src/rounding.test.ts +0 -65
- package/src/rounding.ts +0 -168
- /package/dist/cjs/{rounding.test.d.ts → rounding/cleanUpNumber.test.d.ts} +0 -0
- /package/dist/{mjs/rounding.test.d.ts → cjs/rounding/toRoundedString.test.d.ts} +0 -0
package/dist/mjs/shapes/Path.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { toRoundedString, toStringOfSamePrecision } from '../rounding.mjs';
|
|
2
1
|
import LineSegment2 from './LineSegment2.mjs';
|
|
3
2
|
import Rect2 from './Rect2.mjs';
|
|
4
3
|
import { Vec2 } from '../Vec2.mjs';
|
|
5
4
|
import CubicBezier from './CubicBezier.mjs';
|
|
6
5
|
import QuadraticBezier from './QuadraticBezier.mjs';
|
|
7
6
|
import PointShape2D from './PointShape2D.mjs';
|
|
7
|
+
import toRoundedString from '../rounding/toRoundedString.mjs';
|
|
8
|
+
import toStringOfSamePrecision from '../rounding/toStringOfSamePrecision.mjs';
|
|
8
9
|
export var PathCommandType;
|
|
9
10
|
(function (PathCommandType) {
|
|
10
11
|
PathCommandType[PathCommandType["LineTo"] = 0] = "LineTo";
|
|
@@ -229,7 +230,7 @@ export class Path {
|
|
|
229
230
|
for (const { part, distFn, bbox } of uncheckedDistFunctions) {
|
|
230
231
|
// Skip if impossible for the distance to the target to be lesser than
|
|
231
232
|
// the current minimum.
|
|
232
|
-
if (!bbox.grownBy(minDist).containsPoint(point)) {
|
|
233
|
+
if (isFinite(minDist) && !bbox.grownBy(minDist).containsPoint(point)) {
|
|
233
234
|
continue;
|
|
234
235
|
}
|
|
235
236
|
const currentDist = distFn(point);
|
|
@@ -267,7 +268,7 @@ export class Path {
|
|
|
267
268
|
});
|
|
268
269
|
const result = [];
|
|
269
270
|
const stoppingThreshold = strokeRadius / 1000;
|
|
270
|
-
// Returns the maximum
|
|
271
|
+
// Returns the maximum parameter value explored
|
|
271
272
|
const raymarchFrom = (startPoint,
|
|
272
273
|
// Direction to march in (multiplies line.direction)
|
|
273
274
|
directionMultiplier,
|
|
@@ -311,9 +312,14 @@ export class Path {
|
|
|
311
312
|
if (lastPart && isOnLineSegment && Math.abs(lastDist) < stoppingThreshold) {
|
|
312
313
|
result.push({
|
|
313
314
|
point: currentPoint,
|
|
314
|
-
parameterValue: NaN,
|
|
315
|
+
parameterValue: NaN, // lastPart.nearestPointTo(currentPoint).parameterValue,
|
|
315
316
|
curve: lastPart,
|
|
317
|
+
curveIndex: this.geometry.indexOf(lastPart),
|
|
316
318
|
});
|
|
319
|
+
// Slightly increase the parameter value to prevent the same point from being
|
|
320
|
+
// added to the results twice.
|
|
321
|
+
const parameterIncrease = strokeRadius / 20 / line.length;
|
|
322
|
+
lastParameter += isFinite(parameterIncrease) ? parameterIncrease : 0;
|
|
317
323
|
}
|
|
318
324
|
return lastParameter;
|
|
319
325
|
};
|
|
@@ -346,14 +352,18 @@ export class Path {
|
|
|
346
352
|
if (!line.bbox.intersects(this.bbox.grownBy(strokeRadius ?? 0))) {
|
|
347
353
|
return [];
|
|
348
354
|
}
|
|
355
|
+
let index = 0;
|
|
349
356
|
for (const part of this.geometry) {
|
|
350
|
-
const
|
|
351
|
-
|
|
357
|
+
const intersections = part.argIntersectsLineSegment(line);
|
|
358
|
+
for (const intersection of intersections) {
|
|
352
359
|
result.push({
|
|
353
360
|
curve: part,
|
|
354
|
-
|
|
361
|
+
curveIndex: index,
|
|
362
|
+
point: part.at(intersection),
|
|
363
|
+
parameterValue: intersection,
|
|
355
364
|
});
|
|
356
365
|
}
|
|
366
|
+
index++;
|
|
357
367
|
}
|
|
358
368
|
// If given a non-zero strokeWidth, attempt to raymarch.
|
|
359
369
|
// Even if raymarching, we need to collect starting points.
|
|
@@ -366,6 +376,42 @@ export class Path {
|
|
|
366
376
|
}
|
|
367
377
|
return result;
|
|
368
378
|
}
|
|
379
|
+
/**
|
|
380
|
+
* @returns the nearest point on this path to the given `point`.
|
|
381
|
+
*
|
|
382
|
+
* @internal
|
|
383
|
+
* @beta
|
|
384
|
+
*/
|
|
385
|
+
nearestPointTo(point) {
|
|
386
|
+
// Find the closest point on this
|
|
387
|
+
let closestSquareDist = Infinity;
|
|
388
|
+
let closestPartIndex = 0;
|
|
389
|
+
let closestParameterValue = 0;
|
|
390
|
+
let closestPoint = this.startPoint;
|
|
391
|
+
for (let i = 0; i < this.geometry.length; i++) {
|
|
392
|
+
const current = this.geometry[i];
|
|
393
|
+
const nearestPoint = current.nearestPointTo(point);
|
|
394
|
+
const sqareDist = nearestPoint.point.squareDistanceTo(point);
|
|
395
|
+
if (i === 0 || sqareDist < closestSquareDist) {
|
|
396
|
+
closestPartIndex = i;
|
|
397
|
+
closestSquareDist = sqareDist;
|
|
398
|
+
closestParameterValue = nearestPoint.parameterValue;
|
|
399
|
+
closestPoint = nearestPoint.point;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
curve: this.geometry[closestPartIndex],
|
|
404
|
+
curveIndex: closestPartIndex,
|
|
405
|
+
parameterValue: closestParameterValue,
|
|
406
|
+
point: closestPoint,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
at(index) {
|
|
410
|
+
return this.geometry[index.curveIndex].at(index.parameterValue);
|
|
411
|
+
}
|
|
412
|
+
tangentAt(index) {
|
|
413
|
+
return this.geometry[index.curveIndex].tangentAt(index.parameterValue);
|
|
414
|
+
}
|
|
369
415
|
static mapPathCommand(part, mapping) {
|
|
370
416
|
switch (part.kind) {
|
|
371
417
|
case PathCommandType.MoveTo:
|
|
@@ -409,18 +455,85 @@ export class Path {
|
|
|
409
455
|
return this.mapPoints(point => affineTransfm.transformVec2(point));
|
|
410
456
|
}
|
|
411
457
|
// Creates a new path by joining [other] to the end of this path
|
|
412
|
-
union(other
|
|
458
|
+
union(other,
|
|
459
|
+
// allowReverse: true iff reversing other or this is permitted if it means
|
|
460
|
+
// no moveTo command is necessary when unioning the paths.
|
|
461
|
+
options = { allowReverse: true }) {
|
|
413
462
|
if (!other) {
|
|
414
463
|
return this;
|
|
415
464
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
465
|
+
const thisEnd = this.getEndPoint();
|
|
466
|
+
let newParts = [];
|
|
467
|
+
if (thisEnd.eq(other.startPoint)) {
|
|
468
|
+
newParts = this.parts.concat(other.parts);
|
|
469
|
+
}
|
|
470
|
+
else if (options.allowReverse && this.startPoint.eq(other.getEndPoint())) {
|
|
471
|
+
return other.union(this, { allowReverse: false });
|
|
472
|
+
}
|
|
473
|
+
else if (options.allowReverse && this.startPoint.eq(other.startPoint)) {
|
|
474
|
+
return this.union(other.reversed(), { allowReverse: false });
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
newParts = [
|
|
478
|
+
...this.parts,
|
|
479
|
+
{
|
|
480
|
+
kind: PathCommandType.MoveTo,
|
|
481
|
+
point: other.startPoint,
|
|
482
|
+
},
|
|
483
|
+
...other.parts,
|
|
484
|
+
];
|
|
485
|
+
}
|
|
486
|
+
return new Path(this.startPoint, newParts);
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* @returns a version of this path with the direction reversed.
|
|
490
|
+
*
|
|
491
|
+
* Example:
|
|
492
|
+
* ```ts,runnable,console
|
|
493
|
+
* import {Path} from '@js-draw/math';
|
|
494
|
+
* console.log(Path.fromString('m0,0l1,1').reversed()); // -> M1,1 L0,0
|
|
495
|
+
* ```
|
|
496
|
+
*/
|
|
497
|
+
reversed() {
|
|
498
|
+
const newStart = this.getEndPoint();
|
|
499
|
+
const newParts = [];
|
|
500
|
+
let lastPoint = this.startPoint;
|
|
501
|
+
for (const part of this.parts) {
|
|
502
|
+
switch (part.kind) {
|
|
503
|
+
case PathCommandType.LineTo:
|
|
504
|
+
case PathCommandType.MoveTo:
|
|
505
|
+
newParts.push({
|
|
506
|
+
kind: part.kind,
|
|
507
|
+
point: lastPoint,
|
|
508
|
+
});
|
|
509
|
+
lastPoint = part.point;
|
|
510
|
+
break;
|
|
511
|
+
case PathCommandType.CubicBezierTo:
|
|
512
|
+
newParts.push({
|
|
513
|
+
kind: part.kind,
|
|
514
|
+
controlPoint1: part.controlPoint2,
|
|
515
|
+
controlPoint2: part.controlPoint1,
|
|
516
|
+
endPoint: lastPoint,
|
|
517
|
+
});
|
|
518
|
+
lastPoint = part.endPoint;
|
|
519
|
+
break;
|
|
520
|
+
case PathCommandType.QuadraticBezierTo:
|
|
521
|
+
newParts.push({
|
|
522
|
+
kind: part.kind,
|
|
523
|
+
controlPoint: part.controlPoint,
|
|
524
|
+
endPoint: lastPoint,
|
|
525
|
+
});
|
|
526
|
+
lastPoint = part.endPoint;
|
|
527
|
+
break;
|
|
528
|
+
default:
|
|
529
|
+
{
|
|
530
|
+
const exhaustivenessCheck = part;
|
|
531
|
+
return exhaustivenessCheck;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
newParts.reverse();
|
|
536
|
+
return new Path(newStart, newParts);
|
|
424
537
|
}
|
|
425
538
|
getEndPoint() {
|
|
426
539
|
if (this.parts.length === 0) {
|
|
@@ -512,6 +625,52 @@ export class Path {
|
|
|
512
625
|
// Even? Probably no intersection.
|
|
513
626
|
return false;
|
|
514
627
|
}
|
|
628
|
+
/** @returns true if all points on this are equivalent to the points on `other` */
|
|
629
|
+
eq(other, tolerance) {
|
|
630
|
+
if (other.parts.length !== this.parts.length) {
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
for (let i = 0; i < this.parts.length; i++) {
|
|
634
|
+
const part1 = this.parts[i];
|
|
635
|
+
const part2 = other.parts[i];
|
|
636
|
+
switch (part1.kind) {
|
|
637
|
+
case PathCommandType.LineTo:
|
|
638
|
+
case PathCommandType.MoveTo:
|
|
639
|
+
if (part1.kind !== part2.kind) {
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
else if (!part1.point.eq(part2.point, tolerance)) {
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
break;
|
|
646
|
+
case PathCommandType.CubicBezierTo:
|
|
647
|
+
if (part1.kind !== part2.kind) {
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
else if (!part1.controlPoint1.eq(part2.controlPoint1, tolerance)
|
|
651
|
+
|| !part1.controlPoint2.eq(part2.controlPoint2, tolerance)
|
|
652
|
+
|| !part1.endPoint.eq(part2.endPoint, tolerance)) {
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
break;
|
|
656
|
+
case PathCommandType.QuadraticBezierTo:
|
|
657
|
+
if (part1.kind !== part2.kind) {
|
|
658
|
+
return false;
|
|
659
|
+
}
|
|
660
|
+
else if (!part1.controlPoint.eq(part2.controlPoint, tolerance)
|
|
661
|
+
|| !part1.endPoint.eq(part2.endPoint, tolerance)) {
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
break;
|
|
665
|
+
default:
|
|
666
|
+
{
|
|
667
|
+
const exhaustivenessCheck = part1;
|
|
668
|
+
return exhaustivenessCheck;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return true;
|
|
673
|
+
}
|
|
515
674
|
/**
|
|
516
675
|
* Returns a path that outlines `rect`.
|
|
517
676
|
*
|
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
import { Point2 } from '../Vec2';
|
|
2
2
|
import Vec3 from '../Vec3';
|
|
3
|
-
import Abstract2DShape from './Abstract2DShape';
|
|
4
3
|
import LineSegment2 from './LineSegment2';
|
|
4
|
+
import Parameterized2DShape from './Parameterized2DShape';
|
|
5
5
|
import Rect2 from './Rect2';
|
|
6
6
|
/**
|
|
7
7
|
* Like a {@link Point2}, but with additional functionality (e.g. SDF).
|
|
8
8
|
*
|
|
9
9
|
* Access the internal `Point2` using the `p` property.
|
|
10
10
|
*/
|
|
11
|
-
declare class PointShape2D extends
|
|
11
|
+
declare class PointShape2D extends Parameterized2DShape {
|
|
12
12
|
readonly p: Point2;
|
|
13
13
|
constructor(p: Point2);
|
|
14
14
|
signedDistance(point: Vec3): number;
|
|
15
|
-
|
|
15
|
+
argIntersectsLineSegment(lineSegment: LineSegment2, epsilon?: number): number[];
|
|
16
16
|
getTightBoundingBox(): Rect2;
|
|
17
|
+
at(_t: number): Vec3;
|
|
18
|
+
/**
|
|
19
|
+
* Returns an arbitrary unit-length vector.
|
|
20
|
+
*/
|
|
21
|
+
normalAt(_t: number): Vec3;
|
|
22
|
+
tangentAt(_t: number): Vec3;
|
|
23
|
+
splitAt(_t: number): [PointShape2D];
|
|
24
|
+
nearestPointTo(_point: Point2): {
|
|
25
|
+
point: Vec3;
|
|
26
|
+
parameterValue: number;
|
|
27
|
+
};
|
|
17
28
|
}
|
|
18
29
|
export default PointShape2D;
|
|
@@ -1,26 +1,49 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Vec2 } from '../Vec2.mjs';
|
|
2
|
+
import Parameterized2DShape from './Parameterized2DShape.mjs';
|
|
2
3
|
import Rect2 from './Rect2.mjs';
|
|
3
4
|
/**
|
|
4
5
|
* Like a {@link Point2}, but with additional functionality (e.g. SDF).
|
|
5
6
|
*
|
|
6
7
|
* Access the internal `Point2` using the `p` property.
|
|
7
8
|
*/
|
|
8
|
-
class PointShape2D extends
|
|
9
|
+
class PointShape2D extends Parameterized2DShape {
|
|
9
10
|
constructor(p) {
|
|
10
11
|
super();
|
|
11
12
|
this.p = p;
|
|
12
13
|
}
|
|
13
14
|
signedDistance(point) {
|
|
14
|
-
return this.p.
|
|
15
|
+
return this.p.distanceTo(point);
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
+
argIntersectsLineSegment(lineSegment, epsilon) {
|
|
17
18
|
if (lineSegment.containsPoint(this.p, epsilon)) {
|
|
18
|
-
return [
|
|
19
|
+
return [0];
|
|
19
20
|
}
|
|
20
21
|
return [];
|
|
21
22
|
}
|
|
22
23
|
getTightBoundingBox() {
|
|
23
24
|
return new Rect2(this.p.x, this.p.y, 0, 0);
|
|
24
25
|
}
|
|
26
|
+
at(_t) {
|
|
27
|
+
return this.p;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Returns an arbitrary unit-length vector.
|
|
31
|
+
*/
|
|
32
|
+
normalAt(_t) {
|
|
33
|
+
// Return a vector that makes sense.
|
|
34
|
+
return Vec2.unitY;
|
|
35
|
+
}
|
|
36
|
+
tangentAt(_t) {
|
|
37
|
+
return Vec2.unitX;
|
|
38
|
+
}
|
|
39
|
+
splitAt(_t) {
|
|
40
|
+
return [this];
|
|
41
|
+
}
|
|
42
|
+
nearestPointTo(_point) {
|
|
43
|
+
return {
|
|
44
|
+
point: this.p,
|
|
45
|
+
parameterValue: 0,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
25
48
|
}
|
|
26
49
|
export default PointShape2D;
|
|
@@ -18,11 +18,15 @@ export declare class QuadraticBezier extends BezierJSWrapper {
|
|
|
18
18
|
*/
|
|
19
19
|
private static componentAt;
|
|
20
20
|
private static derivativeComponentAt;
|
|
21
|
+
private static secondDerivativeComponentAt;
|
|
21
22
|
/**
|
|
22
23
|
* @returns the curve evaluated at `t`.
|
|
24
|
+
*
|
|
25
|
+
* `t` should be a number in `[0, 1]`.
|
|
23
26
|
*/
|
|
24
27
|
at(t: number): Point2;
|
|
25
28
|
derivativeAt(t: number): Point2;
|
|
29
|
+
secondDerivativeAt(t: number): Point2;
|
|
26
30
|
normal(t: number): Vec2;
|
|
27
31
|
/** @returns an overestimate of this shape's bounding box. */
|
|
28
32
|
getLooseBoundingBox(): Rect2;
|
|
@@ -25,10 +25,19 @@ export class QuadraticBezier extends BezierJSWrapper {
|
|
|
25
25
|
static derivativeComponentAt(t, p0, p1, p2) {
|
|
26
26
|
return -2 * p0 + 2 * p1 + 2 * t * (p0 - 2 * p1 + p2);
|
|
27
27
|
}
|
|
28
|
+
static secondDerivativeComponentAt(t, p0, p1, p2) {
|
|
29
|
+
return 2 * (p0 - 2 * p1 + p2);
|
|
30
|
+
}
|
|
28
31
|
/**
|
|
29
32
|
* @returns the curve evaluated at `t`.
|
|
33
|
+
*
|
|
34
|
+
* `t` should be a number in `[0, 1]`.
|
|
30
35
|
*/
|
|
31
36
|
at(t) {
|
|
37
|
+
if (t === 0)
|
|
38
|
+
return this.p0;
|
|
39
|
+
if (t === 1)
|
|
40
|
+
return this.p2;
|
|
32
41
|
const p0 = this.p0;
|
|
33
42
|
const p1 = this.p1;
|
|
34
43
|
const p2 = this.p2;
|
|
@@ -40,6 +49,12 @@ export class QuadraticBezier extends BezierJSWrapper {
|
|
|
40
49
|
const p2 = this.p2;
|
|
41
50
|
return Vec2.of(QuadraticBezier.derivativeComponentAt(t, p0.x, p1.x, p2.x), QuadraticBezier.derivativeComponentAt(t, p0.y, p1.y, p2.y));
|
|
42
51
|
}
|
|
52
|
+
secondDerivativeAt(t) {
|
|
53
|
+
const p0 = this.p0;
|
|
54
|
+
const p1 = this.p1;
|
|
55
|
+
const p2 = this.p2;
|
|
56
|
+
return Vec2.of(QuadraticBezier.secondDerivativeComponentAt(t, p0.x, p1.x, p2.x), QuadraticBezier.secondDerivativeComponentAt(t, p0.y, p1.y, p2.y));
|
|
57
|
+
}
|
|
43
58
|
normal(t) {
|
|
44
59
|
const tangent = this.derivativeAt(t);
|
|
45
60
|
return tangent.orthog().normalized();
|
|
@@ -100,10 +115,10 @@ export class QuadraticBezier extends BezierJSWrapper {
|
|
|
100
115
|
}
|
|
101
116
|
const at1 = this.at(min1);
|
|
102
117
|
const at2 = this.at(min2);
|
|
103
|
-
const sqrDist1 = at1.
|
|
104
|
-
const sqrDist2 = at2.
|
|
105
|
-
const sqrDist3 = this.at(0).
|
|
106
|
-
const sqrDist4 = this.at(1).
|
|
118
|
+
const sqrDist1 = at1.squareDistanceTo(point);
|
|
119
|
+
const sqrDist2 = at2.squareDistanceTo(point);
|
|
120
|
+
const sqrDist3 = this.at(0).squareDistanceTo(point);
|
|
121
|
+
const sqrDist4 = this.at(1).squareDistanceTo(point);
|
|
107
122
|
return Math.sqrt(Math.min(sqrDist1, sqrDist2, sqrDist3, sqrDist4));
|
|
108
123
|
}
|
|
109
124
|
getPoints() {
|
|
@@ -25,6 +25,9 @@ export declare class Rect2 extends Abstract2DShape {
|
|
|
25
25
|
resizedTo(size: Vec2): Rect2;
|
|
26
26
|
containsPoint(other: Point2): boolean;
|
|
27
27
|
containsRect(other: Rect2): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* @returns true iff this and `other` overlap
|
|
30
|
+
*/
|
|
28
31
|
intersects(other: Rect2): boolean;
|
|
29
32
|
intersection(other: Rect2): Rect2 | null;
|
|
30
33
|
union(other: Rect2): Rect2;
|
|
@@ -38,6 +38,9 @@ export class Rect2 extends Abstract2DShape {
|
|
|
38
38
|
&& this.x + this.w >= other.x + other.w
|
|
39
39
|
&& this.y + this.h >= other.y + other.h;
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* @returns true iff this and `other` overlap
|
|
43
|
+
*/
|
|
41
44
|
intersects(other) {
|
|
42
45
|
// Project along x/y axes.
|
|
43
46
|
const thisMinX = this.x;
|
|
@@ -124,7 +127,7 @@ export class Rect2 extends Abstract2DShape {
|
|
|
124
127
|
let closest = null;
|
|
125
128
|
let closestDist = null;
|
|
126
129
|
for (const point of closestEdgePoints) {
|
|
127
|
-
const dist = point.
|
|
130
|
+
const dist = point.distanceTo(target);
|
|
128
131
|
if (closestDist === null || dist < closestDist) {
|
|
129
132
|
closest = point;
|
|
130
133
|
closestDist = dist;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@js-draw/math",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"description": "A math library for js-draw. ",
|
|
5
5
|
"types": "./dist/mjs/lib.d.ts",
|
|
6
6
|
"main": "./dist/cjs/lib.js",
|
|
@@ -21,14 +21,14 @@
|
|
|
21
21
|
"scripts": {
|
|
22
22
|
"dist-test": "cd dist-test/test_imports && npm install && npm run test",
|
|
23
23
|
"dist": "npm run build && npm run dist-test",
|
|
24
|
-
"build": "rm -rf ./dist &&
|
|
25
|
-
"watch": "
|
|
24
|
+
"build": "rm -rf ./dist && build-tool build",
|
|
25
|
+
"watch": "build-tool watch"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"bezier-js": "6.1.3"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@js-draw/build-tool": "^1.
|
|
31
|
+
"@js-draw/build-tool": "^1.17.0",
|
|
32
32
|
"@types/bezier-js": "4.1.0",
|
|
33
33
|
"@types/jest": "29.5.5",
|
|
34
34
|
"@types/jsdom": "21.1.3"
|
|
@@ -45,5 +45,5 @@
|
|
|
45
45
|
"svg",
|
|
46
46
|
"math"
|
|
47
47
|
],
|
|
48
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "d0eff585750ab5670af3acda8ddff090e8825bd3"
|
|
49
49
|
}
|
package/src/Vec3.test.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import Vec3 from './Vec3';
|
|
3
3
|
|
|
4
4
|
describe('Vec3', () => {
|
|
5
|
-
|
|
5
|
+
test('.xy should contain the x and y components', () => {
|
|
6
6
|
const vec = Vec3.of(1, 2, 3);
|
|
7
7
|
expect(vec.xy).toMatchObject({
|
|
8
8
|
x: 1,
|
|
@@ -10,42 +10,61 @@ describe('Vec3', () => {
|
|
|
10
10
|
});
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
test('should be combinable with other vectors via .zip', () => {
|
|
14
14
|
const vec1 = Vec3.unitX;
|
|
15
15
|
const vec2 = Vec3.unitY;
|
|
16
16
|
expect(vec1.zip(vec2, Math.min)).objEq(Vec3.zero);
|
|
17
17
|
expect(vec1.zip(vec2, Math.max)).objEq(Vec3.of(1, 1, 0));
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
test('.cross should obey the right hand rule', () => {
|
|
21
21
|
const vec1 = Vec3.unitX;
|
|
22
22
|
const vec2 = Vec3.unitY;
|
|
23
23
|
expect(vec1.cross(vec2)).objEq(Vec3.unitZ);
|
|
24
24
|
expect(vec2.cross(vec1)).objEq(Vec3.unitZ.times(-1));
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
test('.orthog should return an orthogonal vector', () => {
|
|
28
28
|
expect(Vec3.unitZ.orthog().dot(Vec3.unitZ)).toBe(0);
|
|
29
29
|
|
|
30
30
|
// Should return some orthogonal vector, even if given the zero vector
|
|
31
31
|
expect(Vec3.zero.orthog().dot(Vec3.zero)).toBe(0);
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
test('.minus should return the difference between two vectors', () => {
|
|
35
35
|
expect(Vec3.of(1, 2, 3).minus(Vec3.of(4, 5, 6))).objEq(Vec3.of(1 - 4, 2 - 5, 3 - 6));
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
test('.orthog should return a unit vector', () => {
|
|
39
39
|
expect(Vec3.zero.orthog().magnitude()).toBe(1);
|
|
40
40
|
expect(Vec3.unitZ.orthog().magnitude()).toBe(1);
|
|
41
41
|
expect(Vec3.unitX.orthog().magnitude()).toBe(1);
|
|
42
42
|
expect(Vec3.unitY.orthog().magnitude()).toBe(1);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
test('.normalizedOrZero should normalize the given vector or return zero', () => {
|
|
46
46
|
expect(Vec3.zero.normalizedOrZero()).objEq(Vec3.zero);
|
|
47
47
|
expect(Vec3.unitX.normalizedOrZero()).objEq(Vec3.unitX);
|
|
48
48
|
expect(Vec3.unitX.times(22).normalizedOrZero()).objEq(Vec3.unitX);
|
|
49
49
|
expect(Vec3.of(1, 1, 1).times(22).normalizedOrZero().length()).toBeCloseTo(1);
|
|
50
50
|
});
|
|
51
|
+
|
|
52
|
+
test.each([
|
|
53
|
+
{ from: Vec3.of(1, 1, 1), to: Vec3.of(1, 2, 1), expected: 1 },
|
|
54
|
+
{ from: Vec3.of(1, 1, 1), to: Vec3.of(1, 2, 2), expected: 2 },
|
|
55
|
+
{ from: Vec3.of(1, 1, 1), to: Vec3.of(2, 2, 2), expected: 3 },
|
|
56
|
+
{ from: Vec3.of(1, 1, 1), to: Vec3.of(0, 1, 1), expected: 1 },
|
|
57
|
+
{ from: Vec3.of(1, 1, 1), to: Vec3.of(0, 1, 0), expected: 2 },
|
|
58
|
+
{ from: Vec3.of(1, 1, 1), to: Vec3.of(0, 0, 0), expected: 3 },
|
|
59
|
+
{ from: Vec3.of(-1, -10, 0), to: Vec3.of(1, 2, 0), expected: 148 },
|
|
60
|
+
])(
|
|
61
|
+
'.squareDistanceTo and .distanceTo should return correct square and euclidean distances (%j)',
|
|
62
|
+
({ from , to, expected }) => {
|
|
63
|
+
expect(from.squareDistanceTo(to)).toBe(expected);
|
|
64
|
+
expect(to.squareDistanceTo(from)).toBe(expected);
|
|
65
|
+
expect(to.distanceTo(from)).toBeCloseTo(Math.sqrt(expected));
|
|
66
|
+
expect(to.minus(from).magnitudeSquared()).toBe(expected);
|
|
67
|
+
expect(from.minus(to).magnitudeSquared()).toBe(expected);
|
|
68
|
+
},
|
|
69
|
+
);
|
|
51
70
|
});
|
package/src/Vec3.ts
CHANGED
|
@@ -63,11 +63,40 @@ export class Vec3 {
|
|
|
63
63
|
return this.dot(this);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Interpreting this vector as a point in ℝ^3, computes the square distance
|
|
68
|
+
* to another point, `p`.
|
|
69
|
+
*
|
|
70
|
+
* Equivalent to `.minus(p).magnitudeSquared()`.
|
|
71
|
+
*/
|
|
72
|
+
public squareDistanceTo(p: Vec3) {
|
|
73
|
+
const dx = this.x - p.x;
|
|
74
|
+
const dy = this.y - p.y;
|
|
75
|
+
const dz = this.z - p.z;
|
|
76
|
+
return dx * dx + dy * dy + dz * dz;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Interpreting this vector as a point in ℝ³, returns the distance to the point
|
|
81
|
+
* `p`.
|
|
82
|
+
*
|
|
83
|
+
* Equivalent to `.minus(p).magnitude()`.
|
|
84
|
+
*/
|
|
85
|
+
public distanceTo(p: Vec3) {
|
|
86
|
+
return Math.sqrt(this.squareDistanceTo(p));
|
|
87
|
+
}
|
|
88
|
+
|
|
66
89
|
/**
|
|
67
90
|
* Returns the entry of this with the greatest magnitude.
|
|
68
91
|
*
|
|
69
92
|
* In other words, returns $\max \{ |x| : x \in {\bf v} \}$, where ${\bf v}$ is the set of
|
|
70
93
|
* all entries of this vector.
|
|
94
|
+
*
|
|
95
|
+
* **Example**:
|
|
96
|
+
* ```ts,runnable,console
|
|
97
|
+
* import { Vec3 } from '@js-draw/math';
|
|
98
|
+
* console.log(Vec3.of(-1, -10, 8).maximumEntryMagnitude()); // -> 10
|
|
99
|
+
* ```
|
|
71
100
|
*/
|
|
72
101
|
public maximumEntryMagnitude(): number {
|
|
73
102
|
return Math.max(Math.abs(this.x), Math.max(Math.abs(this.y), Math.abs(this.z)));
|
|
@@ -81,6 +110,7 @@ export class Vec3 {
|
|
|
81
110
|
* As such, observing that `Math.atan2(-0, -1)` $\approx -\pi$ and `Math.atan2(0, -1)`$\approx \pi$
|
|
82
111
|
* the resultant angle is in the range $[-\pi, pi]$.
|
|
83
112
|
*
|
|
113
|
+
* **Example**:
|
|
84
114
|
* ```ts,runnable,console
|
|
85
115
|
* import { Vec2 } from '@js-draw/math';
|
|
86
116
|
* console.log(Vec2.of(-1, -0).angle()); // atan2(-0, -1)
|
package/src/lib.ts
CHANGED
|
@@ -21,6 +21,8 @@ export { LineSegment2 } from './shapes/LineSegment2';
|
|
|
21
21
|
export {
|
|
22
22
|
Path,
|
|
23
23
|
|
|
24
|
+
IntersectionResult as PathIntersectionResult,
|
|
25
|
+
CurveIndexRecord as PathCurveIndex,
|
|
24
26
|
PathCommandType,
|
|
25
27
|
PathCommand,
|
|
26
28
|
LinePathCommand,
|
|
@@ -36,7 +38,7 @@ export { Mat33, Mat33Array } from './Mat33';
|
|
|
36
38
|
export { Point2, Vec2 } from './Vec2';
|
|
37
39
|
export { Vec3 } from './Vec3';
|
|
38
40
|
export { Color4 } from './Color4';
|
|
39
|
-
export
|
|
41
|
+
export * from './rounding/lib';
|
|
40
42
|
|
|
41
43
|
|
|
42
44
|
// Note: All above exports cannot use `export { default as ... } from "..."` because this
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import cleanUpNumber from './cleanUpNumber';
|
|
2
|
+
|
|
3
|
+
it('cleanUpNumber', () => {
|
|
4
|
+
expect(cleanUpNumber('000.0000')).toBe('0');
|
|
5
|
+
expect(cleanUpNumber('-000.0000')).toBe('0');
|
|
6
|
+
expect(cleanUpNumber('0.0000')).toBe('0');
|
|
7
|
+
expect(cleanUpNumber('0.001')).toBe('.001');
|
|
8
|
+
expect(cleanUpNumber('-0.001')).toBe('-.001');
|
|
9
|
+
expect(cleanUpNumber('-0.000000001')).toBe('-.000000001');
|
|
10
|
+
expect(cleanUpNumber('-0.00000000100')).toBe('-.000000001');
|
|
11
|
+
expect(cleanUpNumber('1234')).toBe('1234');
|
|
12
|
+
expect(cleanUpNumber('1234.5')).toBe('1234.5');
|
|
13
|
+
expect(cleanUpNumber('1234.500')).toBe('1234.5');
|
|
14
|
+
expect(cleanUpNumber('1.1368683772161603e-13')).toBe('0');
|
|
15
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Cleans up stringified numbers */
|
|
2
|
+
export const cleanUpNumber = (text: string) => {
|
|
3
|
+
// Regular expression substitions can be somewhat expensive. Only do them
|
|
4
|
+
// if necessary.
|
|
5
|
+
|
|
6
|
+
if (text.indexOf('e') > 0) {
|
|
7
|
+
// Round to zero.
|
|
8
|
+
if (text.match(/[eE][-]\d{2,}$/)) {
|
|
9
|
+
return '0';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const lastChar = text.charAt(text.length - 1);
|
|
14
|
+
if (lastChar === '0' || lastChar === '.') {
|
|
15
|
+
// Remove trailing zeroes
|
|
16
|
+
text = text.replace(/([.]\d*[^0]+)0+$/, '$1');
|
|
17
|
+
text = text.replace(/[.]0+$/, '.');
|
|
18
|
+
|
|
19
|
+
// Remove trailing period
|
|
20
|
+
text = text.replace(/[.]$/, '');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const firstChar = text.charAt(0);
|
|
24
|
+
if (firstChar === '0' || firstChar === '-') {
|
|
25
|
+
// Remove unnecessary leading zeroes.
|
|
26
|
+
text = text.replace(/^(0+)[.]/, '.');
|
|
27
|
+
text = text.replace(/^-(0+)[.]/, '-.');
|
|
28
|
+
text = text.replace(/^(-?)0+$/, '$10');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (text === '-0') {
|
|
32
|
+
return '0';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return text;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default cleanUpNumber;
|