@khanacademy/kmath 2.0.0 → 2.0.1

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/index.js CHANGED
@@ -12,103 +12,9 @@ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'defau
12
12
  var ___default = /*#__PURE__*/_interopDefaultCompat(_);
13
13
  var $__default = /*#__PURE__*/_interopDefaultCompat($);
14
14
 
15
- // This file is processed by a Rollup plugin (replace) to inject the production
16
- // version number during the release build.
17
- // In dev, you'll never see the version number.
15
+ const libName="@khanacademy/kmath";const libVersion="2.0.1";perseusUtils.addLibraryVersionToPerseusDebug(libName,libVersion);
18
16
 
19
- const libName = "@khanacademy/kmath";
20
- const libVersion = "2.0.0";
21
- perseusUtils.addLibraryVersionToPerseusDebug(libName, libVersion);
22
-
23
- /**
24
- * Number Utils
25
- * A number is a js-number, e.g. 5.12
26
- */
27
-
28
- const DEFAULT_TOLERANCE = 1e-9;
29
-
30
- // TODO: Should this just be Number.Epsilon
31
- const EPSILON = Math.pow(2, -42);
32
- function is$2(x) {
33
- return ___default.default.isNumber(x) && !___default.default.isNaN(x);
34
- }
35
- function equal$4(x, y, tolerance) {
36
- // Checking for undefined makes this function behave nicely
37
- // with vectors of different lengths that are _.zip'd together
38
- if (x == null || y == null) {
39
- return x === y;
40
- }
41
- // We check === here so that +/-Infinity comparisons work correctly
42
- if (x === y) {
43
- return true;
44
- }
45
- if (tolerance == null) {
46
- tolerance = DEFAULT_TOLERANCE;
47
- }
48
- return Math.abs(x - y) < tolerance;
49
- }
50
- function sign$1(x, tolerance) /* Should be: 0 | 1 | -1 */{
51
- return equal$4(x, 0, tolerance) ? 0 : Math.abs(x) / x;
52
- }
53
- function isInteger(num, tolerance) {
54
- return equal$4(Math.round(num), num, tolerance);
55
- }
56
-
57
- // Round a number to a certain number of decimal places
58
- function round$2(num, precision) {
59
- const factor = Math.pow(10, precision);
60
- return Math.round(num * factor) / factor;
61
- }
62
-
63
- // Round num to the nearest multiple of increment
64
- // i.e. roundTo(83, 5) -> 85
65
- function roundTo$2(num, increment) {
66
- return Math.round(num / increment) * increment;
67
- }
68
- function floorTo$2(num, increment) {
69
- return Math.floor(num / increment) * increment;
70
- }
71
- function ceilTo$2(num, increment) {
72
- return Math.ceil(num / increment) * increment;
73
- }
74
-
75
- /**
76
- * toFraction
77
- *
78
- * Returns a [numerator, denominator] array rational representation
79
- * of `decimal`
80
- *
81
- * See http://en.wikipedia.org/wiki/Continued_fraction for implementation
82
- * details
83
- *
84
- * toFraction(4/8) => [1, 2]
85
- * toFraction(0.66) => [33, 50]
86
- * toFraction(0.66, 0.01) => [2/3]
87
- * toFraction(283 + 1/3) => [850, 3]
88
- */
89
- function toFraction(decimal) {
90
- let tolerance = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : EPSILON;
91
- let maxDenominator = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1000;
92
- // Initialize everything to compute successive terms of
93
- // continued-fraction approximations via recurrence relation
94
- let n = [1, 0];
95
- let d = [0, 1];
96
- let a = Math.floor(decimal);
97
- let rem = decimal - a;
98
- while (d[0] <= maxDenominator) {
99
- if (equal$4(n[0] / d[0], decimal, tolerance)) {
100
- return [n[0], d[0]];
101
- }
102
- n = [a * n[0] + n[1], n[0]];
103
- d = [a * d[0] + d[1], d[0]];
104
- a = Math.floor(1 / rem);
105
- rem = 1 / rem - a;
106
- }
107
-
108
- // We failed to find a nice rational representation,
109
- // so return an irrational "fraction"
110
- return [decimal, 1];
111
- }
17
+ const DEFAULT_TOLERANCE=1e-9;const EPSILON=Math.pow(2,-42);function is$2(x){return ___default.default.isNumber(x)&&!___default.default.isNaN(x)}function equal$4(x,y,tolerance){if(x==null||y==null){return x===y}if(x===y){return true}if(tolerance==null){tolerance=DEFAULT_TOLERANCE;}return Math.abs(x-y)<tolerance}function sign$1(x,tolerance){return equal$4(x,0,tolerance)?0:Math.abs(x)/x}function isInteger(num,tolerance){return equal$4(Math.round(num),num,tolerance)}function round$2(num,precision){const factor=Math.pow(10,precision);return Math.round(num*factor)/factor}function roundTo$2(num,increment){return Math.round(num/increment)*increment}function floorTo$2(num,increment){return Math.floor(num/increment)*increment}function ceilTo$2(num,increment){return Math.ceil(num/increment)*increment}function toFraction(decimal,tolerance=EPSILON,maxDenominator=1e3){let n=[1,0];let d=[0,1];let a=Math.floor(decimal);let rem=decimal-a;while(d[0]<=maxDenominator){if(equal$4(n[0]/d[0],decimal,tolerance)){return [n[0],d[0]]}n=[a*n[0]+n[1],n[0]];d=[a*d[0]+d[1],d[0]];a=Math.floor(1/rem);rem=1/rem-a;}return [decimal,1]}
112
18
 
113
19
  var number = /*#__PURE__*/Object.freeze({
114
20
  __proto__: null,
@@ -125,198 +31,7 @@ var number = /*#__PURE__*/Object.freeze({
125
31
  toFraction: toFraction
126
32
  });
127
33
 
128
- /**
129
- * Vector Utils
130
- * A vector is an array of numbers e.g. [0, 3, 4].
131
- */
132
-
133
- function arraySum(array) {
134
- return array.reduce((memo, arg) => memo + arg, 0);
135
- }
136
- function arrayProduct(array) {
137
- return array.reduce((memo, arg) => memo * arg, 1);
138
- }
139
- function zip() {
140
- for (var _len = arguments.length, arrays = new Array(_len), _key = 0; _key < _len; _key++) {
141
- arrays[_key] = arguments[_key];
142
- }
143
- const n = Math.min(...arrays.map(a => a.length));
144
- const results = [];
145
- for (let i = 0; i < n; i++) {
146
- results.push(arrays.map(a => a[i]));
147
- }
148
- return results;
149
- }
150
- function map(pair, f) {
151
- return [f(pair[0], 0), f(pair[1], 1)];
152
- }
153
-
154
- /**
155
- * Checks if the given vector contains only numbers and, optionally, is of the
156
- * right dimension (length).
157
- *
158
- * is([1, 2, 3]) -> true
159
- * is([1, "Hello", 3]) -> false
160
- * is([1, 2, 3], 1) -> false
161
- */
162
-
163
- function is$1(vec, dimension) {
164
- if (!Array.isArray(vec)) {
165
- return false;
166
- }
167
- if (dimension !== undefined && vec.length !== dimension) {
168
- return false;
169
- }
170
- return vec.every(is$2);
171
- }
172
-
173
- // Normalize to a unit vector
174
- function normalize(v) {
175
- return scale(v, 1 / length(v));
176
- }
177
-
178
- // Length/magnitude of a vector
179
- function length(v) {
180
- return Math.sqrt(dot(v, v));
181
- }
182
- // Dot product of two vectors
183
- function dot(a, b) {
184
- const zipped = zip(a, b);
185
- const multiplied = zipped.map(arrayProduct);
186
- return arraySum(multiplied);
187
- }
188
-
189
- /* vector-add multiple [x, y] coords/vectors
190
- *
191
- * add([1, 2], [3, 4]) -> [4, 6]
192
- */
193
- function add$1() {
194
- const zipped = zip(...arguments);
195
- // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
196
- return zipped.map(arraySum);
197
- }
198
- function subtract(v1, v2) {
199
- // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
200
- return zip(v1, v2).map(dim => dim[0] - dim[1]);
201
- }
202
- function negate(v) {
203
- // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
204
- return v.map(x => {
205
- return -x;
206
- });
207
- }
208
-
209
- // Scale a vector
210
- function scale(v1, scalar) {
211
- // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
212
- return v1.map(x => {
213
- return x * scalar;
214
- });
215
- }
216
- function equal$3(v1, v2, tolerance) {
217
- return v1.length === v2.length && zip(v1, v2).every(pair => equal$4(pair[0], pair[1], tolerance));
218
- }
219
- function codirectional(v1, v2, tolerance) {
220
- // The origin is trivially codirectional with all other vectors.
221
- // This gives nice semantics for codirectionality between points when
222
- // comparing their difference vectors.
223
- if (equal$4(length(v1), 0, tolerance) || equal$4(length(v2), 0, tolerance)) {
224
- return true;
225
- }
226
- v1 = normalize(v1);
227
- v2 = normalize(v2);
228
- return equal$3(v1, v2, tolerance);
229
- }
230
- function collinear$1(v1, v2, tolerance) {
231
- return codirectional(v1, v2, tolerance) || codirectional(v1, negate(v2), tolerance);
232
- }
233
-
234
- // TODO(jeremy) These coordinate conversion functions really only handle 2D points (ie. [number, number])
235
-
236
- // Convert a cartesian coordinate into a radian polar coordinate
237
- function polarRadFromCart$1(v) {
238
- const radius = length(v);
239
- let theta = Math.atan2(v[1], v[0]);
240
-
241
- // Convert angle range from [-pi, pi] to [0, 2pi]
242
- if (theta < 0) {
243
- theta += 2 * Math.PI;
244
- }
245
- return [radius, theta];
246
- }
247
-
248
- // Converts a cartesian coordinate into a degree polar coordinate
249
- function polarDegFromCart$1(v) /* TODO: convert to tuple/Point */{
250
- const polar = polarRadFromCart$1(v);
251
- return [polar[0], polar[1] * 180 / Math.PI];
252
- }
253
-
254
- /* Convert a polar coordinate into a cartesian coordinate
255
- *
256
- * Examples:
257
- * cartFromPolarRad(5, Math.PI)
258
- */
259
- function cartFromPolarRad$1(radius) /* TODO: convert to tuple/Point */{
260
- let theta = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
261
- return [radius * Math.cos(theta), radius * Math.sin(theta)];
262
- }
263
-
264
- /* Convert a polar coordinate into a cartesian coordinate
265
- *
266
- * Examples:
267
- * cartFromPolarDeg(5, 30)
268
- */
269
- function cartFromPolarDeg$1(radius) {
270
- let theta = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
271
- return cartFromPolarRad$1(radius, theta * Math.PI / 180);
272
- }
273
-
274
- // Rotate vector
275
- function rotateRad$1(v, theta) {
276
- const polar = polarRadFromCart$1(v);
277
- const angle = polar[1] + theta;
278
- return cartFromPolarRad$1(polar[0], angle);
279
- }
280
- function rotateDeg$1(v, theta) {
281
- const polar = polarDegFromCart$1(v);
282
- const angle = polar[1] + theta;
283
- return cartFromPolarDeg$1(polar[0], angle);
284
- }
285
-
286
- // Angle between two vectors
287
- function angleRad(v1, v2) {
288
- return Math.acos(dot(v1, v2) / (length(v1) * length(v2)));
289
- }
290
- function angleDeg(v1, v2) {
291
- return angleRad(v1, v2) * 180 / Math.PI;
292
- }
293
-
294
- // Vector projection of v1 onto v2
295
- function projection(v1, v2) {
296
- const scalar = dot(v1, v2) / dot(v2, v2);
297
- return scale(v2, scalar);
298
- }
299
-
300
- // Round each number to a certain number of decimal places
301
- function round$1(vec, precision) {
302
- // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
303
- return vec.map((elem, i) => round$2(elem, precision[i] || precision));
304
- }
305
-
306
- // Round each number to the nearest increment
307
-
308
- function roundTo$1(vec, increment) {
309
- // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
310
- return vec.map((elem, i) => roundTo$2(elem, increment[i] || increment));
311
- }
312
- function floorTo$1(vec, increment) {
313
- // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
314
- return vec.map((elem, i) => floorTo$2(elem, increment[i] || increment));
315
- }
316
- function ceilTo$1(vec, increment) {
317
- // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
318
- return vec.map((elem, i) => ceilTo$2(elem, increment[i] || increment));
319
- }
34
+ function arraySum(array){return array.reduce((memo,arg)=>memo+arg,0)}function arrayProduct(array){return array.reduce((memo,arg)=>memo*arg,1)}function zip(...arrays){const n=Math.min(...arrays.map(a=>a.length));const results=[];for(let i=0;i<n;i++){results.push(arrays.map(a=>a[i]));}return results}function map(pair,f){return [f(pair[0],0),f(pair[1],1)]}function is$1(vec,dimension){if(!Array.isArray(vec)){return false}if(dimension!==undefined&&vec.length!==dimension){return false}return vec.every(is$2)}function normalize(v){return scale(v,1/length(v))}function length(v){return Math.sqrt(dot(v,v))}function dot(a,b){const zipped=zip(a,b);const multiplied=zipped.map(arrayProduct);return arraySum(multiplied)}function add$1(...vecs){const zipped=zip(...vecs);return zipped.map(arraySum)}function subtract(v1,v2){return zip(v1,v2).map(dim=>dim[0]-dim[1])}function negate(v){return v.map(x=>{return -x})}function scale(v1,scalar){return v1.map(x=>{return x*scalar})}function equal$3(v1,v2,tolerance){return v1.length===v2.length&&zip(v1,v2).every(pair=>equal$4(pair[0],pair[1],tolerance))}function codirectional(v1,v2,tolerance){if(equal$4(length(v1),0,tolerance)||equal$4(length(v2),0,tolerance)){return true}v1=normalize(v1);v2=normalize(v2);return equal$3(v1,v2,tolerance)}function collinear$1(v1,v2,tolerance){return codirectional(v1,v2,tolerance)||codirectional(v1,negate(v2),tolerance)}function polarRadFromCart$1(v){const radius=length(v);let theta=Math.atan2(v[1],v[0]);if(theta<0){theta+=2*Math.PI;}return [radius,theta]}function polarDegFromCart$1(v){const polar=polarRadFromCart$1(v);return [polar[0],polar[1]*180/Math.PI]}function cartFromPolarRad$1(radius,theta=0){return [radius*Math.cos(theta),radius*Math.sin(theta)]}function cartFromPolarDeg$1(radius,theta=0){return cartFromPolarRad$1(radius,theta*Math.PI/180)}function rotateRad$1(v,theta){const polar=polarRadFromCart$1(v);const angle=polar[1]+theta;return cartFromPolarRad$1(polar[0],angle)}function rotateDeg$1(v,theta){const polar=polarDegFromCart$1(v);const angle=polar[1]+theta;return cartFromPolarDeg$1(polar[0],angle)}function angleRad(v1,v2){return Math.acos(dot(v1,v2)/(length(v1)*length(v2)))}function angleDeg(v1,v2){return angleRad(v1,v2)*180/Math.PI}function projection(v1,v2){const scalar=dot(v1,v2)/dot(v2,v2);return scale(v2,scalar)}function round$1(vec,precision){return vec.map((elem,i)=>round$2(elem,precision[i]||precision))}function roundTo$1(vec,increment){return vec.map((elem,i)=>roundTo$2(elem,increment[i]||increment))}function floorTo$1(vec,increment){return vec.map((elem,i)=>floorTo$2(elem,increment[i]||increment))}function ceilTo$1(vec,increment){return vec.map((elem,i)=>ceilTo$2(elem,increment[i]||increment))}
320
35
 
321
36
  var vector$1 = /*#__PURE__*/Object.freeze({
322
37
  __proto__: null,
@@ -348,94 +63,7 @@ var vector$1 = /*#__PURE__*/Object.freeze({
348
63
  zip: zip
349
64
  });
350
65
 
351
- /**
352
- * Point Utils
353
- * A point is an array of two numbers e.g. [0, 0].
354
- */
355
-
356
-
357
- // A point, in 2D, 3D, or nD space.
358
-
359
- // Rotate point (around origin unless a center is specified)
360
- function rotateRad(point, theta, center) {
361
- if (center === undefined) {
362
- return rotateRad$1(point, theta);
363
- } else {
364
- return add$1(center, rotateRad$1(subtract(point, center), theta));
365
- }
366
- }
367
- function rotateDeg(point, theta, center) {
368
- if (center === undefined) {
369
- return rotateDeg$1(point, theta);
370
- } else {
371
- return add$1(center, rotateDeg$1(subtract(point, center), theta));
372
- }
373
- }
374
-
375
- // Distance between two points
376
- function distanceToPoint$1(point1, point2) {
377
- return length(subtract(point1, point2));
378
- }
379
-
380
- // Distance between point and line
381
- function distanceToLine(point, line) {
382
- const lv = subtract(line[1], line[0]);
383
- const pv = subtract(point, line[0]);
384
- const projectedPv = projection(pv, lv);
385
- const distancePv = subtract(projectedPv, pv);
386
- return length(distancePv);
387
- }
388
-
389
- // Reflect point over line
390
- function reflectOverLine(point, line) {
391
- const lv = subtract(line[1], line[0]);
392
- const pv = subtract(point, line[0]);
393
- const projectedPv = projection(pv, lv);
394
- const reflectedPv = subtract(scale(projectedPv, 2), pv);
395
- return add$1(line[0], reflectedPv);
396
- }
397
-
398
- /**
399
- * Compares two points, returning -1, 0, or 1, for use with
400
- * Array.prototype.sort
401
- *
402
- * Note: This technically doesn't satisfy the total-ordering
403
- * requirements of Array.prototype.sort unless equalityTolerance
404
- * is 0. In some cases very close points that compare within a
405
- * few equalityTolerances could appear in the wrong order.
406
- */
407
- function compare(point1, point2, equalityTolerance) /* TODO: convert to -1 | 0 | 1 type */{
408
- if (point1.length !== point2.length) {
409
- return point1.length - point2.length;
410
- }
411
- for (let i = 0; i < point1.length; i++) {
412
- if (!equal$4(point1[i], point2[i], equalityTolerance)) {
413
- return point1[i] - point2[i];
414
- }
415
- }
416
- return 0;
417
- }
418
-
419
- // Check if a value is a point
420
- const is = is$1;
421
-
422
- // Add and subtract vector(s)
423
- const addVector = add$1;
424
- const addVectors = add$1;
425
- const subtractVector = subtract;
426
- const equal$2 = equal$3;
427
-
428
- // Convert from cartesian to polar and back
429
- const polarRadFromCart = polarRadFromCart$1;
430
- const polarDegFromCart = polarDegFromCart$1;
431
- const cartFromPolarRad = cartFromPolarRad$1;
432
- const cartFromPolarDeg = cartFromPolarDeg$1;
433
-
434
- // Rounding
435
- const round = round$1;
436
- const roundTo = roundTo$1;
437
- const floorTo = floorTo$1;
438
- const ceilTo = ceilTo$1;
66
+ function rotateRad(point,theta,center){if(center===undefined){return rotateRad$1(point,theta)}else {return add$1(center,rotateRad$1(subtract(point,center),theta))}}function rotateDeg(point,theta,center){if(center===undefined){return rotateDeg$1(point,theta)}else {return add$1(center,rotateDeg$1(subtract(point,center),theta))}}function distanceToPoint$1(point1,point2){return length(subtract(point1,point2))}function distanceToLine(point,line){const lv=subtract(line[1],line[0]);const pv=subtract(point,line[0]);const projectedPv=projection(pv,lv);const distancePv=subtract(projectedPv,pv);return length(distancePv)}function reflectOverLine(point,line){const lv=subtract(line[1],line[0]);const pv=subtract(point,line[0]);const projectedPv=projection(pv,lv);const reflectedPv=subtract(scale(projectedPv,2),pv);return add$1(line[0],reflectedPv)}function compare(point1,point2,equalityTolerance){if(point1.length!==point2.length){return point1.length-point2.length}for(let i=0;i<point1.length;i++){if(!equal$4(point1[i],point2[i],equalityTolerance)){return point1[i]-point2[i]}}return 0}const is=is$1;const addVector=add$1;const addVectors=add$1;const subtractVector=subtract;const equal$2=equal$3;const polarRadFromCart=polarRadFromCart$1;const polarDegFromCart=polarDegFromCart$1;const cartFromPolarRad=cartFromPolarRad$1;const cartFromPolarDeg=cartFromPolarDeg$1;const round=round$1;const roundTo=roundTo$1;const floorTo=floorTo$1;const ceilTo=ceilTo$1;
439
67
 
440
68
  var point = /*#__PURE__*/Object.freeze({
441
69
  __proto__: null,
@@ -460,38 +88,7 @@ var point = /*#__PURE__*/Object.freeze({
460
88
  subtractVector: subtractVector
461
89
  });
462
90
 
463
- /**
464
- * Line Utils
465
- * A line is an array of two points e.g. [[-5, 0], [5, 0]].
466
- */
467
-
468
- function distanceToPoint(line, point$1) {
469
- return distanceToLine(point$1, line);
470
- }
471
- function reflectPoint(line, point$1) {
472
- return reflectOverLine(point$1, line);
473
- }
474
- function midpoint(line) {
475
- return [(line[0][0] + line[1][0]) / 2, (line[0][1] + line[1][1]) / 2];
476
- }
477
- function equal$1(line1, line2, tolerance) {
478
- // TODO: A nicer implementation might just check collinearity of
479
- // vectors using underscore magick
480
- // Compare the directions of the lines
481
- const v1 = subtract(line1[1], line1[0]);
482
- const v2 = subtract(line2[1], line2[0]);
483
- if (!collinear$1(v1, v2, tolerance)) {
484
- return false;
485
- }
486
- // If the start point is the same for the two lines, then they are the same
487
- if (equal$2(line1[0], line2[0])) {
488
- return true;
489
- }
490
- // Make sure that the direction to get from line1 to
491
- // line2 is the same as the direction of the lines
492
- const line1ToLine2Vector = subtract(line2[0], line1[0]);
493
- return collinear$1(v1, line1ToLine2Vector, tolerance);
494
- }
91
+ function distanceToPoint(line,point$1){return distanceToLine(point$1,line)}function reflectPoint(line,point$1){return reflectOverLine(point$1,line)}function midpoint(line){return [(line[0][0]+line[1][0])/2,(line[0][1]+line[1][1])/2]}function equal$1(line1,line2,tolerance){const v1=subtract(line1[1],line1[0]);const v2=subtract(line2[1],line2[0]);if(!collinear$1(v1,v2,tolerance)){return false}if(equal$2(line1[0],line2[0])){return true}const line1ToLine2Vector=subtract(line2[0],line1[0]);return collinear$1(v1,line1ToLine2Vector,tolerance)}
495
92
 
496
93
  var line = /*#__PURE__*/Object.freeze({
497
94
  __proto__: null,
@@ -501,523 +98,16 @@ var line = /*#__PURE__*/Object.freeze({
501
98
  reflectPoint: reflectPoint
502
99
  });
503
100
 
504
- /**
505
- * Ray Utils
506
- * A ray (→) is an array of an endpoint and another point along the ray.
507
- * For example, [[0, 0], [1, 0]] is the ray starting at the origin and
508
- * traveling along the positive x-axis.
509
- */
510
-
511
- function equal(ray1, ray2, tolerance) {
512
- // Compare the directions of the rays
513
- const v1 = subtract(ray1[1], ray1[0]);
514
- const v2 = subtract(ray2[1], ray2[0]);
515
- const sameOrigin = equal$2(ray1[0], ray2[0]);
516
- const codirectional$1 = codirectional(v1, v2, tolerance);
517
- return sameOrigin && codirectional$1;
518
- }
101
+ function equal(ray1,ray2,tolerance){const v1=subtract(ray1[1],ray1[0]);const v2=subtract(ray2[1],ray2[0]);const sameOrigin=equal$2(ray1[0],ray2[0]);const codirectional$1=codirectional(v1,v2,tolerance);return sameOrigin&&codirectional$1}
519
102
 
520
103
  var ray = /*#__PURE__*/Object.freeze({
521
104
  __proto__: null,
522
105
  equal: equal
523
106
  });
524
107
 
525
- const KhanMath = {
526
- // Simplify formulas before display
527
- cleanMath: function (expr) {
528
- return typeof expr === "string" ? expr.replace(/\+\s*-/g, "- ").replace(/-\s*-/g, "+ ").replace(/\^1/g, "") : expr;
529
- },
530
- // Bound a number by 1e-6 and 1e20 to avoid exponents after toString
531
- bound: function (num) {
532
- if (num === 0) {
533
- return num;
534
- }
535
- if (num < 0) {
536
- return -KhanMath.bound(-num);
537
- }
538
- return Math.max(1e-6, Math.min(num, 1e20));
539
- },
540
- factorial: function (x) {
541
- if (x <= 1) {
542
- return x;
543
- }
544
- return x * KhanMath.factorial(x - 1);
545
- },
546
- getGCD: function (a, b) {
547
- if (arguments.length > 2) {
548
- // TODO(kevinb): rewrite using rest args instead of arguments
549
- // eslint-disable-next-line prefer-rest-params
550
- const rest = [].slice.call(arguments, 1);
551
- // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
552
- return KhanMath.getGCD(a, KhanMath.getGCD(...rest));
553
- }
554
- let mod;
555
- a = Math.abs(a);
556
- b = Math.abs(b);
557
-
558
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
559
- while (b) {
560
- mod = a % b;
561
- a = b;
562
- b = mod;
563
- }
564
- return a;
565
- },
566
- getLCM: function (a, b) {
567
- if (arguments.length > 2) {
568
- // TODO(kevinb): rewrite using rest args instead of arguments
569
- // eslint-disable-next-line prefer-rest-params
570
- const rest = [].slice.call(arguments, 1);
571
- // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
572
- return KhanMath.getLCM(a, KhanMath.getLCM(...rest));
573
- }
574
- return Math.abs(a * b) / KhanMath.getGCD(a, b);
575
- },
576
- primes: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97],
577
- isPrime: function (n) {
578
- if (n <= 1) {
579
- return false;
580
- }
581
- if (n < 101) {
582
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
583
- return !!$__default.default.grep(KhanMath.primes, function (p, i) {
584
- return Math.abs(p - n) <= 0.5;
585
- }).length;
586
- }
587
- if (n <= 1 || n > 2 && n % 2 === 0) {
588
- return false;
589
- }
590
- for (let i = 3, sqrt = Math.sqrt(n); i <= sqrt; i += 2) {
591
- if (n % i === 0) {
592
- return false;
593
- }
594
- }
595
- return true;
596
- },
597
- // @ts-expect-error - TS2366 - Function lacks ending return statement and return type does not include 'undefined'.
598
- getPrimeFactorization: function (number) {
599
- if (number === 1) {
600
- return [];
601
- }
602
- if (KhanMath.isPrime(number)) {
603
- return [number];
604
- }
605
- const maxf = Math.sqrt(number);
606
- for (let f = 2; f <= maxf; f++) {
607
- if (number % f === 0) {
608
- return $__default.default.merge(KhanMath.getPrimeFactorization(f), KhanMath.getPrimeFactorization(number / f));
609
- }
610
- }
611
- },
612
- // Round a number to the nearest increment
613
- // E.g., if increment = 30 and num = 40, return 30. if increment = 30 and
614
- // num = 45, return 60.
615
- roundToNearest: function (increment, num) {
616
- return Math.round(num / increment) * increment;
617
- },
618
- // Round a number to a certain number of decimal places
619
- roundTo: function (precision, num) {
620
- const factor = Math.pow(10, precision).toFixed(5);
621
- // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'number'. | TS2363 - The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type. | TS2363 - The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
622
- return Math.round((num * factor).toFixed(5)) / factor;
623
- },
624
- /**
625
- * Return a string of num rounded to a fixed precision decimal places,
626
- * with an approx symbol if num had to be rounded, and trailing 0s
627
- */
628
- toFixedApprox: function (num, precision) {
629
- // TODO(aria): Make this locale-dependent
630
- const fixedStr = num.toFixed(precision);
631
- if (equal$4(+fixedStr, num)) {
632
- return fixedStr;
633
- }
634
- return "\\approx " + fixedStr;
635
- },
636
- /**
637
- * Return a string of num rounded to precision decimal places, with an
638
- * approx symbol if num had to be rounded, but no trailing 0s if it was
639
- * not rounded.
640
- */
641
- roundToApprox: function (num, precision) {
642
- const fixed = KhanMath.roundTo(precision, num);
643
- if (equal$4(fixed, num)) {
644
- return String(fixed);
645
- }
646
- return KhanMath.toFixedApprox(num, precision);
647
- },
648
- // toFraction(4/8) => [1, 2]
649
- // toFraction(0.666) => [333, 500]
650
- // toFraction(0.666, 0.001) => [2, 3]
651
- //
652
- // tolerance can't be bigger than 1, sorry
653
- toFraction: function (decimal, tolerance) {
654
- if (tolerance == null) {
655
- tolerance = Math.pow(2, -46);
656
- }
657
- if (decimal < 0 || decimal > 1) {
658
- let fract = decimal % 1;
659
- fract += fract < 0 ? 1 : 0;
660
- const nd = KhanMath.toFraction(fract, tolerance);
661
- nd[0] += Math.round(decimal - fract) * nd[1];
662
- return nd;
663
- }
664
- if (Math.abs(Math.round(Number(decimal)) - decimal) <= tolerance) {
665
- return [Math.round(decimal), 1];
666
- }
667
- let loN = 0;
668
- let loD = 1;
669
- let hiN = 1;
670
- let hiD = 1;
671
- let midN = 1;
672
- let midD = 2;
673
-
674
- // eslint-disable-next-line no-constant-condition
675
- while (true) {
676
- if (Math.abs(Number(midN / midD) - decimal) <= tolerance) {
677
- return [midN, midD];
678
- }
679
- if (midN / midD < decimal) {
680
- loN = midN;
681
- loD = midD;
682
- } else {
683
- hiN = midN;
684
- hiD = midD;
685
- }
686
- midN = loN + hiN;
687
- midD = loD + hiD;
688
- }
689
- },
690
- // Returns the format (string) of a given numeric string
691
- // Note: purposively more inclusive than answer-types' predicate.forms
692
- // That is, it is not necessarily true that interpreted input are numeric
693
- getNumericFormat: function (text) {
694
- text = $__default.default.trim(text);
695
- text = text.replace(/\u2212/, "-").replace(/([+-])\s+/g, "$1");
696
- if (text.match(/^[+-]?\d+$/)) {
697
- return "integer";
698
- }
699
- if (text.match(/^[+-]?\d+\s+\d+\s*\/\s*\d+$/)) {
700
- return "mixed";
701
- }
702
- const fraction = text.match(/^[+-]?(\d+)\s*\/\s*(\d+)$/);
703
- if (fraction) {
704
- return parseFloat(fraction[1]) > parseFloat(fraction[2]) ? "improper" : "proper";
705
- }
706
- if (text.replace(/[,. ]/g, "").match(/^\d+$/)) {
707
- return "decimal";
708
- }
709
- if (text.match(/(pi?|\u03c0|t(?:au)?|\u03c4|pau)/)) {
710
- return "pi";
711
- }
712
- return null;
713
- },
714
- // Returns a string of the number in a specified format
715
- toNumericString: function (number$1, format) {
716
- if (number$1 == null) {
717
- return "";
718
- }
719
- if (number$1 === 0) {
720
- return "0"; // otherwise it might end up as 0% or 0pi
721
- }
722
- if (format === "percent") {
723
- return number$1 * 100 + "%";
724
- }
725
- if (format === "pi") {
726
- const fraction = toFraction(number$1 / Math.PI);
727
- const numerator = Math.abs(fraction[0]);
728
- const denominator = fraction[1];
729
- if (isInteger(numerator)) {
730
- const sign = number$1 < 0 ? "-" : "";
731
- const pi = "\u03C0";
732
- return sign + (numerator === 1 ? "" : numerator) + pi + (denominator === 1 ? "" : "/" + denominator);
733
- }
734
- }
735
- if (___default.default(["proper", "improper", "mixed", "fraction"]).contains(format)) {
736
- const fraction = toFraction(number$1);
737
- const numerator = Math.abs(fraction[0]);
738
- const denominator = fraction[1];
739
- const sign = number$1 < 0 ? "-" : "";
740
- if (denominator === 1) {
741
- return sign + numerator; // for integers, irrational, d > 1000
742
- }
743
- if (format === "mixed") {
744
- const modulus = numerator % denominator;
745
- const integer = (numerator - modulus) / denominator;
746
- return sign + (
747
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
748
- integer ? integer + " " : "") + modulus + "/" + denominator;
749
- } // otherwise proper, improper, or fraction
750
- return sign + numerator + "/" + denominator;
751
- }
752
-
753
- // otherwise (decimal, float, long long)
754
- return String(number$1);
755
- }
756
- };
757
- function sum(array) {
758
- return array.reduce(add, 0);
759
- }
760
- function add(a, b) {
761
- return a + b;
762
- }
763
-
764
- /**
765
- * A collection of geomtry-related utility functions
766
- */
767
-
768
-
769
- // This should really be a readonly tuple of [number, number]
770
-
771
- // Given a number, return whether it is positive (1), negative (-1), or zero (0)
772
- function sign(val) {
773
- if (perseusCore.approximateEqual(val, 0)) {
774
- return 0;
775
- }
776
- return val > 0 ? 1 : -1;
777
- }
778
-
779
- // Determine whether three points are collinear (0), for a clockwise turn (negative),
780
- // or counterclockwise turn (positive)
781
- function ccw(a, b, c) {
782
- return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
783
- }
784
- function collinear(a, b, c) {
785
- return perseusCore.approximateEqual(ccw(a, b, c), 0);
786
- }
787
-
788
- // Given rect bounding points A and B, whether point C is inside the rect
789
- function pointInRect(a, b, c) {
790
- return c[0] <= Math.max(a[0], b[0]) && c[0] >= Math.min(a[0], b[0]) && c[1] <= Math.max(a[1], b[1]) && c[1] >= Math.min(a[1], b[1]);
791
- }
792
-
793
- // Whether line segment AB intersects line segment CD
794
- // http://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
795
- function intersects(ab, cd) {
796
- const triplets = [[ab[0], ab[1], cd[0]], [ab[0], ab[1], cd[1]], [cd[0], cd[1], ab[0]], [cd[0], cd[1], ab[1]]];
797
- const orientations = ___default.default.map(triplets, function (triplet) {
798
- return sign(ccw(...triplet));
799
- });
800
- if (orientations[0] !== orientations[1] && orientations[2] !== orientations[3]) {
801
- return true;
802
- }
803
- for (let i = 0; i < 4; i++) {
804
- if (orientations[i] === 0 && pointInRect(...triplets[i])) {
805
- return true;
806
- }
807
- }
808
- return false;
809
- }
810
-
811
- // Whether any two sides of a polygon intersect each other
812
- function polygonSidesIntersect(vertices) {
813
- for (let i = 0; i < vertices.length; i++) {
814
- for (let k = i + 1; k < vertices.length; k++) {
815
- // If any two vertices are the same point, sides overlap
816
- if (equal$2(vertices[i], vertices[k])) {
817
- return true;
818
- }
819
-
820
- // Find the other end of the sides starting at vertices i and k
821
- const iNext = (i + 1) % vertices.length;
822
- const kNext = (k + 1) % vertices.length;
823
-
824
- // Adjacent sides always intersect (at the vertex); skip those
825
- if (iNext === k || kNext === i) {
826
- continue;
827
- }
828
- const side1 = [vertices[i], vertices[iNext]];
829
- const side2 = [vertices[k], vertices[kNext]];
830
- if (intersects(side1, side2)) {
831
- return true;
832
- }
833
- }
834
- }
835
- return false;
836
- }
837
- function vector(a, b) {
838
- return ___default.default.map(___default.default.zip(a, b), function (pair) {
839
- return pair[0] - pair[1];
840
- });
841
- }
842
- function reverseVector(vector) {
843
- return [-vector[0], -vector[1]];
844
- }
108
+ const KhanMath={cleanMath:function(expr){return typeof expr==="string"?expr.replace(/\+\s*-/g,"- ").replace(/-\s*-/g,"+ ").replace(/\^1/g,""):expr},bound:function(num){if(num===0){return num}if(num<0){return -KhanMath.bound(-num)}return Math.max(1e-6,Math.min(num,1e20))},factorial:function(x){if(x<=1){return x}return x*KhanMath.factorial(x-1)},getGCD:function(a,b){if(arguments.length>2){const rest=[].slice.call(arguments,1);return KhanMath.getGCD(a,KhanMath.getGCD(...rest))}let mod;a=Math.abs(a);b=Math.abs(b);while(b){mod=a%b;a=b;b=mod;}return a},getLCM:function(a,b){if(arguments.length>2){const rest=[].slice.call(arguments,1);return KhanMath.getLCM(a,KhanMath.getLCM(...rest))}return Math.abs(a*b)/KhanMath.getGCD(a,b)},primes:[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97],isPrime:function(n){if(n<=1){return false}if(n<101){return !!$__default.default.grep(KhanMath.primes,function(p,i){return Math.abs(p-n)<=.5}).length}if(n<=1||n>2&&n%2===0){return false}for(let i=3,sqrt=Math.sqrt(n);i<=sqrt;i+=2){if(n%i===0){return false}}return true},getPrimeFactorization:function(number){if(number===1){return []}if(KhanMath.isPrime(number)){return [number]}const maxf=Math.sqrt(number);for(let f=2;f<=maxf;f++){if(number%f===0){return $__default.default.merge(KhanMath.getPrimeFactorization(f),KhanMath.getPrimeFactorization(number/f))}}},roundToNearest:function(increment,num){return Math.round(num/increment)*increment},roundTo:function(precision,num){const factor=Math.pow(10,precision).toFixed(5);return Math.round((num*factor).toFixed(5))/factor},toFixedApprox:function(num,precision){const fixedStr=num.toFixed(precision);if(equal$4(+fixedStr,num)){return fixedStr}return "\\approx "+fixedStr},roundToApprox:function(num,precision){const fixed=KhanMath.roundTo(precision,num);if(equal$4(fixed,num)){return String(fixed)}return KhanMath.toFixedApprox(num,precision)},toFraction:function(decimal,tolerance){if(tolerance==null){tolerance=Math.pow(2,-46);}if(decimal<0||decimal>1){let fract=decimal%1;fract+=fract<0?1:0;const nd=KhanMath.toFraction(fract,tolerance);nd[0]+=Math.round(decimal-fract)*nd[1];return nd}if(Math.abs(Math.round(Number(decimal))-decimal)<=tolerance){return [Math.round(decimal),1]}let loN=0;let loD=1;let hiN=1;let hiD=1;let midN=1;let midD=2;while(true){if(Math.abs(Number(midN/midD)-decimal)<=tolerance){return [midN,midD]}if(midN/midD<decimal){loN=midN;loD=midD;}else {hiN=midN;hiD=midD;}midN=loN+hiN;midD=loD+hiD;}},getNumericFormat:function(text){text=$__default.default.trim(text);text=text.replace(/\u2212/,"-").replace(/([+-])\s+/g,"$1");if(text.match(/^[+-]?\d+$/)){return "integer"}if(text.match(/^[+-]?\d+\s+\d+\s*\/\s*\d+$/)){return "mixed"}const fraction=text.match(/^[+-]?(\d+)\s*\/\s*(\d+)$/);if(fraction){return parseFloat(fraction[1])>parseFloat(fraction[2])?"improper":"proper"}if(text.replace(/[,. ]/g,"").match(/^\d+$/)){return "decimal"}if(text.match(/(pi?|\u03c0|t(?:au)?|\u03c4|pau)/)){return "pi"}return null},toNumericString:function(number$1,format){if(number$1==null){return ""}if(number$1===0){return "0"}if(format==="percent"){return number$1*100+"%"}if(format==="pi"){const fraction=toFraction(number$1/Math.PI);const numerator=Math.abs(fraction[0]);const denominator=fraction[1];if(isInteger(numerator)){const sign=number$1<0?"-":"";const pi="π";return sign+(numerator===1?"":numerator)+pi+(denominator===1?"":"/"+denominator)}}if(___default.default(["proper","improper","mixed","fraction"]).contains(format)){const fraction=toFraction(number$1);const numerator=Math.abs(fraction[0]);const denominator=fraction[1];const sign=number$1<0?"-":"";if(denominator===1){return sign+numerator}if(format==="mixed"){const modulus=numerator%denominator;const integer=(numerator-modulus)/denominator;return sign+(integer?integer+" ":"")+modulus+"/"+denominator}return sign+numerator+"/"+denominator}return String(number$1)}};function sum(array){return array.reduce(add,0)}function add(a,b){return a+b}
845
109
 
846
- // Returns whether connecting the given sequence of `points` forms a clockwise
847
- // path (assuming a closed loop, where the last point connects back to the
848
- // first).
849
- function clockwise(points) {
850
- const segments = ___default.default.zip(points, points.slice(1).concat(points.slice(0, 1)));
851
- const areas = ___default.default.map(segments, function (segment) {
852
- const p1 = segment[0];
853
- const p2 = segment[1];
854
- return (p2[0] - p1[0]) * (p2[1] + p1[1]);
855
- });
856
- return sum(areas) > 0;
857
- }
858
- function magnitude(v) {
859
- return Math.sqrt(___default.default.reduce(v, function (memo, el) {
860
- // @ts-expect-error - TS2345 - Argument of type 'Coord' is not assignable to parameter of type 'number'.
861
- return memo + Math.pow(el, 2);
862
- }, 0));
863
- }
864
- function dotProduct(a, b) {
865
- return ___default.default.reduce(___default.default.zip(a, b), function (memo, pair) {
866
- return memo + pair[0] * pair[1];
867
- }, 0);
868
- }
869
- function sideLengths(coords) {
870
- const segments = ___default.default.zip(coords, rotate(coords));
871
- return segments.map(function (segment) {
872
- // @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
873
- return magnitude(vector(...segment));
874
- });
875
- }
876
-
877
- // Based on http://math.stackexchange.com/a/151149
878
- function angleMeasures(coords) {
879
- const triplets = ___default.default.zip(rotate(coords, -1), coords, rotate(coords, 1));
880
- const offsets = ___default.default.map(triplets, function (triplet) {
881
- const p = vector(triplet[1], triplet[0]);
882
- const q = vector(triplet[2], triplet[1]);
883
- // @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'Coord'. | TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'.
884
- const raw = Math.acos(dotProduct(p, q) / (magnitude(p) * magnitude(q)));
885
- // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
886
- return sign(ccw(...triplet)) > 0 ? raw : -raw;
887
- });
888
- const sum = ___default.default.reduce(offsets, function (memo, arg) {
889
- return memo + arg;
890
- }, 0);
891
- return ___default.default.map(offsets, function (offset) {
892
- return sum > 0 ? Math.PI - offset : Math.PI + offset;
893
- });
894
- }
895
-
896
- // Whether two polygons are similar (or if specified, congruent)
897
- function similar(coords1, coords2, tolerance) {
898
- if (coords1.length !== coords2.length) {
899
- return false;
900
- }
901
- const n = coords1.length;
902
- const angles1 = angleMeasures(coords1);
903
- const angles2 = angleMeasures(coords2);
904
- const sides1 = sideLengths(coords1);
905
- const sides2 = sideLengths(coords2);
906
- for (let i = 0; i < 2 * n; i++) {
907
- let angles = angles2.slice();
908
- let sides = sides2.slice();
909
-
910
- // Reverse angles and sides to allow matching reflected polygons
911
- if (i >= n) {
912
- angles.reverse();
913
- sides.reverse();
914
- // Since sides are calculated from two coordinates,
915
- // simply reversing results in an off by one error
916
- // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
917
- sides = rotate(sides, 1);
918
- }
919
-
920
- // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
921
- angles = rotate(angles, i);
922
- // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
923
- sides = rotate(sides, i);
924
- if (perseusCore.approximateDeepEqual(angles1, angles)) {
925
- const sidePairs = ___default.default.zip(sides1, sides);
926
- const factors = ___default.default.map(sidePairs, function (pair) {
927
- return pair[0] / pair[1];
928
- });
929
- const same = ___default.default.all(factors, function (factor) {
930
- return perseusCore.approximateEqual(factors[0], factor);
931
- });
932
- const congruentEnough = ___default.default.all(sidePairs, function (pair) {
933
- return equal$4(pair[0], pair[1], tolerance);
934
- });
935
- if (same && congruentEnough) {
936
- return true;
937
- }
938
- }
939
- }
940
- return false;
941
- }
942
-
943
- // Given triangle with sides ABC return angle opposite side C in degrees
944
- function lawOfCosines(a, b, c) {
945
- return Math.acos((a * a + b * b - c * c) / (2 * a * b)) * 180 / Math.PI;
946
- }
947
- function canonicalSineCoefficients(_ref) {
948
- let [amplitude, angularFrequency, phase, verticalOffset] = _ref;
949
- // For a curve of the form f(x) = a * Sin(b * x - c) + d,
950
- // this function ensures that a, b > 0, and c is its
951
- // smallest possible positive value.
952
-
953
- // Guarantee a > 0
954
- if (amplitude < 0) {
955
- amplitude *= -1;
956
- angularFrequency *= -1;
957
- phase *= -1;
958
- }
959
- const period = 2 * Math.PI;
960
- // Guarantee b > 0
961
- if (angularFrequency < 0) {
962
- angularFrequency *= -1;
963
- phase *= -1;
964
- phase += period / 2;
965
- }
966
-
967
- // Guarantee c is smallest possible positive value
968
- while (phase > 0) {
969
- phase -= period;
970
- }
971
- while (phase < 0) {
972
- phase += period;
973
- }
974
- return [amplitude, angularFrequency, phase, verticalOffset];
975
- }
976
-
977
- // e.g. rotate([1, 2, 3]) -> [2, 3, 1]
978
- function rotate(array, n) {
979
- n = typeof n === "undefined" ? 1 : n % array.length;
980
- return array.slice(n).concat(array.slice(0, n));
981
- }
982
- function getLineEquation(first, second) {
983
- if (perseusCore.approximateEqual(first[0], second[0])) {
984
- return "x = " + first[0].toFixed(3);
985
- }
986
- const m = (second[1] - first[1]) / (second[0] - first[0]);
987
- const b = first[1] - m * first[0];
988
- return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
989
- }
990
-
991
- // Stolen from the wikipedia article
992
- // http://en.wikipedia.org/wiki/Line-line_intersection
993
- function getLineIntersection(
994
- // TODO(LP-10725): update these to be 2-tuples
995
- firstPoints, secondPoints) {
996
- const x1 = firstPoints[0][0];
997
- const y1 = firstPoints[0][1];
998
- const x2 = firstPoints[1][0];
999
- const y2 = firstPoints[1][1];
1000
- const x3 = secondPoints[0][0];
1001
- const y3 = secondPoints[0][1];
1002
- const x4 = secondPoints[1][0];
1003
- const y4 = secondPoints[1][1];
1004
- const determinant = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
1005
- if (Math.abs(determinant) < 1e-9) {
1006
- // Lines are parallel
1007
- return null;
1008
- }
1009
- const x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / determinant;
1010
- const y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / determinant;
1011
- return [x, y];
1012
- }
1013
- function getLineIntersectionString(firstPoints, secondPoints) {
1014
- const intersection = getLineIntersection(firstPoints, secondPoints);
1015
- if (intersection === null) {
1016
- return "Lines are parallel";
1017
- }
1018
- const [x, y] = intersection;
1019
- return "Intersection: (" + x.toFixed(3) + ", " + y.toFixed(3) + ")";
1020
- }
110
+ function sign(val){if(perseusCore.approximateEqual(val,0)){return 0}return val>0?1:-1}function ccw(a,b,c){return (b[0]-a[0])*(c[1]-a[1])-(c[0]-a[0])*(b[1]-a[1])}function collinear(a,b,c){return perseusCore.approximateEqual(ccw(a,b,c),0)}function pointInRect(a,b,c){return c[0]<=Math.max(a[0],b[0])&&c[0]>=Math.min(a[0],b[0])&&c[1]<=Math.max(a[1],b[1])&&c[1]>=Math.min(a[1],b[1])}function intersects(ab,cd){const triplets=[[ab[0],ab[1],cd[0]],[ab[0],ab[1],cd[1]],[cd[0],cd[1],ab[0]],[cd[0],cd[1],ab[1]]];const orientations=___default.default.map(triplets,function(triplet){return sign(ccw(...triplet))});if(orientations[0]!==orientations[1]&&orientations[2]!==orientations[3]){return true}for(let i=0;i<4;i++){if(orientations[i]===0&&pointInRect(...triplets[i])){return true}}return false}function polygonSidesIntersect(vertices){for(let i=0;i<vertices.length;i++){for(let k=i+1;k<vertices.length;k++){if(equal$2(vertices[i],vertices[k])){return true}const iNext=(i+1)%vertices.length;const kNext=(k+1)%vertices.length;if(iNext===k||kNext===i){continue}const side1=[vertices[i],vertices[iNext]];const side2=[vertices[k],vertices[kNext]];if(intersects(side1,side2)){return true}}}return false}function vector(a,b){return ___default.default.map(___default.default.zip(a,b),function(pair){return pair[0]-pair[1]})}function reverseVector(vector){return [-vector[0],-vector[1]]}function clockwise(points){const segments=___default.default.zip(points,points.slice(1).concat(points.slice(0,1)));const areas=___default.default.map(segments,function(segment){const p1=segment[0];const p2=segment[1];return (p2[0]-p1[0])*(p2[1]+p1[1])});return sum(areas)>0}function magnitude(v){return Math.sqrt(___default.default.reduce(v,function(memo,el){return memo+Math.pow(el,2)},0))}function dotProduct(a,b){return ___default.default.reduce(___default.default.zip(a,b),function(memo,pair){return memo+pair[0]*pair[1]},0)}function sideLengths(coords){const segments=___default.default.zip(coords,rotate(coords));return segments.map(function(segment){return magnitude(vector(...segment))})}function angleMeasures(coords){const triplets=___default.default.zip(rotate(coords,-1),coords,rotate(coords,1));const offsets=___default.default.map(triplets,function(triplet){const p=vector(triplet[1],triplet[0]);const q=vector(triplet[2],triplet[1]);const raw=Math.acos(dotProduct(p,q)/(magnitude(p)*magnitude(q)));return sign(ccw(...triplet))>0?raw:-raw});const sum=___default.default.reduce(offsets,function(memo,arg){return memo+arg},0);return ___default.default.map(offsets,function(offset){return sum>0?Math.PI-offset:Math.PI+offset})}function similar(coords1,coords2,tolerance){if(coords1.length!==coords2.length){return false}const n=coords1.length;const angles1=angleMeasures(coords1);const angles2=angleMeasures(coords2);const sides1=sideLengths(coords1);const sides2=sideLengths(coords2);for(let i=0;i<2*n;i++){let angles=angles2.slice();let sides=sides2.slice();if(i>=n){angles.reverse();sides.reverse();sides=rotate(sides,1);}angles=rotate(angles,i);sides=rotate(sides,i);if(perseusCore.approximateDeepEqual(angles1,angles)){const sidePairs=___default.default.zip(sides1,sides);const factors=___default.default.map(sidePairs,function(pair){return pair[0]/pair[1]});const same=___default.default.all(factors,function(factor){return perseusCore.approximateEqual(factors[0],factor)});const congruentEnough=___default.default.all(sidePairs,function(pair){return equal$4(pair[0],pair[1],tolerance)});if(same&&congruentEnough){return true}}}return false}function lawOfCosines(a,b,c){return Math.acos((a*a+b*b-c*c)/(2*a*b))*180/Math.PI}function canonicalSineCoefficients([amplitude,angularFrequency,phase,verticalOffset]){if(amplitude<0){amplitude*=-1;angularFrequency*=-1;phase*=-1;}const period=2*Math.PI;if(angularFrequency<0){angularFrequency*=-1;phase*=-1;phase+=period/2;}while(phase>0){phase-=period;}while(phase<0){phase+=period;}return [amplitude,angularFrequency,phase,verticalOffset]}function rotate(array,n){n=typeof n==="undefined"?1:n%array.length;return array.slice(n).concat(array.slice(0,n))}function getLineEquation(first,second){if(perseusCore.approximateEqual(first[0],second[0])){return "x = "+first[0].toFixed(3)}const m=(second[1]-first[1])/(second[0]-first[0]);const b=first[1]-m*first[0];return "y = "+m.toFixed(3)+"x + "+b.toFixed(3)}function getLineIntersection(firstPoints,secondPoints){const x1=firstPoints[0][0];const y1=firstPoints[0][1];const x2=firstPoints[1][0];const y2=firstPoints[1][1];const x3=secondPoints[0][0];const y3=secondPoints[0][1];const x4=secondPoints[1][0];const y4=secondPoints[1][1];const determinant=(x1-x2)*(y3-y4)-(y1-y2)*(x3-x4);if(Math.abs(determinant)<1e-9){return null}const x=((x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4))/determinant;const y=((x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4))/determinant;return [x,y]}function getLineIntersectionString(firstPoints,secondPoints){const intersection=getLineIntersection(firstPoints,secondPoints);if(intersection===null){return "Lines are parallel"}const[x,y]=intersection;return "Intersection: ("+x.toFixed(3)+", "+y.toFixed(3)+")"}
1021
111
 
1022
112
  var geometry = /*#__PURE__*/Object.freeze({
1023
113
  __proto__: null,
@@ -1040,68 +130,7 @@ var geometry = /*#__PURE__*/Object.freeze({
1040
130
  vector: vector
1041
131
  });
1042
132
 
1043
- // This file contains helper functions for working with angles.
1044
-
1045
- function convertDegreesToRadians(degrees) {
1046
- return degrees / 180 * Math.PI;
1047
- }
1048
- function convertRadiansToDegrees(radians) {
1049
- const degree = radians / Math.PI * 180;
1050
- // Account for floating point errors.
1051
- return Number(degree.toPrecision(15));
1052
- }
1053
-
1054
- // Returns a value between -180 and 180, inclusive. The angle is measured
1055
- // between the positive x-axis and the given vector.
1056
- function calculateAngleInDegrees(_ref) {
1057
- let [x, y] = _ref;
1058
- return Math.atan2(y, x) * 180 / Math.PI;
1059
- }
1060
-
1061
- // Converts polar coordinates to cartesian. The th(eta) parameter is in degrees.
1062
- function polar(r, th) {
1063
- if (typeof r === "number") {
1064
- r = [r, r];
1065
- }
1066
- th = th * Math.PI / 180;
1067
- return [r[0] * Math.cos(th), r[1] * Math.sin(th)];
1068
- }
1069
- // This function calculates the angle between two points and an optional vertex.
1070
- // If the vertex is not provided, the angle is measured between the two points.
1071
- // This does not account for reflex angles or clockwise position.
1072
- const getAngleFromVertex = (point, vertex) => {
1073
- const x = point[0] - vertex[0];
1074
- const y = point[1] - vertex[1];
1075
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
1076
- if (!x && !y) {
1077
- return 0;
1078
- }
1079
- return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
1080
- };
1081
-
1082
- // This function calculates the clockwise angle between three points,
1083
- // and is used to generate the labels and equation strings of the
1084
- // current angle for the interactive graph.
1085
- const getClockwiseAngle = function (coords) {
1086
- let allowReflexAngles = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
1087
- const coordsCopy = [...coords];
1088
- // The coords are saved as [point1, vertex, point2] in the interactive graph
1089
- const areClockwise = clockwise([coordsCopy[0], coordsCopy[2], coordsCopy[1]]);
1090
-
1091
- // We may need to reverse the coordinates if we allow
1092
- // reflex angles and the points are not in clockwise order.
1093
- const shouldReverseCoords = !areClockwise || allowReflexAngles;
1094
-
1095
- // Reverse the coordinates accordingly to ensure the angle is calculated correctly
1096
- const clockwiseCoords = shouldReverseCoords ? coordsCopy.reverse() : coordsCopy;
1097
-
1098
- // Calculate the angles between the two points and get the difference
1099
- // between the two angles to get the clockwise angle.
1100
- const startAngle = getAngleFromVertex(clockwiseCoords[0], clockwiseCoords[1]);
1101
- const endAngle = getAngleFromVertex(clockwiseCoords[2], clockwiseCoords[1]);
1102
- const angle = (startAngle + 360 - endAngle) % 360;
1103
- return angle;
1104
- };
133
+ function convertDegreesToRadians(degrees){return degrees/180*Math.PI}function convertRadiansToDegrees(radians){const degree=radians/Math.PI*180;return Number(degree.toPrecision(15))}function calculateAngleInDegrees([x,y]){return Math.atan2(y,x)*180/Math.PI}function polar(r,th){if(typeof r==="number"){r=[r,r];}th=th*Math.PI/180;return [r[0]*Math.cos(th),r[1]*Math.sin(th)]}const getAngleFromVertex=(point,vertex)=>{const x=point[0]-vertex[0];const y=point[1]-vertex[1];if(!x&&!y){return 0}return (180+Math.atan2(-y,-x)*180/Math.PI+360)%360};const getClockwiseAngle=(coords,allowReflexAngles=false)=>{const coordsCopy=[...coords];const areClockwise=clockwise([coordsCopy[0],coordsCopy[2],coordsCopy[1]]);const shouldReverseCoords=!areClockwise||allowReflexAngles;const clockwiseCoords=shouldReverseCoords?coordsCopy.reverse():coordsCopy;const startAngle=getAngleFromVertex(clockwiseCoords[0],clockwiseCoords[1]);const endAngle=getAngleFromVertex(clockwiseCoords[2],clockwiseCoords[1]);const angle=(startAngle+360-endAngle)%360;return angle};
1105
134
 
1106
135
  var angles = /*#__PURE__*/Object.freeze({
1107
136
  __proto__: null,
@@ -1113,37 +142,7 @@ var angles = /*#__PURE__*/Object.freeze({
1113
142
  polar: polar
1114
143
  });
1115
144
 
1116
- // TODO: there's another, very similar getSinusoidCoefficients function
1117
- // they should probably be merged
1118
- function getSinusoidCoefficients(coords) {
1119
- // It's assumed that p1 is the root and p2 is the first peak
1120
- const p1 = coords[0];
1121
- const p2 = coords[1];
1122
-
1123
- // Resulting coefficients are canonical for this sine curve
1124
- const amplitude = p2[1] - p1[1];
1125
- const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
1126
- const phase = p1[0] * angularFrequency;
1127
- const verticalOffset = p1[1];
1128
- return [amplitude, angularFrequency, phase, verticalOffset];
1129
- }
1130
- // TODO: there's another, very similar getQuadraticCoefficients function
1131
- // they should probably be merged
1132
- function getQuadraticCoefficients(coords) {
1133
- const p1 = coords[0];
1134
- const p2 = coords[1];
1135
- const p3 = coords[2];
1136
- const denom = (p1[0] - p2[0]) * (p1[0] - p3[0]) * (p2[0] - p3[0]);
1137
- if (denom === 0) {
1138
- // Many of the callers assume that the return value is always defined.
1139
- // @ts-expect-error - TS2322 - Type 'undefined' is not assignable to type 'QuadraticCoefficient'.
1140
- return;
1141
- }
1142
- const a = (p3[0] * (p2[1] - p1[1]) + p2[0] * (p1[1] - p3[1]) + p1[0] * (p3[1] - p2[1])) / denom;
1143
- const b = (p3[0] * p3[0] * (p1[1] - p2[1]) + p2[0] * p2[0] * (p3[1] - p1[1]) + p1[0] * p1[0] * (p2[1] - p3[1])) / denom;
1144
- const c = (p2[0] * p3[0] * (p2[0] - p3[0]) * p1[1] + p3[0] * p1[0] * (p3[0] - p1[0]) * p2[1] + p1[0] * p2[0] * (p1[0] - p2[0]) * p3[1]) / denom;
1145
- return [a, b, c];
1146
- }
145
+ function getSinusoidCoefficients(coords){const p1=coords[0];const p2=coords[1];const amplitude=p2[1]-p1[1];const angularFrequency=Math.PI/(2*(p2[0]-p1[0]));const phase=p1[0]*angularFrequency;const verticalOffset=p1[1];return [amplitude,angularFrequency,phase,verticalOffset]}function getQuadraticCoefficients(coords){const p1=coords[0];const p2=coords[1];const p3=coords[2];const denom=(p1[0]-p2[0])*(p1[0]-p3[0])*(p2[0]-p3[0]);if(denom===0){return}const a=(p3[0]*(p2[1]-p1[1])+p2[0]*(p1[1]-p3[1])+p1[0]*(p3[1]-p2[1]))/denom;const b=(p3[0]*p3[0]*(p1[1]-p2[1])+p2[0]*p2[0]*(p3[1]-p1[1])+p1[0]*p1[0]*(p2[1]-p3[1]))/denom;const c=(p2[0]*p3[0]*(p2[0]-p3[0])*p1[1]+p3[0]*p1[0]*(p3[0]-p1[0])*p2[1]+p1[0]*p2[0]*(p1[0]-p2[0])*p3[1])/denom;return [a,b,c]}
1147
146
 
1148
147
  var coefficients = /*#__PURE__*/Object.freeze({
1149
148
  __proto__: null,