@js-draw/math 1.16.0 → 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 +1 -1
- 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 +173 -15
- 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 +1 -1
- 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 +173 -15
- 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 +2 -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 +209 -25
- 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
@@ -2,7 +2,7 @@ import LineSegment2 from './LineSegment2';
|
|
2
2
|
import Mat33 from '../Mat33';
|
3
3
|
import Rect2 from './Rect2';
|
4
4
|
import { Point2 } from '../Vec2';
|
5
|
-
import
|
5
|
+
import Parameterized2DShape from './Parameterized2DShape';
|
6
6
|
export declare enum PathCommandType {
|
7
7
|
LineTo = 0,
|
8
8
|
MoveTo = 1,
|
@@ -29,12 +29,23 @@ export interface MoveToPathCommand {
|
|
29
29
|
point: Point2;
|
30
30
|
}
|
31
31
|
export type PathCommand = CubicBezierPathCommand | QuadraticBezierPathCommand | MoveToPathCommand | LinePathCommand;
|
32
|
-
interface IntersectionResult {
|
33
|
-
curve:
|
34
|
-
|
32
|
+
export interface IntersectionResult {
|
33
|
+
curve: Parameterized2DShape;
|
34
|
+
curveIndex: number;
|
35
|
+
/** Parameter value for the closest point **on** the path to the intersection. @internal @deprecated */
|
35
36
|
parameterValue?: number;
|
37
|
+
/** Point at which the intersection occured. */
|
36
38
|
point: Point2;
|
37
39
|
}
|
40
|
+
/**
|
41
|
+
* Allows indexing a particular part of a path.
|
42
|
+
*
|
43
|
+
* @see {@link Path.at} {@link Path.tangentAt}
|
44
|
+
*/
|
45
|
+
export interface CurveIndexRecord {
|
46
|
+
curveIndex: number;
|
47
|
+
parameterValue: number;
|
48
|
+
}
|
38
49
|
/**
|
39
50
|
* Represents a union of lines and curves.
|
40
51
|
*/
|
@@ -57,7 +68,7 @@ export declare class Path {
|
|
57
68
|
constructor(startPoint: Point2, parts: Readonly<PathCommand>[]);
|
58
69
|
getExactBBox(): Rect2;
|
59
70
|
private cachedGeometry;
|
60
|
-
get geometry():
|
71
|
+
get geometry(): Parameterized2DShape[];
|
61
72
|
/**
|
62
73
|
* Iterates through the start/end points of each component in this path.
|
63
74
|
*
|
@@ -86,10 +97,31 @@ export declare class Path {
|
|
86
97
|
* **Note**: `strokeRadius` is half of a stroke's width.
|
87
98
|
*/
|
88
99
|
intersection(line: LineSegment2, strokeRadius?: number): IntersectionResult[];
|
100
|
+
/**
|
101
|
+
* @returns the nearest point on this path to the given `point`.
|
102
|
+
*
|
103
|
+
* @internal
|
104
|
+
* @beta
|
105
|
+
*/
|
106
|
+
nearestPointTo(point: Point2): IntersectionResult;
|
107
|
+
at(index: CurveIndexRecord): import("../Vec3").Vec3;
|
108
|
+
tangentAt(index: CurveIndexRecord): import("../Vec3").Vec3;
|
89
109
|
private static mapPathCommand;
|
90
110
|
mapPoints(mapping: (point: Point2) => Point2): Path;
|
91
111
|
transformedBy(affineTransfm: Mat33): Path;
|
92
|
-
union(other: Path | null
|
112
|
+
union(other: Path | null, options?: {
|
113
|
+
allowReverse?: boolean;
|
114
|
+
}): Path;
|
115
|
+
/**
|
116
|
+
* @returns a version of this path with the direction reversed.
|
117
|
+
*
|
118
|
+
* Example:
|
119
|
+
* ```ts,runnable,console
|
120
|
+
* import {Path} from '@js-draw/math';
|
121
|
+
* console.log(Path.fromString('m0,0l1,1').reversed()); // -> M1,1 L0,0
|
122
|
+
* ```
|
123
|
+
*/
|
124
|
+
reversed(): Path;
|
93
125
|
private getEndPoint;
|
94
126
|
/**
|
95
127
|
* Like {@link closedRoughlyIntersects} except takes stroke width into account.
|
@@ -103,6 +135,8 @@ export declare class Path {
|
|
103
135
|
*/
|
104
136
|
roughlyIntersects(rect: Rect2, strokeWidth?: number): boolean;
|
105
137
|
closedRoughlyIntersects(rect: Rect2): boolean;
|
138
|
+
/** @returns true if all points on this are equivalent to the points on `other` */
|
139
|
+
eq(other: Path, tolerance?: number): boolean;
|
106
140
|
/**
|
107
141
|
* Returns a path that outlines `rect`.
|
108
142
|
*
|
package/dist/cjs/shapes/Path.js
CHANGED
@@ -236,7 +236,7 @@ class Path {
|
|
236
236
|
for (const { part, distFn, bbox } of uncheckedDistFunctions) {
|
237
237
|
// Skip if impossible for the distance to the target to be lesser than
|
238
238
|
// the current minimum.
|
239
|
-
if (!bbox.grownBy(minDist).containsPoint(point)) {
|
239
|
+
if (isFinite(minDist) && !bbox.grownBy(minDist).containsPoint(point)) {
|
240
240
|
continue;
|
241
241
|
}
|
242
242
|
const currentDist = distFn(point);
|
@@ -274,7 +274,7 @@ class Path {
|
|
274
274
|
});
|
275
275
|
const result = [];
|
276
276
|
const stoppingThreshold = strokeRadius / 1000;
|
277
|
-
// Returns the maximum
|
277
|
+
// Returns the maximum parameter value explored
|
278
278
|
const raymarchFrom = (startPoint,
|
279
279
|
// Direction to march in (multiplies line.direction)
|
280
280
|
directionMultiplier,
|
@@ -318,9 +318,14 @@ class Path {
|
|
318
318
|
if (lastPart && isOnLineSegment && Math.abs(lastDist) < stoppingThreshold) {
|
319
319
|
result.push({
|
320
320
|
point: currentPoint,
|
321
|
-
parameterValue: NaN,
|
321
|
+
parameterValue: NaN, // lastPart.nearestPointTo(currentPoint).parameterValue,
|
322
322
|
curve: lastPart,
|
323
|
+
curveIndex: this.geometry.indexOf(lastPart),
|
323
324
|
});
|
325
|
+
// Slightly increase the parameter value to prevent the same point from being
|
326
|
+
// added to the results twice.
|
327
|
+
const parameterIncrease = strokeRadius / 20 / line.length;
|
328
|
+
lastParameter += isFinite(parameterIncrease) ? parameterIncrease : 0;
|
324
329
|
}
|
325
330
|
return lastParameter;
|
326
331
|
};
|
@@ -353,14 +358,18 @@ class Path {
|
|
353
358
|
if (!line.bbox.intersects(this.bbox.grownBy(strokeRadius ?? 0))) {
|
354
359
|
return [];
|
355
360
|
}
|
361
|
+
let index = 0;
|
356
362
|
for (const part of this.geometry) {
|
357
|
-
const
|
358
|
-
|
363
|
+
const intersections = part.argIntersectsLineSegment(line);
|
364
|
+
for (const intersection of intersections) {
|
359
365
|
result.push({
|
360
366
|
curve: part,
|
361
|
-
|
367
|
+
curveIndex: index,
|
368
|
+
point: part.at(intersection),
|
369
|
+
parameterValue: intersection,
|
362
370
|
});
|
363
371
|
}
|
372
|
+
index++;
|
364
373
|
}
|
365
374
|
// If given a non-zero strokeWidth, attempt to raymarch.
|
366
375
|
// Even if raymarching, we need to collect starting points.
|
@@ -373,6 +382,42 @@ class Path {
|
|
373
382
|
}
|
374
383
|
return result;
|
375
384
|
}
|
385
|
+
/**
|
386
|
+
* @returns the nearest point on this path to the given `point`.
|
387
|
+
*
|
388
|
+
* @internal
|
389
|
+
* @beta
|
390
|
+
*/
|
391
|
+
nearestPointTo(point) {
|
392
|
+
// Find the closest point on this
|
393
|
+
let closestSquareDist = Infinity;
|
394
|
+
let closestPartIndex = 0;
|
395
|
+
let closestParameterValue = 0;
|
396
|
+
let closestPoint = this.startPoint;
|
397
|
+
for (let i = 0; i < this.geometry.length; i++) {
|
398
|
+
const current = this.geometry[i];
|
399
|
+
const nearestPoint = current.nearestPointTo(point);
|
400
|
+
const sqareDist = nearestPoint.point.squareDistanceTo(point);
|
401
|
+
if (i === 0 || sqareDist < closestSquareDist) {
|
402
|
+
closestPartIndex = i;
|
403
|
+
closestSquareDist = sqareDist;
|
404
|
+
closestParameterValue = nearestPoint.parameterValue;
|
405
|
+
closestPoint = nearestPoint.point;
|
406
|
+
}
|
407
|
+
}
|
408
|
+
return {
|
409
|
+
curve: this.geometry[closestPartIndex],
|
410
|
+
curveIndex: closestPartIndex,
|
411
|
+
parameterValue: closestParameterValue,
|
412
|
+
point: closestPoint,
|
413
|
+
};
|
414
|
+
}
|
415
|
+
at(index) {
|
416
|
+
return this.geometry[index.curveIndex].at(index.parameterValue);
|
417
|
+
}
|
418
|
+
tangentAt(index) {
|
419
|
+
return this.geometry[index.curveIndex].tangentAt(index.parameterValue);
|
420
|
+
}
|
376
421
|
static mapPathCommand(part, mapping) {
|
377
422
|
switch (part.kind) {
|
378
423
|
case PathCommandType.MoveTo:
|
@@ -416,18 +461,85 @@ class Path {
|
|
416
461
|
return this.mapPoints(point => affineTransfm.transformVec2(point));
|
417
462
|
}
|
418
463
|
// Creates a new path by joining [other] to the end of this path
|
419
|
-
union(other
|
464
|
+
union(other,
|
465
|
+
// allowReverse: true iff reversing other or this is permitted if it means
|
466
|
+
// no moveTo command is necessary when unioning the paths.
|
467
|
+
options = { allowReverse: true }) {
|
420
468
|
if (!other) {
|
421
469
|
return this;
|
422
470
|
}
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
471
|
+
const thisEnd = this.getEndPoint();
|
472
|
+
let newParts = [];
|
473
|
+
if (thisEnd.eq(other.startPoint)) {
|
474
|
+
newParts = this.parts.concat(other.parts);
|
475
|
+
}
|
476
|
+
else if (options.allowReverse && this.startPoint.eq(other.getEndPoint())) {
|
477
|
+
return other.union(this, { allowReverse: false });
|
478
|
+
}
|
479
|
+
else if (options.allowReverse && this.startPoint.eq(other.startPoint)) {
|
480
|
+
return this.union(other.reversed(), { allowReverse: false });
|
481
|
+
}
|
482
|
+
else {
|
483
|
+
newParts = [
|
484
|
+
...this.parts,
|
485
|
+
{
|
486
|
+
kind: PathCommandType.MoveTo,
|
487
|
+
point: other.startPoint,
|
488
|
+
},
|
489
|
+
...other.parts,
|
490
|
+
];
|
491
|
+
}
|
492
|
+
return new Path(this.startPoint, newParts);
|
493
|
+
}
|
494
|
+
/**
|
495
|
+
* @returns a version of this path with the direction reversed.
|
496
|
+
*
|
497
|
+
* Example:
|
498
|
+
* ```ts,runnable,console
|
499
|
+
* import {Path} from '@js-draw/math';
|
500
|
+
* console.log(Path.fromString('m0,0l1,1').reversed()); // -> M1,1 L0,0
|
501
|
+
* ```
|
502
|
+
*/
|
503
|
+
reversed() {
|
504
|
+
const newStart = this.getEndPoint();
|
505
|
+
const newParts = [];
|
506
|
+
let lastPoint = this.startPoint;
|
507
|
+
for (const part of this.parts) {
|
508
|
+
switch (part.kind) {
|
509
|
+
case PathCommandType.LineTo:
|
510
|
+
case PathCommandType.MoveTo:
|
511
|
+
newParts.push({
|
512
|
+
kind: part.kind,
|
513
|
+
point: lastPoint,
|
514
|
+
});
|
515
|
+
lastPoint = part.point;
|
516
|
+
break;
|
517
|
+
case PathCommandType.CubicBezierTo:
|
518
|
+
newParts.push({
|
519
|
+
kind: part.kind,
|
520
|
+
controlPoint1: part.controlPoint2,
|
521
|
+
controlPoint2: part.controlPoint1,
|
522
|
+
endPoint: lastPoint,
|
523
|
+
});
|
524
|
+
lastPoint = part.endPoint;
|
525
|
+
break;
|
526
|
+
case PathCommandType.QuadraticBezierTo:
|
527
|
+
newParts.push({
|
528
|
+
kind: part.kind,
|
529
|
+
controlPoint: part.controlPoint,
|
530
|
+
endPoint: lastPoint,
|
531
|
+
});
|
532
|
+
lastPoint = part.endPoint;
|
533
|
+
break;
|
534
|
+
default:
|
535
|
+
{
|
536
|
+
const exhaustivenessCheck = part;
|
537
|
+
return exhaustivenessCheck;
|
538
|
+
}
|
539
|
+
}
|
540
|
+
}
|
541
|
+
newParts.reverse();
|
542
|
+
return new Path(newStart, newParts);
|
431
543
|
}
|
432
544
|
getEndPoint() {
|
433
545
|
if (this.parts.length === 0) {
|
@@ -519,6 +631,52 @@ class Path {
|
|
519
631
|
// Even? Probably no intersection.
|
520
632
|
return false;
|
521
633
|
}
|
634
|
+
/** @returns true if all points on this are equivalent to the points on `other` */
|
635
|
+
eq(other, tolerance) {
|
636
|
+
if (other.parts.length !== this.parts.length) {
|
637
|
+
return false;
|
638
|
+
}
|
639
|
+
for (let i = 0; i < this.parts.length; i++) {
|
640
|
+
const part1 = this.parts[i];
|
641
|
+
const part2 = other.parts[i];
|
642
|
+
switch (part1.kind) {
|
643
|
+
case PathCommandType.LineTo:
|
644
|
+
case PathCommandType.MoveTo:
|
645
|
+
if (part1.kind !== part2.kind) {
|
646
|
+
return false;
|
647
|
+
}
|
648
|
+
else if (!part1.point.eq(part2.point, tolerance)) {
|
649
|
+
return false;
|
650
|
+
}
|
651
|
+
break;
|
652
|
+
case PathCommandType.CubicBezierTo:
|
653
|
+
if (part1.kind !== part2.kind) {
|
654
|
+
return false;
|
655
|
+
}
|
656
|
+
else if (!part1.controlPoint1.eq(part2.controlPoint1, tolerance)
|
657
|
+
|| !part1.controlPoint2.eq(part2.controlPoint2, tolerance)
|
658
|
+
|| !part1.endPoint.eq(part2.endPoint, tolerance)) {
|
659
|
+
return false;
|
660
|
+
}
|
661
|
+
break;
|
662
|
+
case PathCommandType.QuadraticBezierTo:
|
663
|
+
if (part1.kind !== part2.kind) {
|
664
|
+
return false;
|
665
|
+
}
|
666
|
+
else if (!part1.controlPoint.eq(part2.controlPoint, tolerance)
|
667
|
+
|| !part1.endPoint.eq(part2.endPoint, tolerance)) {
|
668
|
+
return false;
|
669
|
+
}
|
670
|
+
break;
|
671
|
+
default:
|
672
|
+
{
|
673
|
+
const exhaustivenessCheck = part1;
|
674
|
+
return exhaustivenessCheck;
|
675
|
+
}
|
676
|
+
}
|
677
|
+
}
|
678
|
+
return true;
|
679
|
+
}
|
522
680
|
/**
|
523
681
|
* Returns a path that outlines `rect`.
|
524
682
|
*
|
@@ -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;
|
@@ -3,29 +3,52 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
4
|
};
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
-
const
|
6
|
+
const Vec2_1 = require("../Vec2");
|
7
|
+
const Parameterized2DShape_1 = __importDefault(require("./Parameterized2DShape"));
|
7
8
|
const Rect2_1 = __importDefault(require("./Rect2"));
|
8
9
|
/**
|
9
10
|
* Like a {@link Point2}, but with additional functionality (e.g. SDF).
|
10
11
|
*
|
11
12
|
* Access the internal `Point2` using the `p` property.
|
12
13
|
*/
|
13
|
-
class PointShape2D extends
|
14
|
+
class PointShape2D extends Parameterized2DShape_1.default {
|
14
15
|
constructor(p) {
|
15
16
|
super();
|
16
17
|
this.p = p;
|
17
18
|
}
|
18
19
|
signedDistance(point) {
|
19
|
-
return this.p.
|
20
|
+
return this.p.distanceTo(point);
|
20
21
|
}
|
21
|
-
|
22
|
+
argIntersectsLineSegment(lineSegment, epsilon) {
|
22
23
|
if (lineSegment.containsPoint(this.p, epsilon)) {
|
23
|
-
return [
|
24
|
+
return [0];
|
24
25
|
}
|
25
26
|
return [];
|
26
27
|
}
|
27
28
|
getTightBoundingBox() {
|
28
29
|
return new Rect2_1.default(this.p.x, this.p.y, 0, 0);
|
29
30
|
}
|
31
|
+
at(_t) {
|
32
|
+
return this.p;
|
33
|
+
}
|
34
|
+
/**
|
35
|
+
* Returns an arbitrary unit-length vector.
|
36
|
+
*/
|
37
|
+
normalAt(_t) {
|
38
|
+
// Return a vector that makes sense.
|
39
|
+
return Vec2_1.Vec2.unitY;
|
40
|
+
}
|
41
|
+
tangentAt(_t) {
|
42
|
+
return Vec2_1.Vec2.unitX;
|
43
|
+
}
|
44
|
+
splitAt(_t) {
|
45
|
+
return [this];
|
46
|
+
}
|
47
|
+
nearestPointTo(_point) {
|
48
|
+
return {
|
49
|
+
point: this.p,
|
50
|
+
parameterValue: 0,
|
51
|
+
};
|
52
|
+
}
|
30
53
|
}
|
31
54
|
exports.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;
|
@@ -31,10 +31,19 @@ class QuadraticBezier extends BezierJSWrapper_1.default {
|
|
31
31
|
static derivativeComponentAt(t, p0, p1, p2) {
|
32
32
|
return -2 * p0 + 2 * p1 + 2 * t * (p0 - 2 * p1 + p2);
|
33
33
|
}
|
34
|
+
static secondDerivativeComponentAt(t, p0, p1, p2) {
|
35
|
+
return 2 * (p0 - 2 * p1 + p2);
|
36
|
+
}
|
34
37
|
/**
|
35
38
|
* @returns the curve evaluated at `t`.
|
39
|
+
*
|
40
|
+
* `t` should be a number in `[0, 1]`.
|
36
41
|
*/
|
37
42
|
at(t) {
|
43
|
+
if (t === 0)
|
44
|
+
return this.p0;
|
45
|
+
if (t === 1)
|
46
|
+
return this.p2;
|
38
47
|
const p0 = this.p0;
|
39
48
|
const p1 = this.p1;
|
40
49
|
const p2 = this.p2;
|
@@ -46,6 +55,12 @@ class QuadraticBezier extends BezierJSWrapper_1.default {
|
|
46
55
|
const p2 = this.p2;
|
47
56
|
return Vec2_1.Vec2.of(QuadraticBezier.derivativeComponentAt(t, p0.x, p1.x, p2.x), QuadraticBezier.derivativeComponentAt(t, p0.y, p1.y, p2.y));
|
48
57
|
}
|
58
|
+
secondDerivativeAt(t) {
|
59
|
+
const p0 = this.p0;
|
60
|
+
const p1 = this.p1;
|
61
|
+
const p2 = this.p2;
|
62
|
+
return Vec2_1.Vec2.of(QuadraticBezier.secondDerivativeComponentAt(t, p0.x, p1.x, p2.x), QuadraticBezier.secondDerivativeComponentAt(t, p0.y, p1.y, p2.y));
|
63
|
+
}
|
49
64
|
normal(t) {
|
50
65
|
const tangent = this.derivativeAt(t);
|
51
66
|
return tangent.orthog().normalized();
|
@@ -106,10 +121,10 @@ class QuadraticBezier extends BezierJSWrapper_1.default {
|
|
106
121
|
}
|
107
122
|
const at1 = this.at(min1);
|
108
123
|
const at2 = this.at(min2);
|
109
|
-
const sqrDist1 = at1.
|
110
|
-
const sqrDist2 = at2.
|
111
|
-
const sqrDist3 = this.at(0).
|
112
|
-
const sqrDist4 = this.at(1).
|
124
|
+
const sqrDist1 = at1.squareDistanceTo(point);
|
125
|
+
const sqrDist2 = at2.squareDistanceTo(point);
|
126
|
+
const sqrDist3 = this.at(0).squareDistanceTo(point);
|
127
|
+
const sqrDist4 = this.at(1).squareDistanceTo(point);
|
113
128
|
return Math.sqrt(Math.min(sqrDist1, sqrDist2, sqrDist3, sqrDist4));
|
114
129
|
}
|
115
130
|
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;
|
package/dist/cjs/shapes/Rect2.js
CHANGED
@@ -44,6 +44,9 @@ class Rect2 extends Abstract2DShape_1.default {
|
|
44
44
|
&& this.x + this.w >= other.x + other.w
|
45
45
|
&& this.y + this.h >= other.y + other.h;
|
46
46
|
}
|
47
|
+
/**
|
48
|
+
* @returns true iff this and `other` overlap
|
49
|
+
*/
|
47
50
|
intersects(other) {
|
48
51
|
// Project along x/y axes.
|
49
52
|
const thisMinX = this.x;
|
@@ -130,7 +133,7 @@ class Rect2 extends Abstract2DShape_1.default {
|
|
130
133
|
let closest = null;
|
131
134
|
let closestDist = null;
|
132
135
|
for (const point of closestEdgePoints) {
|
133
|
-
const dist = point.
|
136
|
+
const dist = point.distanceTo(target);
|
134
137
|
if (closestDist === null || dist < closestDist) {
|
135
138
|
closest = point;
|
136
139
|
closestDist = dist;
|
package/dist/mjs/Vec3.d.ts
CHANGED
@@ -35,11 +35,31 @@ export declare class Vec3 {
|
|
35
35
|
length(): number;
|
36
36
|
magnitude(): number;
|
37
37
|
magnitudeSquared(): number;
|
38
|
+
/**
|
39
|
+
* Interpreting this vector as a point in ℝ^3, computes the square distance
|
40
|
+
* to another point, `p`.
|
41
|
+
*
|
42
|
+
* Equivalent to `.minus(p).magnitudeSquared()`.
|
43
|
+
*/
|
44
|
+
squareDistanceTo(p: Vec3): number;
|
45
|
+
/**
|
46
|
+
* Interpreting this vector as a point in ℝ³, returns the distance to the point
|
47
|
+
* `p`.
|
48
|
+
*
|
49
|
+
* Equivalent to `.minus(p).magnitude()`.
|
50
|
+
*/
|
51
|
+
distanceTo(p: Vec3): number;
|
38
52
|
/**
|
39
53
|
* Returns the entry of this with the greatest magnitude.
|
40
54
|
*
|
41
55
|
* In other words, returns $\max \{ |x| : x \in {\bf v} \}$, where ${\bf v}$ is the set of
|
42
56
|
* all entries of this vector.
|
57
|
+
*
|
58
|
+
* **Example**:
|
59
|
+
* ```ts,runnable,console
|
60
|
+
* import { Vec3 } from '@js-draw/math';
|
61
|
+
* console.log(Vec3.of(-1, -10, 8).maximumEntryMagnitude()); // -> 10
|
62
|
+
* ```
|
43
63
|
*/
|
44
64
|
maximumEntryMagnitude(): number;
|
45
65
|
/**
|
@@ -50,6 +70,7 @@ export declare class Vec3 {
|
|
50
70
|
* As such, observing that `Math.atan2(-0, -1)` $\approx -\pi$ and `Math.atan2(0, -1)`$\approx \pi$
|
51
71
|
* the resultant angle is in the range $[-\pi, pi]$.
|
52
72
|
*
|
73
|
+
* **Example**:
|
53
74
|
* ```ts,runnable,console
|
54
75
|
* import { Vec2 } from '@js-draw/math';
|
55
76
|
* console.log(Vec2.of(-1, -0).angle()); // atan2(-0, -1)
|
package/dist/mjs/Vec3.mjs
CHANGED
@@ -55,11 +55,38 @@ export class Vec3 {
|
|
55
55
|
magnitudeSquared() {
|
56
56
|
return this.dot(this);
|
57
57
|
}
|
58
|
+
/**
|
59
|
+
* Interpreting this vector as a point in ℝ^3, computes the square distance
|
60
|
+
* to another point, `p`.
|
61
|
+
*
|
62
|
+
* Equivalent to `.minus(p).magnitudeSquared()`.
|
63
|
+
*/
|
64
|
+
squareDistanceTo(p) {
|
65
|
+
const dx = this.x - p.x;
|
66
|
+
const dy = this.y - p.y;
|
67
|
+
const dz = this.z - p.z;
|
68
|
+
return dx * dx + dy * dy + dz * dz;
|
69
|
+
}
|
70
|
+
/**
|
71
|
+
* Interpreting this vector as a point in ℝ³, returns the distance to the point
|
72
|
+
* `p`.
|
73
|
+
*
|
74
|
+
* Equivalent to `.minus(p).magnitude()`.
|
75
|
+
*/
|
76
|
+
distanceTo(p) {
|
77
|
+
return Math.sqrt(this.squareDistanceTo(p));
|
78
|
+
}
|
58
79
|
/**
|
59
80
|
* Returns the entry of this with the greatest magnitude.
|
60
81
|
*
|
61
82
|
* In other words, returns $\max \{ |x| : x \in {\bf v} \}$, where ${\bf v}$ is the set of
|
62
83
|
* all entries of this vector.
|
84
|
+
*
|
85
|
+
* **Example**:
|
86
|
+
* ```ts,runnable,console
|
87
|
+
* import { Vec3 } from '@js-draw/math';
|
88
|
+
* console.log(Vec3.of(-1, -10, 8).maximumEntryMagnitude()); // -> 10
|
89
|
+
* ```
|
63
90
|
*/
|
64
91
|
maximumEntryMagnitude() {
|
65
92
|
return Math.max(Math.abs(this.x), Math.max(Math.abs(this.y), Math.abs(this.z)));
|
@@ -72,6 +99,7 @@ export class Vec3 {
|
|
72
99
|
* As such, observing that `Math.atan2(-0, -1)` $\approx -\pi$ and `Math.atan2(0, -1)`$\approx \pi$
|
73
100
|
* the resultant angle is in the range $[-\pi, pi]$.
|
74
101
|
*
|
102
|
+
* **Example**:
|
75
103
|
* ```ts,runnable,console
|
76
104
|
* import { Vec2 } from '@js-draw/math';
|
77
105
|
* console.log(Vec2.of(-1, -0).angle()); // atan2(-0, -1)
|
package/dist/mjs/lib.d.ts
CHANGED
@@ -17,7 +17,7 @@
|
|
17
17
|
* @packageDocumentation
|
18
18
|
*/
|
19
19
|
export { LineSegment2 } from './shapes/LineSegment2';
|
20
|
-
export { Path, PathCommandType, PathCommand, LinePathCommand, MoveToPathCommand, QuadraticBezierPathCommand, CubicBezierPathCommand, } from './shapes/Path';
|
20
|
+
export { Path, IntersectionResult as PathIntersectionResult, CurveIndexRecord as PathCurveIndex, PathCommandType, PathCommand, LinePathCommand, MoveToPathCommand, QuadraticBezierPathCommand, CubicBezierPathCommand, } from './shapes/Path';
|
21
21
|
export { Rect2 } from './shapes/Rect2';
|
22
22
|
export { QuadraticBezier } from './shapes/QuadraticBezier';
|
23
23
|
export { Abstract2DShape } from './shapes/Abstract2DShape';
|
@@ -38,6 +38,9 @@ export declare abstract class Abstract2DShape {
|
|
38
38
|
containsPoint(point: Point2, epsilon?: number): boolean;
|
39
39
|
/**
|
40
40
|
* Returns a bounding box that precisely fits the content of this shape.
|
41
|
+
*
|
42
|
+
* **Note**: This bounding box should aligned with the x/y axes. (Thus, it may be
|
43
|
+
* possible to find a tighter bounding box not axes-aligned).
|
41
44
|
*/
|
42
45
|
abstract getTightBoundingBox(): Rect2;
|
43
46
|
/**
|
@@ -1,21 +1,22 @@
|
|
1
1
|
import { Bezier } from 'bezier-js';
|
2
2
|
import { Point2, Vec2 } from '../Vec2';
|
3
|
-
import Abstract2DShape from './Abstract2DShape';
|
4
3
|
import LineSegment2 from './LineSegment2';
|
5
4
|
import Rect2 from './Rect2';
|
5
|
+
import Parameterized2DShape from './Parameterized2DShape';
|
6
6
|
/**
|
7
7
|
* A lazy-initializing wrapper around Bezier-js.
|
8
8
|
*
|
9
9
|
* Subclasses may override `at`, `derivativeAt`, and `normal` with functions
|
10
10
|
* that do not initialize a `bezier-js` `Bezier`.
|
11
11
|
*
|
12
|
-
* Do not use this class directly
|
12
|
+
* **Do not use this class directly.** It may be removed/replaced in a future release.
|
13
13
|
* @internal
|
14
14
|
*/
|
15
|
-
declare abstract class BezierJSWrapper extends
|
15
|
+
export declare abstract class BezierJSWrapper extends Parameterized2DShape {
|
16
16
|
#private;
|
17
|
+
protected constructor(bezierJsBezier?: Bezier);
|
17
18
|
/** Returns the start, control points, and end point of this Bézier. */
|
18
|
-
abstract getPoints(): Point2[];
|
19
|
+
abstract getPoints(): readonly Point2[];
|
19
20
|
protected getBezier(): Bezier;
|
20
21
|
signedDistance(point: Point2): number;
|
21
22
|
/**
|
@@ -29,8 +30,17 @@ declare abstract class BezierJSWrapper extends Abstract2DShape {
|
|
29
30
|
*/
|
30
31
|
at(t: number): Point2;
|
31
32
|
derivativeAt(t: number): Point2;
|
33
|
+
secondDerivativeAt(t: number): Point2;
|
32
34
|
normal(t: number): Vec2;
|
35
|
+
normalAt(t: number): Vec2;
|
36
|
+
tangentAt(t: number): Vec2;
|
33
37
|
getTightBoundingBox(): Rect2;
|
34
|
-
|
38
|
+
argIntersectsLineSegment(line: LineSegment2): number[];
|
39
|
+
splitAt(t: number): [BezierJSWrapper] | [BezierJSWrapper, BezierJSWrapper];
|
40
|
+
nearestPointTo(point: Point2): {
|
41
|
+
parameterValue: number;
|
42
|
+
point: import("../Vec3").Vec3;
|
43
|
+
};
|
44
|
+
toString(): string;
|
35
45
|
}
|
36
46
|
export default BezierJSWrapper;
|