@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/cjs/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/cjs/Vec3.js
CHANGED
|
@@ -58,11 +58,38 @@ class Vec3 {
|
|
|
58
58
|
magnitudeSquared() {
|
|
59
59
|
return this.dot(this);
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Interpreting this vector as a point in ℝ^3, computes the square distance
|
|
63
|
+
* to another point, `p`.
|
|
64
|
+
*
|
|
65
|
+
* Equivalent to `.minus(p).magnitudeSquared()`.
|
|
66
|
+
*/
|
|
67
|
+
squareDistanceTo(p) {
|
|
68
|
+
const dx = this.x - p.x;
|
|
69
|
+
const dy = this.y - p.y;
|
|
70
|
+
const dz = this.z - p.z;
|
|
71
|
+
return dx * dx + dy * dy + dz * dz;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Interpreting this vector as a point in ℝ³, returns the distance to the point
|
|
75
|
+
* `p`.
|
|
76
|
+
*
|
|
77
|
+
* Equivalent to `.minus(p).magnitude()`.
|
|
78
|
+
*/
|
|
79
|
+
distanceTo(p) {
|
|
80
|
+
return Math.sqrt(this.squareDistanceTo(p));
|
|
81
|
+
}
|
|
61
82
|
/**
|
|
62
83
|
* Returns the entry of this with the greatest magnitude.
|
|
63
84
|
*
|
|
64
85
|
* In other words, returns $\max \{ |x| : x \in {\bf v} \}$, where ${\bf v}$ is the set of
|
|
65
86
|
* all entries of this vector.
|
|
87
|
+
*
|
|
88
|
+
* **Example**:
|
|
89
|
+
* ```ts,runnable,console
|
|
90
|
+
* import { Vec3 } from '@js-draw/math';
|
|
91
|
+
* console.log(Vec3.of(-1, -10, 8).maximumEntryMagnitude()); // -> 10
|
|
92
|
+
* ```
|
|
66
93
|
*/
|
|
67
94
|
maximumEntryMagnitude() {
|
|
68
95
|
return Math.max(Math.abs(this.x), Math.max(Math.abs(this.y), Math.abs(this.z)));
|
|
@@ -75,6 +102,7 @@ class Vec3 {
|
|
|
75
102
|
* As such, observing that `Math.atan2(-0, -1)` $\approx -\pi$ and `Math.atan2(0, -1)`$\approx \pi$
|
|
76
103
|
* the resultant angle is in the range $[-\pi, pi]$.
|
|
77
104
|
*
|
|
105
|
+
* **Example**:
|
|
78
106
|
* ```ts,runnable,console
|
|
79
107
|
* import { Vec2 } from '@js-draw/math';
|
|
80
108
|
* console.log(Vec2.of(-1, -0).angle()); // atan2(-0, -1)
|
package/dist/cjs/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';
|
|
@@ -25,4 +25,4 @@ export { Mat33, Mat33Array } from './Mat33';
|
|
|
25
25
|
export { Point2, Vec2 } from './Vec2';
|
|
26
26
|
export { Vec3 } from './Vec3';
|
|
27
27
|
export { Color4 } from './Color4';
|
|
28
|
-
export
|
|
28
|
+
export * from './rounding/lib';
|
package/dist/cjs/lib.js
CHANGED
|
@@ -17,8 +17,22 @@
|
|
|
17
17
|
*
|
|
18
18
|
* @packageDocumentation
|
|
19
19
|
*/
|
|
20
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
23
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
24
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
25
|
+
}
|
|
26
|
+
Object.defineProperty(o, k2, desc);
|
|
27
|
+
}) : (function(o, m, k, k2) {
|
|
28
|
+
if (k2 === undefined) k2 = k;
|
|
29
|
+
o[k2] = m[k];
|
|
30
|
+
}));
|
|
31
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
32
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
33
|
+
};
|
|
20
34
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.
|
|
35
|
+
exports.Color4 = exports.Vec3 = exports.Vec2 = exports.Mat33 = exports.Abstract2DShape = exports.QuadraticBezier = exports.Rect2 = exports.PathCommandType = exports.Path = exports.LineSegment2 = void 0;
|
|
22
36
|
var LineSegment2_1 = require("./shapes/LineSegment2");
|
|
23
37
|
Object.defineProperty(exports, "LineSegment2", { enumerable: true, get: function () { return LineSegment2_1.LineSegment2; } });
|
|
24
38
|
var Path_1 = require("./shapes/Path");
|
|
@@ -38,7 +52,6 @@ var Vec3_1 = require("./Vec3");
|
|
|
38
52
|
Object.defineProperty(exports, "Vec3", { enumerable: true, get: function () { return Vec3_1.Vec3; } });
|
|
39
53
|
var Color4_1 = require("./Color4");
|
|
40
54
|
Object.defineProperty(exports, "Color4", { enumerable: true, get: function () { return Color4_1.Color4; } });
|
|
41
|
-
|
|
42
|
-
Object.defineProperty(exports, "toRoundedString", { enumerable: true, get: function () { return rounding_1.toRoundedString; } });
|
|
55
|
+
__exportStar(require("./rounding/lib"), exports);
|
|
43
56
|
// Note: All above exports cannot use `export { default as ... } from "..."` because this
|
|
44
57
|
// breaks TypeDoc -- TypeDoc otherwise labels any imports of these classes as `default`.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cleanUpNumber = void 0;
|
|
4
|
+
/** Cleans up stringified numbers */
|
|
5
|
+
const cleanUpNumber = (text) => {
|
|
6
|
+
// Regular expression substitions can be somewhat expensive. Only do them
|
|
7
|
+
// if necessary.
|
|
8
|
+
if (text.indexOf('e') > 0) {
|
|
9
|
+
// Round to zero.
|
|
10
|
+
if (text.match(/[eE][-]\d{2,}$/)) {
|
|
11
|
+
return '0';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const lastChar = text.charAt(text.length - 1);
|
|
15
|
+
if (lastChar === '0' || lastChar === '.') {
|
|
16
|
+
// Remove trailing zeroes
|
|
17
|
+
text = text.replace(/([.]\d*[^0]+)0+$/, '$1');
|
|
18
|
+
text = text.replace(/[.]0+$/, '.');
|
|
19
|
+
// Remove trailing period
|
|
20
|
+
text = text.replace(/[.]$/, '');
|
|
21
|
+
}
|
|
22
|
+
const firstChar = text.charAt(0);
|
|
23
|
+
if (firstChar === '0' || firstChar === '-') {
|
|
24
|
+
// Remove unnecessary leading zeroes.
|
|
25
|
+
text = text.replace(/^(0+)[.]/, '.');
|
|
26
|
+
text = text.replace(/^-(0+)[.]/, '-.');
|
|
27
|
+
text = text.replace(/^(-?)0+$/, '$10');
|
|
28
|
+
}
|
|
29
|
+
if (text === '-0') {
|
|
30
|
+
return '0';
|
|
31
|
+
}
|
|
32
|
+
return text;
|
|
33
|
+
};
|
|
34
|
+
exports.cleanUpNumber = cleanUpNumber;
|
|
35
|
+
exports.default = exports.cleanUpNumber;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const numberRegex: RegExp;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the length of `numberAsString` after a decimal point.
|
|
3
|
+
*
|
|
4
|
+
* For example,
|
|
5
|
+
* ```ts
|
|
6
|
+
* getLenAfterDecimal('1.001') // -> 3
|
|
7
|
+
* ```
|
|
8
|
+
*/
|
|
9
|
+
export declare const getLenAfterDecimal: (numberAsString: string) => number;
|
|
10
|
+
export default getLenAfterDecimal;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getLenAfterDecimal = void 0;
|
|
4
|
+
const constants_1 = require("./constants");
|
|
5
|
+
/**
|
|
6
|
+
* Returns the length of `numberAsString` after a decimal point.
|
|
7
|
+
*
|
|
8
|
+
* For example,
|
|
9
|
+
* ```ts
|
|
10
|
+
* getLenAfterDecimal('1.001') // -> 3
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
const getLenAfterDecimal = (numberAsString) => {
|
|
14
|
+
const numberMatch = constants_1.numberRegex.exec(numberAsString);
|
|
15
|
+
if (!numberMatch) {
|
|
16
|
+
// If not a match, either the number is exponential notation (or is something
|
|
17
|
+
// like NaN or Infinity)
|
|
18
|
+
if (numberAsString.search(/[eE]/) !== -1 || /^[a-zA-Z]+$/.exec(numberAsString)) {
|
|
19
|
+
return -1;
|
|
20
|
+
// Or it has no decimal point
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const afterDecimalLen = numberMatch[3].length;
|
|
27
|
+
return afterDecimalLen;
|
|
28
|
+
};
|
|
29
|
+
exports.getLenAfterDecimal = getLenAfterDecimal;
|
|
30
|
+
exports.default = exports.getLenAfterDecimal;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { toRoundedString } from './toRoundedString';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toRoundedString = void 0;
|
|
4
|
+
var toRoundedString_1 = require("./toRoundedString");
|
|
5
|
+
Object.defineProperty(exports, "toRoundedString", { enumerable: true, get: function () { return toRoundedString_1.toRoundedString; } });
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export declare const cleanUpNumber: (text: string) => string;
|
|
2
1
|
/**
|
|
3
2
|
* Converts `num` to a string, removing trailing digits that were likely caused by
|
|
4
3
|
* precision errors.
|
|
@@ -11,5 +10,4 @@ export declare const cleanUpNumber: (text: string) => string;
|
|
|
11
10
|
* ```
|
|
12
11
|
*/
|
|
13
12
|
export declare const toRoundedString: (num: number) => string;
|
|
14
|
-
export
|
|
15
|
-
export declare const toStringOfSamePrecision: (num: number, ...references: string[]) => string;
|
|
13
|
+
export default toRoundedString;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.toRoundedString = void 0;
|
|
7
|
+
const cleanUpNumber_1 = __importDefault(require("./cleanUpNumber"));
|
|
8
|
+
/**
|
|
9
|
+
* Converts `num` to a string, removing trailing digits that were likely caused by
|
|
10
|
+
* precision errors.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts,runnable,console
|
|
14
|
+
* import { toRoundedString } from '@js-draw/math';
|
|
15
|
+
*
|
|
16
|
+
* console.log('Rounded: ', toRoundedString(1.000000011));
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
const toRoundedString = (num) => {
|
|
20
|
+
// Try to remove rounding errors. If the number ends in at least three/four zeroes
|
|
21
|
+
// (or nines) just one or two digits, it's probably a rounding error.
|
|
22
|
+
const fixRoundingUpExp = /^([-]?\d*\.\d{3,})0{4,}\d{1,4}$/;
|
|
23
|
+
const hasRoundingDownExp = /^([-]?)(\d*)\.(\d{3,}9{4,})\d{1,4}$/;
|
|
24
|
+
let text = num.toString(10);
|
|
25
|
+
if (text.indexOf('.') === -1) {
|
|
26
|
+
return text;
|
|
27
|
+
}
|
|
28
|
+
const roundingDownMatch = hasRoundingDownExp.exec(text);
|
|
29
|
+
if (roundingDownMatch) {
|
|
30
|
+
const negativeSign = roundingDownMatch[1];
|
|
31
|
+
const postDecimalString = roundingDownMatch[3];
|
|
32
|
+
const lastDigit = parseInt(postDecimalString.charAt(postDecimalString.length - 1), 10);
|
|
33
|
+
const postDecimal = parseInt(postDecimalString, 10);
|
|
34
|
+
const preDecimal = parseInt(roundingDownMatch[2], 10);
|
|
35
|
+
const origPostDecimalString = roundingDownMatch[3];
|
|
36
|
+
let newPostDecimal = (postDecimal + 10 - lastDigit).toString();
|
|
37
|
+
let carry = 0;
|
|
38
|
+
if (newPostDecimal.length > postDecimal.toString().length) {
|
|
39
|
+
// Left-shift
|
|
40
|
+
newPostDecimal = newPostDecimal.substring(1);
|
|
41
|
+
carry = 1;
|
|
42
|
+
}
|
|
43
|
+
// parseInt(...).toString() removes leading zeroes. Add them back.
|
|
44
|
+
while (newPostDecimal.length < origPostDecimalString.length) {
|
|
45
|
+
newPostDecimal = carry.toString(10) + newPostDecimal;
|
|
46
|
+
carry = 0;
|
|
47
|
+
}
|
|
48
|
+
text = `${negativeSign + (preDecimal + carry).toString()}.${newPostDecimal}`;
|
|
49
|
+
}
|
|
50
|
+
text = text.replace(fixRoundingUpExp, '$1');
|
|
51
|
+
return (0, cleanUpNumber_1.default)(text);
|
|
52
|
+
};
|
|
53
|
+
exports.toRoundedString = toRoundedString;
|
|
54
|
+
exports.default = exports.toRoundedString;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.toStringOfSamePrecision = void 0;
|
|
7
|
+
const cleanUpNumber_1 = __importDefault(require("./cleanUpNumber"));
|
|
8
|
+
const constants_1 = require("./constants");
|
|
9
|
+
const getLenAfterDecimal_1 = __importDefault(require("./getLenAfterDecimal"));
|
|
10
|
+
const toRoundedString_1 = __importDefault(require("./toRoundedString"));
|
|
11
|
+
// [reference] should be a string representation of a base-10 number (no exponential (e.g. 10e10))
|
|
12
|
+
const toStringOfSamePrecision = (num, ...references) => {
|
|
13
|
+
const text = num.toString(10);
|
|
14
|
+
const textMatch = constants_1.numberRegex.exec(text);
|
|
15
|
+
if (!textMatch) {
|
|
16
|
+
return text;
|
|
17
|
+
}
|
|
18
|
+
let decimalPlaces = -1;
|
|
19
|
+
for (const reference of references) {
|
|
20
|
+
decimalPlaces = Math.max((0, getLenAfterDecimal_1.default)(reference), decimalPlaces);
|
|
21
|
+
}
|
|
22
|
+
if (decimalPlaces === -1) {
|
|
23
|
+
return (0, toRoundedString_1.default)(num);
|
|
24
|
+
}
|
|
25
|
+
// Make text's after decimal length match [afterDecimalLen].
|
|
26
|
+
let postDecimal = textMatch[3].substring(0, decimalPlaces);
|
|
27
|
+
let preDecimal = textMatch[2];
|
|
28
|
+
const nextDigit = textMatch[3].charAt(decimalPlaces);
|
|
29
|
+
if (nextDigit !== '') {
|
|
30
|
+
const asNumber = parseInt(nextDigit, 10);
|
|
31
|
+
if (asNumber >= 5) {
|
|
32
|
+
// Don't attempt to parseInt() an empty string.
|
|
33
|
+
if (postDecimal.length > 0) {
|
|
34
|
+
const leadingZeroMatch = /^(0+)(\d*)$/.exec(postDecimal);
|
|
35
|
+
let leadingZeroes = '';
|
|
36
|
+
let postLeading = postDecimal;
|
|
37
|
+
if (leadingZeroMatch) {
|
|
38
|
+
leadingZeroes = leadingZeroMatch[1];
|
|
39
|
+
postLeading = leadingZeroMatch[2];
|
|
40
|
+
}
|
|
41
|
+
postDecimal = (parseInt(postDecimal) + 1).toString();
|
|
42
|
+
// If postDecimal got longer, remove leading zeroes if possible
|
|
43
|
+
if (postDecimal.length > postLeading.length && leadingZeroes.length > 0) {
|
|
44
|
+
leadingZeroes = leadingZeroes.substring(1);
|
|
45
|
+
}
|
|
46
|
+
postDecimal = leadingZeroes + postDecimal;
|
|
47
|
+
}
|
|
48
|
+
if (postDecimal.length === 0 || postDecimal.length > decimalPlaces) {
|
|
49
|
+
preDecimal = (parseInt(preDecimal) + 1).toString();
|
|
50
|
+
postDecimal = postDecimal.substring(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const negativeSign = textMatch[1];
|
|
55
|
+
return (0, cleanUpNumber_1.default)(`${negativeSign}${preDecimal}.${postDecimal}`);
|
|
56
|
+
};
|
|
57
|
+
exports.toStringOfSamePrecision = toStringOfSamePrecision;
|
|
58
|
+
exports.default = exports.toStringOfSamePrecision;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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;
|
|
@@ -1,37 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
3
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
4
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
5
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
6
|
-
};
|
|
7
2
|
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
8
3
|
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
9
4
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
10
5
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
11
6
|
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
12
7
|
};
|
|
8
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
9
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
10
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
|
+
};
|
|
13
13
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
14
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
15
|
};
|
|
16
16
|
var _BezierJSWrapper_bezierJs;
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.BezierJSWrapper = void 0;
|
|
18
19
|
const bezier_js_1 = require("bezier-js");
|
|
19
20
|
const Vec2_1 = require("../Vec2");
|
|
20
|
-
const Abstract2DShape_1 = __importDefault(require("./Abstract2DShape"));
|
|
21
21
|
const Rect2_1 = __importDefault(require("./Rect2"));
|
|
22
|
+
const Parameterized2DShape_1 = __importDefault(require("./Parameterized2DShape"));
|
|
22
23
|
/**
|
|
23
24
|
* A lazy-initializing wrapper around Bezier-js.
|
|
24
25
|
*
|
|
25
26
|
* Subclasses may override `at`, `derivativeAt`, and `normal` with functions
|
|
26
27
|
* that do not initialize a `bezier-js` `Bezier`.
|
|
27
28
|
*
|
|
28
|
-
* Do not use this class directly
|
|
29
|
+
* **Do not use this class directly.** It may be removed/replaced in a future release.
|
|
29
30
|
* @internal
|
|
30
31
|
*/
|
|
31
|
-
class BezierJSWrapper extends
|
|
32
|
-
constructor() {
|
|
33
|
-
super(
|
|
32
|
+
class BezierJSWrapper extends Parameterized2DShape_1.default {
|
|
33
|
+
constructor(bezierJsBezier) {
|
|
34
|
+
super();
|
|
34
35
|
_BezierJSWrapper_bezierJs.set(this, null);
|
|
36
|
+
if (bezierJsBezier) {
|
|
37
|
+
__classPrivateFieldSet(this, _BezierJSWrapper_bezierJs, bezierJsBezier, "f");
|
|
38
|
+
}
|
|
35
39
|
}
|
|
36
40
|
getBezier() {
|
|
37
41
|
if (!__classPrivateFieldGet(this, _BezierJSWrapper_bezierJs, "f")) {
|
|
@@ -41,7 +45,7 @@ class BezierJSWrapper extends Abstract2DShape_1.default {
|
|
|
41
45
|
}
|
|
42
46
|
signedDistance(point) {
|
|
43
47
|
// .d: Distance
|
|
44
|
-
return this.
|
|
48
|
+
return this.nearestPointTo(point).point.distanceTo(point);
|
|
45
49
|
}
|
|
46
50
|
/**
|
|
47
51
|
* @returns the (more) exact distance from `point` to this.
|
|
@@ -61,34 +65,147 @@ class BezierJSWrapper extends Abstract2DShape_1.default {
|
|
|
61
65
|
derivativeAt(t) {
|
|
62
66
|
return Vec2_1.Vec2.ofXY(this.getBezier().derivative(t));
|
|
63
67
|
}
|
|
68
|
+
secondDerivativeAt(t) {
|
|
69
|
+
return Vec2_1.Vec2.ofXY(this.getBezier().dderivative(t));
|
|
70
|
+
}
|
|
64
71
|
normal(t) {
|
|
65
72
|
return Vec2_1.Vec2.ofXY(this.getBezier().normal(t));
|
|
66
73
|
}
|
|
74
|
+
normalAt(t) {
|
|
75
|
+
return this.normal(t);
|
|
76
|
+
}
|
|
77
|
+
tangentAt(t) {
|
|
78
|
+
return this.derivativeAt(t).normalized();
|
|
79
|
+
}
|
|
67
80
|
getTightBoundingBox() {
|
|
68
81
|
const bbox = this.getBezier().bbox();
|
|
69
82
|
const width = bbox.x.max - bbox.x.min;
|
|
70
83
|
const height = bbox.y.max - bbox.y.min;
|
|
71
84
|
return new Rect2_1.default(bbox.x.min, bbox.y.min, width, height);
|
|
72
85
|
}
|
|
73
|
-
|
|
86
|
+
argIntersectsLineSegment(line) {
|
|
74
87
|
const bezier = this.getBezier();
|
|
75
|
-
|
|
88
|
+
return bezier.intersects(line).map(t => {
|
|
76
89
|
// We're using the .intersects(line) function, which is documented
|
|
77
90
|
// to always return numbers. However, to satisfy the type checker (and
|
|
78
91
|
// possibly improperly-defined types),
|
|
79
92
|
if (typeof t === 'string') {
|
|
80
93
|
t = parseFloat(t);
|
|
81
94
|
}
|
|
82
|
-
const point = Vec2_1.Vec2.ofXY(
|
|
95
|
+
const point = Vec2_1.Vec2.ofXY(this.at(t));
|
|
83
96
|
// Ensure that the intersection is on the line segment
|
|
84
|
-
if (point.
|
|
85
|
-
|| point.
|
|
97
|
+
if (point.distanceTo(line.p1) > line.length
|
|
98
|
+
|| point.distanceTo(line.p2) > line.length) {
|
|
86
99
|
return null;
|
|
87
100
|
}
|
|
88
|
-
return
|
|
101
|
+
return t;
|
|
89
102
|
}).filter(entry => entry !== null);
|
|
90
|
-
|
|
103
|
+
}
|
|
104
|
+
splitAt(t) {
|
|
105
|
+
if (t <= 0 || t >= 1) {
|
|
106
|
+
return [this];
|
|
107
|
+
}
|
|
108
|
+
const bezier = this.getBezier();
|
|
109
|
+
const split = bezier.split(t);
|
|
110
|
+
return [
|
|
111
|
+
new BezierJSWrapperImpl(split.left.points.map(point => Vec2_1.Vec2.ofXY(point)), split.left),
|
|
112
|
+
new BezierJSWrapperImpl(split.right.points.map(point => Vec2_1.Vec2.ofXY(point)), split.right),
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
nearestPointTo(point) {
|
|
116
|
+
// One implementation could be similar to this:
|
|
117
|
+
// const projection = this.getBezier().project(point);
|
|
118
|
+
// return {
|
|
119
|
+
// point: Vec2.ofXY(projection),
|
|
120
|
+
// parameterValue: projection.t!,
|
|
121
|
+
// };
|
|
122
|
+
// However, Bezier-js is rather impercise (and relies on a lookup table).
|
|
123
|
+
// Thus, we instead use Newton's Method:
|
|
124
|
+
// We want to find t such that f(t) = |B(t) - p|² is minimized.
|
|
125
|
+
// Expanding,
|
|
126
|
+
// f(t) = (Bₓ(t) - pₓ)² + (Bᵧ(t) - pᵧ)²
|
|
127
|
+
// ⇒ f'(t) = Dₜ(Bₓ(t) - pₓ)² + Dₜ(Bᵧ(t) - pᵧ)²
|
|
128
|
+
// ⇒ f'(t) = 2(Bₓ(t) - pₓ)(Bₓ'(t)) + 2(Bᵧ(t) - pᵧ)(Bᵧ'(t))
|
|
129
|
+
// = 2Bₓ(t)Bₓ'(t) - 2pₓBₓ'(t) + 2Bᵧ(t)Bᵧ'(t) - 2pᵧBᵧ'(t)
|
|
130
|
+
// ⇒ f''(t)= 2Bₓ'(t)Bₓ'(t) + 2Bₓ(t)Bₓ''(t) - 2pₓBₓ''(t) + 2Bᵧ'(t)Bᵧ'(t)
|
|
131
|
+
// + 2Bᵧ(t)Bᵧ''(t) - 2pᵧBᵧ''(t)
|
|
132
|
+
// Because f'(t) = 0 at relative extrema, we can use Newton's Method
|
|
133
|
+
// to improve on an initial guess.
|
|
134
|
+
const sqrDistAt = (t) => point.squareDistanceTo(this.at(t));
|
|
135
|
+
const yIntercept = sqrDistAt(0);
|
|
136
|
+
let t = 0;
|
|
137
|
+
let minSqrDist = yIntercept;
|
|
138
|
+
// Start by testing a few points:
|
|
139
|
+
const pointsToTest = 4;
|
|
140
|
+
for (let i = 0; i < pointsToTest; i++) {
|
|
141
|
+
const testT = i / (pointsToTest - 1);
|
|
142
|
+
const testMinSqrDist = sqrDistAt(testT);
|
|
143
|
+
if (testMinSqrDist < minSqrDist) {
|
|
144
|
+
t = testT;
|
|
145
|
+
minSqrDist = testMinSqrDist;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// To use Newton's Method, we need to evaluate the second derivative of the distance
|
|
149
|
+
// function:
|
|
150
|
+
const secondDerivativeAt = (t) => {
|
|
151
|
+
// f''(t) = 2Bₓ'(t)Bₓ'(t) + 2Bₓ(t)Bₓ''(t) - 2pₓBₓ''(t)
|
|
152
|
+
// + 2Bᵧ'(t)Bᵧ'(t) + 2Bᵧ(t)Bᵧ''(t) - 2pᵧBᵧ''(t)
|
|
153
|
+
const b = this.at(t);
|
|
154
|
+
const bPrime = this.derivativeAt(t);
|
|
155
|
+
const bPrimePrime = this.secondDerivativeAt(t);
|
|
156
|
+
return (2 * bPrime.x * bPrime.x + 2 * b.x * bPrimePrime.x - 2 * point.x * bPrimePrime.x
|
|
157
|
+
+ 2 * bPrime.y * bPrime.y + 2 * b.y * bPrimePrime.y - 2 * point.y * bPrimePrime.y);
|
|
158
|
+
};
|
|
159
|
+
// Because we're zeroing f'(t), we also need to be able to compute it:
|
|
160
|
+
const derivativeAt = (t) => {
|
|
161
|
+
// f'(t) = 2Bₓ(t)Bₓ'(t) - 2pₓBₓ'(t) + 2Bᵧ(t)Bᵧ'(t) - 2pᵧBᵧ'(t)
|
|
162
|
+
const b = this.at(t);
|
|
163
|
+
const bPrime = this.derivativeAt(t);
|
|
164
|
+
return (2 * b.x * bPrime.x - 2 * point.x * bPrime.x
|
|
165
|
+
+ 2 * b.y * bPrime.y - 2 * point.y * bPrime.y);
|
|
166
|
+
};
|
|
167
|
+
const iterate = () => {
|
|
168
|
+
const slope = secondDerivativeAt(t);
|
|
169
|
+
// We intersect a line through the point on f'(t) at t with the x-axis:
|
|
170
|
+
// y = m(x - x₀) + y₀
|
|
171
|
+
// ⇒ x - x₀ = (y - y₀) / m
|
|
172
|
+
// ⇒ x = (y - y₀) / m + x₀
|
|
173
|
+
//
|
|
174
|
+
// Thus, when zeroed,
|
|
175
|
+
// tN = (0 - f'(t)) / m + t
|
|
176
|
+
const newT = (0 - derivativeAt(t)) / slope + t;
|
|
177
|
+
//const distDiff = sqrDistAt(newT) - sqrDistAt(t);
|
|
178
|
+
//console.assert(distDiff <= 0, `${-distDiff} >= 0`);
|
|
179
|
+
t = newT;
|
|
180
|
+
if (t > 1) {
|
|
181
|
+
t = 1;
|
|
182
|
+
}
|
|
183
|
+
else if (t < 0) {
|
|
184
|
+
t = 0;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
for (let i = 0; i < 12; i++) {
|
|
188
|
+
iterate();
|
|
189
|
+
}
|
|
190
|
+
return { parameterValue: t, point: this.at(t) };
|
|
191
|
+
}
|
|
192
|
+
toString() {
|
|
193
|
+
return `Bézier(${this.getPoints().map(point => point.toString()).join(', ')})`;
|
|
91
194
|
}
|
|
92
195
|
}
|
|
196
|
+
exports.BezierJSWrapper = BezierJSWrapper;
|
|
93
197
|
_BezierJSWrapper_bezierJs = new WeakMap();
|
|
198
|
+
/**
|
|
199
|
+
* Private concrete implementation of `BezierJSWrapper`, used by methods above that need to return a wrapper
|
|
200
|
+
* around a `Bezier`.
|
|
201
|
+
*/
|
|
202
|
+
class BezierJSWrapperImpl extends BezierJSWrapper {
|
|
203
|
+
constructor(controlPoints, curve) {
|
|
204
|
+
super(curve);
|
|
205
|
+
this.controlPoints = controlPoints;
|
|
206
|
+
}
|
|
207
|
+
getPoints() {
|
|
208
|
+
return this.controlPoints;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
94
211
|
exports.default = BezierJSWrapper;
|