@js-draw/math 1.11.1 → 1.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. package/dist/cjs/Vec3.d.ts +21 -0
  2. package/dist/cjs/Vec3.js +28 -0
  3. package/dist/cjs/lib.d.ts +2 -2
  4. package/dist/cjs/lib.js +16 -3
  5. package/dist/cjs/rounding/cleanUpNumber.d.ts +3 -0
  6. package/dist/cjs/rounding/cleanUpNumber.js +35 -0
  7. package/dist/cjs/rounding/constants.d.ts +1 -0
  8. package/dist/cjs/rounding/constants.js +4 -0
  9. package/dist/cjs/rounding/getLenAfterDecimal.d.ts +10 -0
  10. package/dist/cjs/rounding/getLenAfterDecimal.js +30 -0
  11. package/dist/cjs/rounding/lib.d.ts +1 -0
  12. package/dist/cjs/rounding/lib.js +5 -0
  13. package/dist/cjs/{rounding.d.ts → rounding/toRoundedString.d.ts} +1 -3
  14. package/dist/cjs/rounding/toRoundedString.js +54 -0
  15. package/dist/cjs/rounding/toStringOfSamePrecision.d.ts +2 -0
  16. package/dist/cjs/rounding/toStringOfSamePrecision.js +58 -0
  17. package/dist/cjs/rounding/toStringOfSamePrecision.test.d.ts +1 -0
  18. package/dist/cjs/shapes/Abstract2DShape.d.ts +3 -0
  19. package/dist/cjs/shapes/BezierJSWrapper.d.ts +15 -5
  20. package/dist/cjs/shapes/BezierJSWrapper.js +135 -18
  21. package/dist/cjs/shapes/LineSegment2.d.ts +34 -5
  22. package/dist/cjs/shapes/LineSegment2.js +63 -10
  23. package/dist/cjs/shapes/Parameterized2DShape.d.ts +31 -0
  24. package/dist/cjs/shapes/Parameterized2DShape.js +15 -0
  25. package/dist/cjs/shapes/Path.d.ts +40 -6
  26. package/dist/cjs/shapes/Path.js +181 -22
  27. package/dist/cjs/shapes/PointShape2D.d.ts +14 -3
  28. package/dist/cjs/shapes/PointShape2D.js +28 -5
  29. package/dist/cjs/shapes/QuadraticBezier.d.ts +4 -0
  30. package/dist/cjs/shapes/QuadraticBezier.js +19 -4
  31. package/dist/cjs/shapes/Rect2.d.ts +3 -0
  32. package/dist/cjs/shapes/Rect2.js +4 -1
  33. package/dist/mjs/Vec3.d.ts +21 -0
  34. package/dist/mjs/Vec3.mjs +28 -0
  35. package/dist/mjs/lib.d.ts +2 -2
  36. package/dist/mjs/lib.mjs +1 -1
  37. package/dist/mjs/rounding/cleanUpNumber.d.ts +3 -0
  38. package/dist/mjs/rounding/cleanUpNumber.mjs +31 -0
  39. package/dist/mjs/rounding/cleanUpNumber.test.d.ts +1 -0
  40. package/dist/mjs/rounding/constants.d.ts +1 -0
  41. package/dist/mjs/rounding/constants.mjs +1 -0
  42. package/dist/mjs/rounding/getLenAfterDecimal.d.ts +10 -0
  43. package/dist/mjs/rounding/getLenAfterDecimal.mjs +26 -0
  44. package/dist/mjs/rounding/lib.d.ts +1 -0
  45. package/dist/mjs/rounding/lib.mjs +1 -0
  46. package/dist/mjs/{rounding.d.ts → rounding/toRoundedString.d.ts} +1 -3
  47. package/dist/mjs/rounding/toRoundedString.mjs +47 -0
  48. package/dist/mjs/rounding/toRoundedString.test.d.ts +1 -0
  49. package/dist/mjs/rounding/toStringOfSamePrecision.d.ts +2 -0
  50. package/dist/mjs/rounding/toStringOfSamePrecision.mjs +51 -0
  51. package/dist/mjs/rounding/toStringOfSamePrecision.test.d.ts +1 -0
  52. package/dist/mjs/shapes/Abstract2DShape.d.ts +3 -0
  53. package/dist/mjs/shapes/BezierJSWrapper.d.ts +15 -5
  54. package/dist/mjs/shapes/BezierJSWrapper.mjs +133 -18
  55. package/dist/mjs/shapes/LineSegment2.d.ts +34 -5
  56. package/dist/mjs/shapes/LineSegment2.mjs +63 -10
  57. package/dist/mjs/shapes/Parameterized2DShape.d.ts +31 -0
  58. package/dist/mjs/shapes/Parameterized2DShape.mjs +8 -0
  59. package/dist/mjs/shapes/Path.d.ts +40 -6
  60. package/dist/mjs/shapes/Path.mjs +175 -16
  61. package/dist/mjs/shapes/PointShape2D.d.ts +14 -3
  62. package/dist/mjs/shapes/PointShape2D.mjs +28 -5
  63. package/dist/mjs/shapes/QuadraticBezier.d.ts +4 -0
  64. package/dist/mjs/shapes/QuadraticBezier.mjs +19 -4
  65. package/dist/mjs/shapes/Rect2.d.ts +3 -0
  66. package/dist/mjs/shapes/Rect2.mjs +4 -1
  67. package/package.json +5 -5
  68. package/src/Vec3.test.ts +26 -7
  69. package/src/Vec3.ts +30 -0
  70. package/src/lib.ts +3 -1
  71. package/src/rounding/cleanUpNumber.test.ts +15 -0
  72. package/src/rounding/cleanUpNumber.ts +38 -0
  73. package/src/rounding/constants.ts +3 -0
  74. package/src/rounding/getLenAfterDecimal.ts +29 -0
  75. package/src/rounding/lib.ts +2 -0
  76. package/src/rounding/toRoundedString.test.ts +32 -0
  77. package/src/rounding/toRoundedString.ts +57 -0
  78. package/src/rounding/toStringOfSamePrecision.test.ts +21 -0
  79. package/src/rounding/toStringOfSamePrecision.ts +63 -0
  80. package/src/shapes/Abstract2DShape.ts +3 -0
  81. package/src/shapes/BezierJSWrapper.ts +154 -14
  82. package/src/shapes/LineSegment2.test.ts +35 -1
  83. package/src/shapes/LineSegment2.ts +79 -11
  84. package/src/shapes/Parameterized2DShape.ts +39 -0
  85. package/src/shapes/Path.test.ts +63 -3
  86. package/src/shapes/Path.ts +211 -26
  87. package/src/shapes/PointShape2D.ts +33 -6
  88. package/src/shapes/QuadraticBezier.test.ts +48 -12
  89. package/src/shapes/QuadraticBezier.ts +23 -5
  90. package/src/shapes/Rect2.ts +4 -1
  91. package/dist/cjs/rounding.js +0 -146
  92. package/dist/mjs/rounding.mjs +0 -139
  93. package/src/rounding.test.ts +0 -65
  94. package/src/rounding.ts +0 -168
  95. /package/dist/cjs/{rounding.test.d.ts → rounding/cleanUpNumber.test.d.ts} +0 -0
  96. /package/dist/{mjs/rounding.test.d.ts → cjs/rounding/toRoundedString.test.d.ts} +0 -0
@@ -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 { toRoundedString } from './rounding';
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.toRoundedString = exports.Color4 = exports.Vec3 = exports.Vec2 = exports.Mat33 = exports.Abstract2DShape = exports.QuadraticBezier = exports.Rect2 = exports.PathCommandType = exports.Path = exports.LineSegment2 = void 0;
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
- var rounding_1 = require("./rounding");
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,3 @@
1
+ /** Cleans up stringified numbers */
2
+ export declare const cleanUpNumber: (text: string) => string;
3
+ export default cleanUpNumber;
@@ -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,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.numberRegex = void 0;
4
+ exports.numberRegex = /^([-]?)(\d*)[.](\d+)$/;
@@ -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 declare const getLenAfterDecimal: (numberAsString: string) => number;
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,2 @@
1
+ export declare const toStringOfSamePrecision: (num: number, ...references: string[]) => string;
2
+ export default toStringOfSamePrecision;
@@ -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;
@@ -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. It may be removed/replaced in a future release.
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 Abstract2DShape {
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
- intersectsLineSegment(line: LineSegment2): Point2[];
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. It may be removed/replaced in a future release.
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 Abstract2DShape_1.default {
32
- constructor() {
33
- super(...arguments);
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.getBezier().project(point.xy).d;
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
- intersectsLineSegment(line) {
86
+ argIntersectsLineSegment(line) {
74
87
  const bezier = this.getBezier();
75
- const intersectionPoints = bezier.intersects(line).map(t => {
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(bezier.get(t));
95
+ const point = Vec2_1.Vec2.ofXY(this.at(t));
83
96
  // Ensure that the intersection is on the line segment
84
- if (point.minus(line.p1).magnitude() > line.length
85
- || point.minus(line.p2).magnitude() > line.length) {
97
+ if (point.distanceTo(line.p1) > line.length
98
+ || point.distanceTo(line.p2) > line.length) {
86
99
  return null;
87
100
  }
88
- return point;
101
+ return t;
89
102
  }).filter(entry => entry !== null);
90
- return intersectionPoints;
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;