@js-draw/math 1.21.3 → 1.23.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. package/build-config.json +1 -1
  2. package/dist/cjs/Color4.d.ts +24 -1
  3. package/dist/cjs/Color4.js +35 -3
  4. package/dist/cjs/Mat33.d.ts +21 -11
  5. package/dist/cjs/Mat33.js +28 -24
  6. package/dist/cjs/Vec3.d.ts +12 -3
  7. package/dist/cjs/Vec3.js +20 -9
  8. package/dist/cjs/lib.d.ts +3 -0
  9. package/dist/cjs/lib.js +3 -0
  10. package/dist/cjs/shapes/BezierJSWrapper.d.ts +2 -0
  11. package/dist/cjs/shapes/BezierJSWrapper.js +22 -13
  12. package/dist/cjs/shapes/LineSegment2.js +13 -17
  13. package/dist/cjs/shapes/Parameterized2DShape.js +1 -1
  14. package/dist/cjs/shapes/Path.d.ts +1 -0
  15. package/dist/cjs/shapes/Path.js +50 -47
  16. package/dist/cjs/shapes/QuadraticBezier.d.ts +19 -2
  17. package/dist/cjs/shapes/QuadraticBezier.js +26 -3
  18. package/dist/cjs/shapes/Rect2.d.ts +13 -0
  19. package/dist/cjs/shapes/Rect2.js +35 -16
  20. package/dist/cjs/shapes/Triangle.js +4 -5
  21. package/dist/cjs/utils/convexHull2Of.js +3 -3
  22. package/dist/mjs/Color4.d.ts +24 -1
  23. package/dist/mjs/Color4.mjs +35 -3
  24. package/dist/mjs/Mat33.d.ts +21 -11
  25. package/dist/mjs/Mat33.mjs +28 -24
  26. package/dist/mjs/Vec3.d.ts +12 -3
  27. package/dist/mjs/Vec3.mjs +20 -9
  28. package/dist/mjs/lib.d.ts +3 -0
  29. package/dist/mjs/lib.mjs +3 -0
  30. package/dist/mjs/shapes/BezierJSWrapper.d.ts +2 -0
  31. package/dist/mjs/shapes/BezierJSWrapper.mjs +22 -13
  32. package/dist/mjs/shapes/LineSegment2.mjs +13 -17
  33. package/dist/mjs/shapes/Parameterized2DShape.mjs +1 -1
  34. package/dist/mjs/shapes/Path.d.ts +1 -0
  35. package/dist/mjs/shapes/Path.mjs +50 -47
  36. package/dist/mjs/shapes/QuadraticBezier.d.ts +19 -2
  37. package/dist/mjs/shapes/QuadraticBezier.mjs +26 -3
  38. package/dist/mjs/shapes/Rect2.d.ts +13 -0
  39. package/dist/mjs/shapes/Rect2.mjs +35 -16
  40. package/dist/mjs/shapes/Triangle.mjs +4 -5
  41. package/dist/mjs/utils/convexHull2Of.mjs +3 -3
  42. package/dist-test/test_imports/test-require.cjs +1 -1
  43. package/package.json +3 -3
  44. package/src/Color4.test.ts +21 -21
  45. package/src/Color4.ts +61 -18
  46. package/src/Mat33.fromCSSMatrix.test.ts +32 -46
  47. package/src/Mat33.test.ts +64 -102
  48. package/src/Mat33.ts +81 -104
  49. package/src/Vec2.test.ts +3 -3
  50. package/src/Vec3.test.ts +2 -3
  51. package/src/Vec3.ts +46 -61
  52. package/src/lib.ts +3 -2
  53. package/src/polynomial/solveQuadratic.test.ts +39 -13
  54. package/src/polynomial/solveQuadratic.ts +5 -6
  55. package/src/rounding/cleanUpNumber.test.ts +1 -1
  56. package/src/rounding/constants.ts +1 -3
  57. package/src/rounding/getLenAfterDecimal.ts +1 -2
  58. package/src/rounding/lib.ts +1 -2
  59. package/src/rounding/toRoundedString.test.ts +1 -1
  60. package/src/rounding/toStringOfSamePrecision.test.ts +1 -2
  61. package/src/rounding/toStringOfSamePrecision.ts +1 -1
  62. package/src/shapes/BezierJSWrapper.ts +56 -37
  63. package/src/shapes/CubicBezier.ts +3 -3
  64. package/src/shapes/LineSegment2.test.ts +24 -17
  65. package/src/shapes/LineSegment2.ts +26 -29
  66. package/src/shapes/Parameterized2DShape.ts +5 -4
  67. package/src/shapes/Path.fromString.test.ts +5 -5
  68. package/src/shapes/Path.test.ts +122 -120
  69. package/src/shapes/Path.toString.test.ts +7 -7
  70. package/src/shapes/Path.ts +379 -352
  71. package/src/shapes/PointShape2D.ts +3 -3
  72. package/src/shapes/QuadraticBezier.test.ts +27 -21
  73. package/src/shapes/QuadraticBezier.ts +26 -11
  74. package/src/shapes/Rect2.test.ts +44 -75
  75. package/src/shapes/Rect2.ts +47 -35
  76. package/src/shapes/Triangle.test.ts +31 -29
  77. package/src/shapes/Triangle.ts +17 -18
  78. package/src/utils/convexHull2Of.test.ts +54 -15
  79. package/src/utils/convexHull2Of.ts +9 -7
  80. package/tsconfig.json +1 -3
  81. package/typedoc.json +2 -2
@@ -1,9 +1,8 @@
1
-
2
1
  import solveQuadratic from './solveQuadratic';
3
2
 
4
3
  describe('solveQuadratic', () => {
5
4
  it('should solve linear equations', () => {
6
- expect(solveQuadratic(0, 1, 2)).toMatchObject([ -2, -2 ]);
5
+ expect(solveQuadratic(0, 1, 2)).toMatchObject([-2, -2]);
7
6
  expect(solveQuadratic(0, 0, 2)[0]).toBeNaN();
8
7
  });
9
8
 
@@ -11,21 +10,48 @@ describe('solveQuadratic', () => {
11
10
  type TestCase = [[number, number, number], [number, number]];
12
11
 
13
12
  const testCases: TestCase[] = [
14
- [ [ 1, 0, 0 ], [ 0, 0 ] ],
15
- [ [ 2, 0, 0 ], [ 0, 0 ] ],
13
+ [
14
+ [1, 0, 0],
15
+ [0, 0],
16
+ ],
17
+ [
18
+ [2, 0, 0],
19
+ [0, 0],
20
+ ],
16
21
 
17
- [ [ 1, 0, -1 ], [ 1, -1 ] ],
18
- [ [ 1, 0, -4 ], [ 2, -2 ] ],
19
- [ [ 1, 0, 4 ], [ NaN, NaN ] ],
22
+ [
23
+ [1, 0, -1],
24
+ [1, -1],
25
+ ],
26
+ [
27
+ [1, 0, -4],
28
+ [2, -2],
29
+ ],
30
+ [
31
+ [1, 0, 4],
32
+ [NaN, NaN],
33
+ ],
20
34
 
21
- [ [ 1, 1, 0 ], [ 0, -1 ] ],
22
- [ [ 1, 2, 0 ], [ 0, -2 ] ],
35
+ [
36
+ [1, 1, 0],
37
+ [0, -1],
38
+ ],
39
+ [
40
+ [1, 2, 0],
41
+ [0, -2],
42
+ ],
23
43
 
24
- [ [ 1, 2, 1 ], [ -1, -1 ] ],
25
- [ [ -9, 2, 1/3 ], [ 1/3, -1/9 ] ],
44
+ [
45
+ [1, 2, 1],
46
+ [-1, -1],
47
+ ],
48
+ [
49
+ [-9, 2, 1 / 3],
50
+ [1 / 3, -1 / 9],
51
+ ],
26
52
  ];
27
53
 
28
- for (const [ testCase, solution ] of testCases) {
54
+ for (const [testCase, solution] of testCases) {
29
55
  const foundSolutions = solveQuadratic(...testCase);
30
56
  for (let i = 0; i < 2; i++) {
31
57
  if (isNaN(solution[i]) && isNaN(foundSolutions[i])) {
@@ -36,4 +62,4 @@ describe('solveQuadratic', () => {
36
62
  }
37
63
  }
38
64
  });
39
- });
65
+ });
@@ -1,4 +1,3 @@
1
-
2
1
  /**
3
2
  * Solves an equation of the form ax² + bx + c = 0.
4
3
  * The larger solution is returned first.
@@ -21,13 +20,13 @@ const solveQuadratic = (a: number, b: number, c: number): [number, number] => {
21
20
  solution = -c / b;
22
21
  }
23
22
 
24
- return [ solution, solution ];
23
+ return [solution, solution];
25
24
  }
26
25
 
27
26
  const discriminant = b * b - 4 * a * c;
28
27
 
29
28
  if (discriminant < 0) {
30
- return [ NaN, NaN ];
29
+ return [NaN, NaN];
31
30
  }
32
31
 
33
32
  const rootDiscriminant = Math.sqrt(discriminant);
@@ -35,9 +34,9 @@ const solveQuadratic = (a: number, b: number, c: number): [number, number] => {
35
34
  const solution2 = (-b - rootDiscriminant) / (2 * a);
36
35
 
37
36
  if (solution1 > solution2) {
38
- return [ solution1, solution2 ];
37
+ return [solution1, solution2];
39
38
  } else {
40
- return [ solution2, solution1 ];
39
+ return [solution2, solution1];
41
40
  }
42
41
  };
43
- export default solveQuadratic;
42
+ export default solveQuadratic;
@@ -12,4 +12,4 @@ it('cleanUpNumber', () => {
12
12
  expect(cleanUpNumber('1234.5')).toBe('1234.5');
13
13
  expect(cleanUpNumber('1234.500')).toBe('1234.5');
14
14
  expect(cleanUpNumber('1.1368683772161603e-13')).toBe('0');
15
- });
15
+ });
@@ -1,3 +1 @@
1
-
2
-
3
- export const numberRegex = /^([-]?)(\d*)[.](\d+)$/;
1
+ export const numberRegex = /^([-]?)(\d*)[.](\d+)$/;
@@ -1,6 +1,5 @@
1
1
  import { numberRegex } from './constants';
2
2
 
3
-
4
3
  /**
5
4
  * Returns the length of `numberAsString` after a decimal point.
6
5
  *
@@ -16,7 +15,7 @@ export const getLenAfterDecimal = (numberAsString: string) => {
16
15
  // like NaN or Infinity)
17
16
  if (numberAsString.search(/[eE]/) !== -1 || /^[a-zA-Z]+$/.exec(numberAsString)) {
18
17
  return -1;
19
- // Or it has no decimal point
18
+ // Or it has no decimal point
20
19
  } else {
21
20
  return 0;
22
21
  }
@@ -1,2 +1 @@
1
-
2
- export { toRoundedString } from './toRoundedString';
1
+ export { toRoundedString } from './toRoundedString';
@@ -29,4 +29,4 @@ describe('toRoundedString', () => {
29
29
  expect(toRoundedString(-10.123499)).toBe('-10.123499');
30
30
  expect(toRoundedString(0.00123499)).toBe('.00123499');
31
31
  });
32
- });
32
+ });
@@ -1,6 +1,5 @@
1
1
  import { toStringOfSamePrecision } from './toStringOfSamePrecision';
2
2
 
3
-
4
3
  it('toStringOfSamePrecision', () => {
5
4
  expect(toStringOfSamePrecision(1.23456, '1.12')).toBe('1.23');
6
5
  expect(toStringOfSamePrecision(1.23456, '1.120')).toBe('1.235');
@@ -18,4 +17,4 @@ it('toStringOfSamePrecision', () => {
18
17
  expect(toStringOfSamePrecision(-0.9999999999999432, '291.3')).toBe('-1');
19
18
  expect(toStringOfSamePrecision(9998.9, '.1', '-11')).toBe('9998.9');
20
19
  expect(toStringOfSamePrecision(-14.20000000000002, '.000001', '-11')).toBe('-14.2');
21
- });
20
+ });
@@ -60,4 +60,4 @@ export const toStringOfSamePrecision = (num: number, ...references: string[]): s
60
60
  return cleanUpNumber(`${negativeSign}${preDecimal}.${postDecimal}`);
61
61
  };
62
62
 
63
- export default toStringOfSamePrecision;
63
+ export default toStringOfSamePrecision;
@@ -14,11 +14,9 @@ import Parameterized2DShape from './Parameterized2DShape';
14
14
  * @internal
15
15
  */
16
16
  export abstract class BezierJSWrapper extends Parameterized2DShape {
17
- #bezierJs: Bezier|null = null;
17
+ #bezierJs: Bezier | null = null;
18
18
 
19
- protected constructor(
20
- bezierJsBezier?: Bezier
21
- ) {
19
+ protected constructor(bezierJsBezier?: Bezier) {
22
20
  super();
23
21
 
24
22
  if (bezierJsBezier) {
@@ -31,7 +29,7 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
31
29
 
32
30
  protected getBezier() {
33
31
  if (!this.#bezierJs) {
34
- this.#bezierJs = new Bezier(this.getPoints().map(p => p.xy));
32
+ this.#bezierJs = new Bezier(this.getPoints().map((p) => p.xy));
35
33
  }
36
34
  return this.#bezierJs;
37
35
  }
@@ -58,6 +56,7 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
58
56
  return Vec2.ofXY(this.getBezier().get(t));
59
57
  }
60
58
 
59
+ /** @returns the curve's directional derivative at `t`. */
61
60
  public derivativeAt(t: number): Point2 {
62
61
  return Vec2.ofXY(this.getBezier().derivative(t));
63
62
  }
@@ -66,6 +65,7 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
66
65
  return Vec2.ofXY((this.getBezier() as any).dderivative(t));
67
66
  }
68
67
 
68
+ /** @returns the [normal vector](https://en.wikipedia.org/wiki/Normal_(geometry)) to this curve at `t`. */
69
69
  public normal(t: number): Vec2 {
70
70
  return Vec2.ofXY(this.getBezier().normal(t));
71
71
  }
@@ -96,41 +96,49 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
96
96
  const asLine = LineSegment2.ofSmallestContainingPoints(this.getPoints());
97
97
  if (asLine) {
98
98
  const intersection = asLine.intersectsLineSegment(line);
99
- return intersection.map(p => this.nearestPointTo(p).parameterValue);
99
+ return intersection.map((p) => this.nearestPointTo(p).parameterValue);
100
100
  }
101
101
 
102
102
  const bezier = this.getBezier();
103
103
 
104
- return bezier.intersects(line).map(t => {
105
- // We're using the .intersects(line) function, which is documented
106
- // to always return numbers. However, to satisfy the type checker (and
107
- // possibly improperly-defined types),
108
- if (typeof t === 'string') {
109
- t = parseFloat(t);
110
- }
111
-
112
- const point = Vec2.ofXY(this.at(t));
113
-
114
- // Ensure that the intersection is on the line segment
115
- if (point.distanceTo(line.p1) > line.length
116
- || point.distanceTo(line.p2) > line.length) {
117
- return null;
118
- }
119
-
120
- return t;
121
- }).filter(entry => entry !== null);
104
+ return bezier
105
+ .intersects(line)
106
+ .map((t) => {
107
+ // We're using the .intersects(line) function, which is documented
108
+ // to always return numbers. However, to satisfy the type checker (and
109
+ // possibly improperly-defined types),
110
+ if (typeof t === 'string') {
111
+ t = parseFloat(t);
112
+ }
113
+
114
+ const point = Vec2.ofXY(this.at(t));
115
+
116
+ // Ensure that the intersection is on the line segment
117
+ if (point.distanceTo(line.p1) > line.length || point.distanceTo(line.p2) > line.length) {
118
+ return null;
119
+ }
120
+
121
+ return t;
122
+ })
123
+ .filter((entry) => entry !== null);
122
124
  }
123
125
 
124
126
  public override splitAt(t: number): [BezierJSWrapper] | [BezierJSWrapper, BezierJSWrapper] {
125
127
  if (t <= 0 || t >= 1) {
126
- return [ this ];
128
+ return [this];
127
129
  }
128
130
 
129
131
  const bezier = this.getBezier();
130
132
  const split = bezier.split(t);
131
133
  return [
132
- new BezierJSWrapperImpl(split.left.points.map(point => Vec2.ofXY(point)), split.left),
133
- new BezierJSWrapperImpl(split.right.points.map(point => Vec2.ofXY(point)), split.right),
134
+ new BezierJSWrapperImpl(
135
+ split.left.points.map((point) => Vec2.ofXY(point)),
136
+ split.left,
137
+ ),
138
+ new BezierJSWrapperImpl(
139
+ split.right.points.map((point) => Vec2.ofXY(point)),
140
+ split.right,
141
+ ),
134
142
  ];
135
143
  }
136
144
 
@@ -162,7 +170,7 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
162
170
 
163
171
  // Start by testing a few points:
164
172
  const pointsToTest = 4;
165
- for (let i = 0; i < pointsToTest; i ++) {
173
+ for (let i = 0; i < pointsToTest; i++) {
166
174
  const testT = i / (pointsToTest - 1);
167
175
  const testMinSqrDist = sqrDistAt(testT);
168
176
 
@@ -181,8 +189,12 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
181
189
  const bPrime = this.derivativeAt(t);
182
190
  const bPrimePrime = this.secondDerivativeAt(t);
183
191
  return (
184
- 2 * bPrime.x * bPrime.x + 2 * b.x * bPrimePrime.x - 2 * point.x * bPrimePrime.x
185
- + 2 * bPrime.y * bPrime.y + 2 * b.y * bPrimePrime.y - 2 * point.y * bPrimePrime.y
192
+ 2 * bPrime.x * bPrime.x +
193
+ 2 * b.x * bPrimePrime.x -
194
+ 2 * point.x * bPrimePrime.x +
195
+ 2 * bPrime.y * bPrime.y +
196
+ 2 * b.y * bPrimePrime.y -
197
+ 2 * point.y * bPrimePrime.y
186
198
  );
187
199
  };
188
200
  // Because we're zeroing f'(t), we also need to be able to compute it:
@@ -191,8 +203,7 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
191
203
  const b = this.at(t);
192
204
  const bPrime = this.derivativeAt(t);
193
205
  return (
194
- 2 * b.x * bPrime.x - 2 * point.x * bPrime.x
195
- + 2 * b.y * bPrime.y - 2 * point.y * bPrime.y
206
+ 2 * b.x * bPrime.x - 2 * point.x * bPrime.x + 2 * b.y * bPrime.y - 2 * point.y * bPrime.y
196
207
  );
197
208
  };
198
209
 
@@ -226,7 +237,10 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
226
237
  }
227
238
 
228
239
  public intersectsBezier(other: BezierJSWrapper) {
229
- const intersections = this.getBezier().intersects(other.getBezier()) as (string[] | null | undefined);
240
+ const intersections = this.getBezier().intersects(other.getBezier()) as
241
+ | string[]
242
+ | null
243
+ | undefined;
230
244
  if (!intersections || intersections.length === 0) {
231
245
  return [];
232
246
  }
@@ -239,7 +253,7 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
239
253
 
240
254
  if (!match) {
241
255
  throw new Error(
242
- `Incorrect format returned by .intersects: ${intersections} should be array of "number/number"!`
256
+ `Incorrect format returned by .intersects: ${intersections} should be array of "number/number"!`,
243
257
  );
244
258
  }
245
259
 
@@ -253,7 +267,9 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
253
267
  }
254
268
 
255
269
  public override toString() {
256
- return `Bézier(${this.getPoints().map(point => point.toString()).join(', ')})`;
270
+ return `Bézier(${this.getPoints()
271
+ .map((point) => point.toString())
272
+ .join(', ')})`;
257
273
  }
258
274
  }
259
275
 
@@ -262,7 +278,10 @@ export abstract class BezierJSWrapper extends Parameterized2DShape {
262
278
  * around a `Bezier`.
263
279
  */
264
280
  class BezierJSWrapperImpl extends BezierJSWrapper {
265
- public constructor(private controlPoints: readonly Point2[], curve?: Bezier) {
281
+ public constructor(
282
+ private controlPoints: readonly Point2[],
283
+ curve?: Bezier,
284
+ ) {
266
285
  super(curve);
267
286
  }
268
287
 
@@ -271,4 +290,4 @@ class BezierJSWrapperImpl extends BezierJSWrapper {
271
290
  }
272
291
  }
273
292
 
274
- export default BezierJSWrapper;
293
+ export default BezierJSWrapper;
@@ -23,13 +23,13 @@ class CubicBezier extends BezierJSWrapper {
23
23
  }
24
24
 
25
25
  public override getPoints() {
26
- return [ this.p0, this.p1, this.p2, this.p3 ];
26
+ return [this.p0, this.p1, this.p2, this.p3];
27
27
  }
28
28
 
29
29
  /** Returns an overestimate of this shape's bounding box. */
30
30
  public override getLooseBoundingBox(): Rect2 {
31
- return Rect2.bboxOf([ this.p0, this.p1, this.p2, this.p3 ]);
31
+ return Rect2.bboxOf([this.p0, this.p1, this.p2, this.p3]);
32
32
  }
33
33
  }
34
34
 
35
- export default CubicBezier;
35
+ export default CubicBezier;
@@ -2,7 +2,6 @@ import LineSegment2 from './LineSegment2';
2
2
  import { Vec2 } from '../Vec2';
3
3
  import Mat33 from '../Mat33';
4
4
 
5
-
6
5
  describe('Line2', () => {
7
6
  it('x and y axes should intersect at (0, 0)', () => {
8
7
  const xAxis = new LineSegment2(Vec2.of(-10, 0), Vec2.of(10, 0));
@@ -65,7 +64,7 @@ describe('Line2', () => {
65
64
  // Points taken from issue observed directly in editor
66
65
  const p1 = Vec2.of(769.6126045442547, 221.037877485765);
67
66
  const p2 = Vec2.of(770.3873954557453, 224.962122514235);
68
- const p3 = Vec2.of( 763.3590010920082, 223.66723995850086);
67
+ const p3 = Vec2.of(763.3590010920082, 223.66723995850086);
69
68
  const p4 = Vec2.of(763.5494167642871, 223.66723995850086);
70
69
 
71
70
  const line1 = new LineSegment2(p1, p2);
@@ -115,7 +114,7 @@ describe('Line2', () => {
115
114
  // Halving
116
115
  //
117
116
  expect(lineSegment.at(0.5)).objEq(midpoint);
118
- const [ firstHalf, secondHalf ] = lineSegment.splitAt(0.5);
117
+ const [firstHalf, secondHalf] = lineSegment.splitAt(0.5);
119
118
 
120
119
  if (!secondHalf) {
121
120
  throw new Error('Splitting a line segment in half should yield two line segments.');
@@ -136,24 +135,32 @@ describe('Line2', () => {
136
135
  it('equivalence check should allow ignoring direction', () => {
137
136
  expect(new LineSegment2(Vec2.zero, Vec2.unitX)).objEq(new LineSegment2(Vec2.zero, Vec2.unitX));
138
137
  expect(new LineSegment2(Vec2.zero, Vec2.unitX)).objEq(new LineSegment2(Vec2.unitX, Vec2.zero));
139
- expect(new LineSegment2(Vec2.zero, Vec2.unitX)).not.objEq(new LineSegment2(Vec2.unitX, Vec2.zero), { ignoreDirection: false });
138
+ expect(new LineSegment2(Vec2.zero, Vec2.unitX)).not.objEq(
139
+ new LineSegment2(Vec2.unitX, Vec2.zero),
140
+ { ignoreDirection: false },
141
+ );
140
142
  });
141
143
 
142
144
  it('should support creating from a collection of points', () => {
143
145
  expect(LineSegment2.ofSmallestContainingPoints([])).toBeNull();
144
146
  expect(LineSegment2.ofSmallestContainingPoints([Vec2.of(1, 1)])).toBeNull();
145
- expect(LineSegment2.ofSmallestContainingPoints(
146
- [Vec2.of(1, 1), Vec2.of(1, 2), Vec2.of(3, 3)]
147
- )).toBeNull();
148
-
149
- expect(LineSegment2.ofSmallestContainingPoints(
150
- [Vec2.of(1, 1), Vec2.of(1, 2)]
151
- )).objEq(new LineSegment2(Vec2.of(1, 1), Vec2.of(1, 2)));
152
- expect(LineSegment2.ofSmallestContainingPoints(
153
- [Vec2.of(1, 1), Vec2.of(2, 2), Vec2.of(3, 3)]
154
- )).objEq(new LineSegment2(Vec2.of(1, 1), Vec2.of(3, 3)));
155
- expect(LineSegment2.ofSmallestContainingPoints(
156
- [Vec2.of(3, 3), Vec2.of(2, 2), Vec2.of(2.4, 2.4), Vec2.of(3, 3)]
157
- )).objEq(new LineSegment2(Vec2.of(2, 2), Vec2.of(3, 3)));
147
+ expect(
148
+ LineSegment2.ofSmallestContainingPoints([Vec2.of(1, 1), Vec2.of(1, 2), Vec2.of(3, 3)]),
149
+ ).toBeNull();
150
+
151
+ expect(LineSegment2.ofSmallestContainingPoints([Vec2.of(1, 1), Vec2.of(1, 2)])).objEq(
152
+ new LineSegment2(Vec2.of(1, 1), Vec2.of(1, 2)),
153
+ );
154
+ expect(
155
+ LineSegment2.ofSmallestContainingPoints([Vec2.of(1, 1), Vec2.of(2, 2), Vec2.of(3, 3)]),
156
+ ).objEq(new LineSegment2(Vec2.of(1, 1), Vec2.of(3, 3)));
157
+ expect(
158
+ LineSegment2.ofSmallestContainingPoints([
159
+ Vec2.of(3, 3),
160
+ Vec2.of(2, 2),
161
+ Vec2.of(2.4, 2.4),
162
+ Vec2.of(3, 3),
163
+ ]),
164
+ ).objEq(new LineSegment2(Vec2.of(2, 2), Vec2.of(3, 3)));
158
165
  });
159
166
  });
@@ -42,7 +42,7 @@ export class LineSegment2 extends Parameterized2DShape {
42
42
  /** Creates a new `LineSegment2` from its endpoints. */
43
43
  public constructor(
44
44
  private readonly point1: Point2,
45
- private readonly point2: Point2
45
+ private readonly point2: Point2,
46
46
  ) {
47
47
  super();
48
48
 
@@ -70,7 +70,7 @@ export class LineSegment2 extends Parameterized2DShape {
70
70
  public static ofSmallestContainingPoints(points: readonly Point2[]) {
71
71
  if (points.length <= 1) return null;
72
72
 
73
- const sorted = [...points].sort((a, b) => a.x !== b.x ? a.x - b.x : a.y - b.y);
73
+ const sorted = [...points].sort((a, b) => (a.x !== b.x ? a.x - b.x : a.y - b.y));
74
74
  const line = new LineSegment2(sorted[0], sorted[sorted.length - 1]);
75
75
 
76
76
  for (const point of sorted) {
@@ -127,15 +127,12 @@ export class LineSegment2 extends Parameterized2DShape {
127
127
  return this.direction;
128
128
  }
129
129
 
130
- public splitAt(t: number): [LineSegment2]|[LineSegment2,LineSegment2] {
130
+ public splitAt(t: number): [LineSegment2] | [LineSegment2, LineSegment2] {
131
131
  if (t <= 0 || t >= 1) {
132
132
  return [this];
133
133
  }
134
134
 
135
- return [
136
- new LineSegment2(this.point1, this.at(t)),
137
- new LineSegment2(this.at(t), this.point2),
138
- ];
135
+ return [new LineSegment2(this.point1, this.at(t)), new LineSegment2(this.at(t), this.point2)];
139
136
  }
140
137
 
141
138
  /**
@@ -146,7 +143,7 @@ export class LineSegment2 extends Parameterized2DShape {
146
143
  * This will change in a future release.
147
144
  * @deprecated
148
145
  */
149
- public intersection(other: LineSegment2): IntersectionResult|null {
146
+ public intersection(other: LineSegment2): IntersectionResult | null {
150
147
  // TODO(v2.0.0): Make this return a `t` value from `0` to `1`.
151
148
 
152
149
  // We want x₁(t) = x₂(t) and y₁(t) = y₂(t)
@@ -191,21 +188,18 @@ export class LineSegment2 extends Parameterized2DShape {
191
188
 
192
189
  const xIntersect = this.point1.x;
193
190
  const yIntersect =
194
- (this.point1.x - other.point1.x) * other.direction.y / other.direction.x + other.point1.y;
191
+ ((this.point1.x - other.point1.x) * other.direction.y) / other.direction.x + other.point1.y;
195
192
  resultPoint = Vec2.of(xIntersect, yIntersect);
196
193
  resultT = (yIntersect - this.point1.y) / this.direction.y;
197
194
  } else {
198
195
  // From above,
199
196
  // x = ((o₁ᵧ - o₂ᵧ)(d₁ₓd₂ₓ) + (d₂ᵧd₁ₓ)(o₂ₓ) - (d₁ᵧd₂ₓ)(o₁ₓ))/(d₂ᵧd₁ₓ - d₁ᵧd₂ₓ)
200
- const numerator = (
201
- (this.point1.y - other.point1.y) * this.direction.x * other.direction.x
202
- + this.direction.x * other.direction.y * other.point1.x
203
- - this.direction.y * other.direction.x * this.point1.x
204
- );
205
- const denominator = (
206
- other.direction.y * this.direction.x
207
- - this.direction.y * other.direction.x
208
- );
197
+ const numerator =
198
+ (this.point1.y - other.point1.y) * this.direction.x * other.direction.x +
199
+ this.direction.x * other.direction.y * other.point1.x -
200
+ this.direction.y * other.direction.x * this.point1.x;
201
+ const denominator =
202
+ other.direction.y * this.direction.x - this.direction.y * other.direction.x;
209
203
 
210
204
  // Avoid dividing by zero. It means there is no intersection
211
205
  if (denominator === 0) {
@@ -225,10 +219,12 @@ export class LineSegment2 extends Parameterized2DShape {
225
219
  const resultToP3 = resultPoint.distanceTo(other.point1);
226
220
  const resultToP4 = resultPoint.distanceTo(other.point2);
227
221
 
228
- if (resultToP1 > this.length
229
- || resultToP2 > this.length
230
- || resultToP3 > other.length
231
- || resultToP4 > other.length) {
222
+ if (
223
+ resultToP1 > this.length ||
224
+ resultToP2 > this.length ||
225
+ resultToP3 > other.length ||
226
+ resultToP4 > other.length
227
+ ) {
232
228
  return null;
233
229
  }
234
230
 
@@ -246,7 +242,7 @@ export class LineSegment2 extends Parameterized2DShape {
246
242
  const intersection = this.intersection(lineSegment);
247
243
 
248
244
  if (intersection) {
249
- return [ intersection.t / this.length ];
245
+ return [intersection.t / this.length];
250
246
  }
251
247
  return [];
252
248
  }
@@ -263,7 +259,7 @@ export class LineSegment2 extends Parameterized2DShape {
263
259
  const intersection = this.intersection(lineSegment);
264
260
 
265
261
  if (intersection) {
266
- return [ intersection.point ];
262
+ return [intersection.point];
267
263
  }
268
264
  return [];
269
265
  }
@@ -273,7 +269,7 @@ export class LineSegment2 extends Parameterized2DShape {
273
269
  return this.nearestPointTo(target).point;
274
270
  }
275
271
 
276
- public override nearestPointTo(target: Vec3): { point: Vec3; parameterValue: number; } {
272
+ public override nearestPointTo(target: Vec3): { point: Vec3; parameterValue: number } {
277
273
  // Distance from P1 along this' direction.
278
274
  const projectedDistFromP1 = target.minus(this.p1).dot(this.direction);
279
275
  const projectedDistFromP2 = this.length - projectedDistFromP1;
@@ -304,7 +300,8 @@ export class LineSegment2 extends Parameterized2DShape {
304
300
  /** Returns a copy of this line segment transformed by the given `affineTransfm`. */
305
301
  public transformedBy(affineTransfm: Mat33): LineSegment2 {
306
302
  return new LineSegment2(
307
- affineTransfm.transformVec2(this.p1), affineTransfm.transformVec2(this.p2)
303
+ affineTransfm.transformVec2(this.p1),
304
+ affineTransfm.transformVec2(this.p2),
308
305
  );
309
306
  }
310
307
 
@@ -324,7 +321,7 @@ export class LineSegment2 extends Parameterized2DShape {
324
321
  * - `tolerance`: The maximum difference between endpoints. (Default: 0)
325
322
  * - `ignoreDirection`: Allow matching a version of `this` with opposite direction. (Default: `true`)
326
323
  */
327
- public eq(other: LineSegment2, options?: { tolerance?: number, ignoreDirection?: boolean }) {
324
+ public eq(other: LineSegment2, options?: { tolerance?: number; ignoreDirection?: boolean }) {
328
325
  if (!(other instanceof LineSegment2)) {
329
326
  return false;
330
327
  }
@@ -333,8 +330,8 @@ export class LineSegment2 extends Parameterized2DShape {
333
330
  const ignoreDirection = options?.ignoreDirection ?? true;
334
331
 
335
332
  return (
336
- (other.p1.eq(this.p1, tolerance) && other.p2.eq(this.p2, tolerance))
337
- || (ignoreDirection && other.p1.eq(this.p2, tolerance) && other.p2.eq(this.p1, tolerance))
333
+ (other.p1.eq(this.p1, tolerance) && other.p2.eq(this.p2, tolerance)) ||
334
+ (ignoreDirection && other.p1.eq(this.p2, tolerance) && other.p2.eq(this.p1, tolerance))
338
335
  );
339
336
  }
340
337
  }
@@ -20,13 +20,15 @@ export abstract class Parameterized2DShape extends Abstract2DShape {
20
20
  /**
21
21
  * Divides this shape into two separate shapes at parameter value $t$.
22
22
  */
23
- abstract splitAt(t: number): [ Parameterized2DShape ] | [ Parameterized2DShape, Parameterized2DShape ];
23
+ abstract splitAt(
24
+ t: number,
25
+ ): [Parameterized2DShape] | [Parameterized2DShape, Parameterized2DShape];
24
26
 
25
27
  /**
26
28
  * Returns the nearest point on `this` to `point` and the `parameterValue` at which
27
29
  * that point occurs.
28
30
  */
29
- abstract nearestPointTo(point: Point2): { point: Point2, parameterValue: number };
31
+ abstract nearestPointTo(point: Point2): { point: Point2; parameterValue: number };
30
32
 
31
33
  /**
32
34
  * Returns the **parameter values** at which `lineSegment` intersects this shape.
@@ -35,9 +37,8 @@ export abstract class Parameterized2DShape extends Abstract2DShape {
35
37
  */
36
38
  public abstract argIntersectsLineSegment(lineSegment: LineSegment2): number[];
37
39
 
38
-
39
40
  public override intersectsLineSegment(line: LineSegment2): Point2[] {
40
- return this.argIntersectsLineSegment(line).map(t => this.at(t));
41
+ return this.argIntersectsLineSegment(line).map((t) => this.at(t));
41
42
  }
42
43
  }
43
44
 
@@ -27,7 +27,7 @@ describe('Path.fromString', () => {
27
27
  },
28
28
  {
29
29
  kind: PathCommandType.MoveTo,
30
- point: Vec2.of(3,3),
30
+ point: Vec2.of(3, 3),
31
31
  },
32
32
  ]);
33
33
  expect(path3.startPoint).toMatchObject(Vec2.of(1, 1));
@@ -71,7 +71,7 @@ describe('Path.fromString', () => {
71
71
  const path1 = Path.fromString('m3,3 l1,2 l1,1 z');
72
72
  const path2 = Path.fromString('m3,3 l1,2 l1,1 Z');
73
73
 
74
- expect(path1.startPoint).toMatchObject(Vec2.of(3,3));
74
+ expect(path1.startPoint).toMatchObject(Vec2.of(3, 3));
75
75
  expect(path2.startPoint).toMatchObject(path1.startPoint);
76
76
  expect(path1.parts).toMatchObject(path2.parts);
77
77
  expect(path1.parts).toMatchObject([
@@ -86,7 +86,7 @@ describe('Path.fromString', () => {
86
86
  {
87
87
  kind: PathCommandType.LineTo,
88
88
  point: path1.startPoint,
89
- }
89
+ },
90
90
  ]);
91
91
  });
92
92
 
@@ -128,7 +128,7 @@ describe('Path.fromString', () => {
128
128
  controlPoint1: Vec2.of(1, 1),
129
129
  controlPoint2: Vec2.of(0.1, 0.1),
130
130
  endPoint: Vec2.zero,
131
- }
131
+ },
132
132
  ]);
133
133
  });
134
134
 
@@ -220,4 +220,4 @@ describe('Path.fromString', () => {
220
220
  ]);
221
221
  expect(path.startPoint).toMatchObject(Vec2.of(5, 10));
222
222
  });
223
- });
223
+ });