@escapace/minimum-perimeter-triangle 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,590 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ lineTangentToHull: () => lineTangentToHull,
24
+ minTriangle: () => minTriangle,
25
+ minTriangleWithBase: () => minTriangleWithBase
26
+ });
27
+ module.exports = __toCommonJS(src_exports);
28
+
29
+ // src/line.ts
30
+ var Line = class {
31
+ start;
32
+ end;
33
+ delta;
34
+ constructor(start, end) {
35
+ this.start = start;
36
+ this.end = end;
37
+ this.delta = end.minus(start);
38
+ }
39
+ /**
40
+ * Length of a line is the length between its two defining points
41
+ */
42
+ get length() {
43
+ return this.delta.norm;
44
+ }
45
+ evaluate(t) {
46
+ return this.start.plus(this.delta.times(t));
47
+ }
48
+ distanceToPoint(p) {
49
+ return Math.abs(p.cross(this.delta) - this.start.cross(this.end)) / this.delta.norm;
50
+ }
51
+ pointOnSide(p, err = 0) {
52
+ const num = this.start.cross(this.end) - p.cross(this.delta);
53
+ if (num === 0 || Math.abs(num) / this.delta.norm < err) {
54
+ return 0 /* Top */;
55
+ }
56
+ return num > 0 ? 1 /* Left */ : -1 /* Right */;
57
+ }
58
+ pointOnTop(p, err) {
59
+ return this.pointOnSide(p, err) === 0 /* Top */;
60
+ }
61
+ overlaps(that, err) {
62
+ return this.pointOnTop(that.start, err) && this.pointOnTop(that.end, err);
63
+ }
64
+ /**
65
+ * If alpha is less than deviationFromZeroAngle, the 2 lines are
66
+ * considered parallel.
67
+ * _______________________________
68
+ * alpha (/
69
+ * /
70
+ * /
71
+ * /
72
+ */
73
+ parallel(that, deviationFromZeroAngle) {
74
+ const d = Math.abs(this.delta.cross(that.delta));
75
+ return d === 0 || d < this.length * this.length * Math.sin(deviationFromZeroAngle);
76
+ }
77
+ intersectionParameter(that, err) {
78
+ const d = this.delta.cross(that.delta);
79
+ if (d === 0 || Math.abs(d) < err) {
80
+ return null;
81
+ }
82
+ const dStart = this.start.minus(that.start);
83
+ return that.delta.cross(dStart) / d;
84
+ }
85
+ closestPointParam(p) {
86
+ return this.delta.dot(p.minus(this.start)) / this.delta.normSquared;
87
+ }
88
+ closestPoint(p) {
89
+ return this.evaluate(this.closestPointParam(p));
90
+ }
91
+ intersectionPoint(that, err) {
92
+ const t = this.intersectionParameter(that, err);
93
+ return t === null ? null : this.evaluate(t);
94
+ }
95
+ };
96
+
97
+ // src/vec2.ts
98
+ var Vec2 = class {
99
+ _x;
100
+ _y;
101
+ _normSquared;
102
+ _norm;
103
+ _normalized;
104
+ constructor(x, y) {
105
+ this._x = x;
106
+ this._y = y;
107
+ }
108
+ times(s) {
109
+ return new Vec2(this._x * s, this._y * s);
110
+ }
111
+ over(s) {
112
+ return new Vec2(this._x / s, this._y / s);
113
+ }
114
+ get x() {
115
+ return this._x;
116
+ }
117
+ get y() {
118
+ return this._y;
119
+ }
120
+ plus(that) {
121
+ return new Vec2(this._x + that._x, this._y + that._y);
122
+ }
123
+ minus(that) {
124
+ return new Vec2(this._x - that._x, this._y - that._y);
125
+ }
126
+ get normSquared() {
127
+ return this._normSquared === void 0 ? this._normSquared = this.dot(this) : this._normSquared;
128
+ }
129
+ get norm() {
130
+ return this._norm === void 0 ? this._norm = Math.sqrt(this.normSquared) : this._norm;
131
+ }
132
+ get normalized() {
133
+ return this._normalized === void 0 ? this._normalized = this.over(this.norm) : this._normalized;
134
+ }
135
+ dot(that) {
136
+ return this._x * that._x + this._y * that._y;
137
+ }
138
+ cross(that) {
139
+ return this._x * that._y - this._y * that._x;
140
+ }
141
+ equals(that, err) {
142
+ if (err === 0) {
143
+ return this.x === that.x && this.y === that.y;
144
+ }
145
+ return this.minus(that).normSquared < err * err;
146
+ }
147
+ normal() {
148
+ return new Vec2(this._y, -this._x);
149
+ }
150
+ toString() {
151
+ return `(${this.x}, ${this.y})`;
152
+ }
153
+ };
154
+
155
+ // src/inscribe.ts
156
+ var Wedge = class {
157
+ leftArm;
158
+ rightArm;
159
+ isDegenerate;
160
+ constructor(leftArm, rightArm, isDegenerate = false) {
161
+ this.leftArm = leftArm;
162
+ this.rightArm = rightArm;
163
+ this.isDegenerate = isDegenerate;
164
+ }
165
+ static new(leftArm, rightArm, err) {
166
+ if (leftArm === null || rightArm === null) {
167
+ return null;
168
+ }
169
+ if (err !== 0 && leftArm.overlaps(rightArm, err)) {
170
+ return null;
171
+ }
172
+ const deviationFromZeroAngle = 0.1 / (leftArm.length * rightArm.length);
173
+ if (leftArm.parallel(rightArm, deviationFromZeroAngle)) {
174
+ const middle = new Line(leftArm.evaluate(0.5), rightArm.evaluate(0.5));
175
+ const p = middle.evaluate(0.5);
176
+ const sideLeft = leftArm.pointOnSide(p, err);
177
+ const sideRight = rightArm.pointOnSide(p, err);
178
+ if (sideLeft === 0 /* Top */ || sideRight === 0 /* Top */) {
179
+ throw new Error();
180
+ }
181
+ return sideLeft !== sideRight ? new Wedge(leftArm, rightArm, true) : new Wedge(leftArm, new Line(rightArm.end, rightArm.start));
182
+ }
183
+ const tLA = leftArm.intersectionParameter(rightArm, 0);
184
+ const tRA = rightArm.intersectionParameter(leftArm, 0);
185
+ if (tLA === 0.5 || tRA === 0.5) {
186
+ return null;
187
+ }
188
+ const W = leftArm.evaluate(tLA);
189
+ const eLA = tLA < 1 - tLA ? leftArm.end : leftArm.start;
190
+ const eRA = tRA < 1 - tRA ? rightArm.end : rightArm.start;
191
+ return new Wedge(new Line(W, eLA), new Line(W, eRA));
192
+ }
193
+ formTriangle(line, err) {
194
+ const thin = this.leftArm.parallel(line, 0.1 / (this.leftArm.length * line.length)) || this.rightArm.parallel(line, 0.1 / (this.rightArm.length * line.length));
195
+ if (thin) {
196
+ return false;
197
+ }
198
+ const A = line.intersectionPoint(this.leftArm, 0);
199
+ const B = line.intersectionPoint(this.rightArm, 0);
200
+ if (this.isDegenerate) {
201
+ return !A.equals(B, err);
202
+ }
203
+ const C = this.leftArm.intersectionPoint(this.rightArm, 0);
204
+ return !C.equals(A, err) && !C.equals(B, err) && !A.equals(B, err) && !new Line(A, B).pointOnTop(C, err);
205
+ }
206
+ looselyContains(p, err) {
207
+ const pLeft = this.leftArm.pointOnSide(p, err);
208
+ const pRight = this.rightArm.pointOnSide(p, err);
209
+ if (pLeft === 0 /* Top */ || pRight === 0 /* Top */) {
210
+ return true;
211
+ }
212
+ if (pLeft === pRight) {
213
+ return false;
214
+ }
215
+ return this.isDegenerate ? (
216
+ // degenerate + different sides => true
217
+ true
218
+ ) : (
219
+ // 2. (Because the arms intersect)
220
+ // Projection params of the point onto the arms must be larger than 0
221
+ this.leftArm.closestPointParam(p) >= 0 && this.rightArm.closestPointParam(p) >= 0
222
+ );
223
+ }
224
+ strictlyContains(p, err) {
225
+ const pLeft = this.leftArm.pointOnSide(p, err);
226
+ const pRight = this.rightArm.pointOnSide(p, err);
227
+ if (pLeft === 0 /* Top */ || pRight === 0 /* Top */) {
228
+ return false;
229
+ }
230
+ if (pLeft === pRight) {
231
+ return false;
232
+ }
233
+ return this.isDegenerate ? (
234
+ // degenerate + different sides => true
235
+ true
236
+ ) : (
237
+ // 2. (Because the arms intersect)
238
+ // Projection params of the point onto the arms must be larger than 0
239
+ this.leftArm.closestPointParam(p) >= 0 && this.rightArm.closestPointParam(p) >= 0
240
+ );
241
+ }
242
+ // While fitting circles into a wedge
243
+ // There are four distinct cases:
244
+ // 1. Wedge is degenerate and additional element is a point
245
+ // 2. Wedge is degenerate and additional element is a line
246
+ // 3. Wedge is non-degenerate and additional element is a point
247
+ // 4. Wedge is non-degenerate and additional element is a line
248
+ // according to these assumptions, the following methods are named
249
+ fit_Dp(p, err) {
250
+ if (!this.strictlyContains(p, err)) {
251
+ return null;
252
+ }
253
+ const A = this.rightArm.closestPoint(p);
254
+ const Ap = this.leftArm.closestPoint(A);
255
+ const I = A.plus(Ap).over(2);
256
+ const r = A.minus(Ap).norm / 2;
257
+ const a = this.rightArm.delta.normSquared;
258
+ const b = I.minus(p).dot(this.rightArm.delta) * 2;
259
+ const c = I.minus(p).normSquared - r * r;
260
+ const discriminant = b * b - 4 * a * c;
261
+ if (discriminant < (-10) ** -5) {
262
+ return null;
263
+ }
264
+ const t = [];
265
+ if (Math.abs(discriminant) < 10 ** -5) {
266
+ t.push(-b / (2 * a));
267
+ } else {
268
+ t.push((-b + Math.sqrt(discriminant)) / (2 * a));
269
+ t.push((-b - Math.sqrt(discriminant)) / (2 * a));
270
+ }
271
+ const result = [];
272
+ t.forEach((t0) => {
273
+ const O = this.rightArm.delta.times(t0).plus(I);
274
+ result.push({
275
+ circle: { centre: O, r },
276
+ tangent: new Line(p, p.plus(O.minus(p).normal()))
277
+ });
278
+ });
279
+ return result;
280
+ }
281
+ fit_Dl(l, err) {
282
+ if (!this.formTriangle(l, err)) {
283
+ return null;
284
+ }
285
+ const A = l.intersectionPoint(this.rightArm, 0);
286
+ const B = l.intersectionPoint(this.leftArm, 0);
287
+ const AB = new Line(A, B);
288
+ const Ap = this.leftArm.closestPoint(A);
289
+ const I = A.plus(Ap).over(2);
290
+ const r = A.minus(Ap).norm / 2;
291
+ const t1 = (AB.delta.cross(A.minus(I)) + r * AB.delta.norm) / AB.delta.cross(this.rightArm.delta);
292
+ const t2 = (AB.delta.cross(A.minus(I)) - r * AB.delta.norm) / AB.delta.cross(this.rightArm.delta);
293
+ const o1 = this.rightArm.delta.times(t1).plus(I);
294
+ const o2 = this.rightArm.delta.times(t2).plus(I);
295
+ return [
296
+ {
297
+ circle: { centre: o1, r },
298
+ tangentParameter: l.closestPointParam(o1)
299
+ },
300
+ {
301
+ circle: { centre: o2, r },
302
+ tangentParameter: l.closestPointParam(o2)
303
+ }
304
+ ];
305
+ }
306
+ fit_NDp(p, err) {
307
+ if (!this.strictlyContains(p, err)) {
308
+ return null;
309
+ }
310
+ const C = this.leftArm.start;
311
+ const A = this.leftArm.end;
312
+ const B = this.rightArm.end;
313
+ const a = C.minus(B).norm;
314
+ const b = C.minus(A).norm;
315
+ const D = A.minus(B).times(a / (a + b)).plus(B);
316
+ const bisector = new Line(C, D);
317
+ const eA = D.minus(C).normSquared - (A.minus(C).cross(D.minus(C)) / b) ** 2;
318
+ const eB = D.minus(C).dot(C.minus(p)) * 2;
319
+ const eC = C.minus(p).normSquared;
320
+ const discriminant = eB * eB - 4 * eA * eC;
321
+ if (discriminant < (-10) ** -5) {
322
+ return null;
323
+ }
324
+ let O, r;
325
+ if (Math.abs(discriminant) < 10 ** -5) {
326
+ const t = -eB / (2 * eA);
327
+ O = bisector.evaluate(t);
328
+ r = O.minus(p).norm;
329
+ } else {
330
+ const t1 = (-eB + Math.sqrt(discriminant)) / (2 * eA);
331
+ const t2 = (-eB - Math.sqrt(discriminant)) / (2 * eA);
332
+ if (bisector.evaluate(t1).minus(p).normSquared > bisector.evaluate(t2).minus(p).normSquared) {
333
+ O = bisector.evaluate(t1);
334
+ r = bisector.evaluate(t1).minus(p).norm;
335
+ } else {
336
+ O = bisector.evaluate(t2);
337
+ r = bisector.evaluate(t2).minus(p).norm;
338
+ }
339
+ }
340
+ return [
341
+ {
342
+ circle: { centre: O, r },
343
+ tangent: new Line(p, p.plus(O.minus(p).normal()))
344
+ }
345
+ ];
346
+ }
347
+ fit_NDl(l, err) {
348
+ if (!this.formTriangle(l, err)) {
349
+ return null;
350
+ }
351
+ const C = this.leftArm.start;
352
+ const A = l.intersectionPoint(this.leftArm, 0);
353
+ const B = l.intersectionPoint(this.rightArm, 0);
354
+ const AC = new Line(A, C);
355
+ const BC = new Line(B, C);
356
+ const AB = new Line(A, B);
357
+ const a = AC.length;
358
+ const b = BC.length;
359
+ const c = AB.length;
360
+ const s = (a + b + c) / 2;
361
+ if (s * (s - a) * (s - b) / (s - c) < 0) {
362
+ return null;
363
+ }
364
+ const r = Math.sqrt(s * (s - a) * (s - b) / (s - c));
365
+ const det = AB.delta.cross(AC.delta);
366
+ const lhsAll = [
367
+ new Vec2(B.cross(A) + r * c, C.cross(A) + r * a),
368
+ new Vec2(B.cross(A) + r * c, C.cross(A) - r * a),
369
+ new Vec2(B.cross(A) - r * c, C.cross(A) + r * a),
370
+ new Vec2(B.cross(A) - r * c, C.cross(A) - r * a)
371
+ ];
372
+ const OAll = [];
373
+ lhsAll.forEach((lhs) => {
374
+ OAll.push(
375
+ new Vec2(
376
+ new Vec2(AB.delta.x, AC.delta.x).cross(lhs),
377
+ new Vec2(AB.delta.y, AC.delta.y).cross(lhs)
378
+ ).over(-det)
379
+ );
380
+ });
381
+ let o = null;
382
+ const dists = [];
383
+ for (const O of OAll) {
384
+ dists.push({
385
+ raw: Math.abs(BC.distanceToPoint(O) - r),
386
+ norm: Math.abs(BC.distanceToPoint(O) / r - 1)
387
+ });
388
+ const absoluteError = Math.abs(BC.distanceToPoint(O) - r);
389
+ const relativeError = Math.abs(BC.distanceToPoint(O) / r - 1);
390
+ if ((absoluteError < 10 ** -5 || relativeError < 10 ** -5) && AC.pointOnSide(O) !== BC.pointOnSide(O)) {
391
+ o = O;
392
+ break;
393
+ }
394
+ }
395
+ let msg = "";
396
+ if (o === null) {
397
+ msg = "fit_NDl, centre is undefined";
398
+ for (let i = 0; i < OAll.length; i++) {
399
+ msg += `centre: (${OAll[i].x}, ${OAll[i].y}), r: ${r}, dist raw: ${dists[i].raw}, dist norm: ${dists[i].norm}
400
+ `;
401
+ }
402
+ }
403
+ if (o === null) {
404
+ throw new Error(msg);
405
+ }
406
+ return [
407
+ {
408
+ circle: { centre: o, r },
409
+ tangentParameter: l.closestPointParam(o)
410
+ }
411
+ ];
412
+ }
413
+ fitCircles(element, err) {
414
+ if (element instanceof Vec2) {
415
+ return this.isDegenerate ? this.fit_Dp(element, err) : this.fit_NDp(element, err);
416
+ }
417
+ if (element instanceof Line) {
418
+ return this.isDegenerate ? this.fit_Dl(element instanceof Line ? element : element, err) : this.fit_NDl(element instanceof Line ? element : element, err);
419
+ }
420
+ return null;
421
+ }
422
+ toString() {
423
+ return `LA: ${this.leftArm.start.toString()} --> ${this.leftArm.end.toString()}
424
+ RA: ${this.rightArm.start.toString()} --> ${this.rightArm.end.toString()}`;
425
+ }
426
+ };
427
+
428
+ // src/index.ts
429
+ function lineTangentToHull(line, points, halo) {
430
+ let holds = true;
431
+ let side = 0 /* Top */;
432
+ let k = 0;
433
+ while (side === 0 /* Top */ && k < points.length) {
434
+ side = line.pointOnSide(points[k], halo);
435
+ k++;
436
+ }
437
+ for (let i = k; i < points.length; i++) {
438
+ const testSide = line.pointOnSide(points[i], halo);
439
+ if (testSide === 0 /* Top */) {
440
+ continue;
441
+ }
442
+ if (testSide !== side) {
443
+ holds = false;
444
+ break;
445
+ }
446
+ }
447
+ return { holds, side };
448
+ }
449
+ function findEnclosingSide(wedge, startVertex, endVertex, points, halo) {
450
+ let side = null;
451
+ let stopVertex = startVertex;
452
+ let vertex = startVertex;
453
+ while (side === null && vertex > endVertex) {
454
+ const p1 = points[vertex];
455
+ const p2 = points[vertex - 1];
456
+ const edge = new Line(p1, p2);
457
+ const circlesEdge = wedge.fitCircles(edge, halo);
458
+ if (circlesEdge !== null) {
459
+ let tangentParameter = 100;
460
+ if (wedge.isDegenerate) {
461
+ let sidedness = 0 /* Top */;
462
+ let k = 0;
463
+ while (sidedness === 0 /* Top */ && k < points.length) {
464
+ sidedness = edge.pointOnSide(points[k], halo);
465
+ k++;
466
+ }
467
+ tangentParameter = edge.pointOnSide(circlesEdge[0].circle.centre) !== sidedness ? circlesEdge[0].tangentParameter : circlesEdge[1].tangentParameter;
468
+ } else {
469
+ tangentParameter = circlesEdge[0].tangentParameter;
470
+ }
471
+ if (tangentParameter > 0 && tangentParameter < 1) {
472
+ const Y = edge.evaluate(tangentParameter);
473
+ const joint = wedge.leftArm.intersectionPoint(edge, halo);
474
+ side = new Line(joint, Y);
475
+ }
476
+ }
477
+ if (side === null) {
478
+ const circlesPoint = wedge.fitCircles(p2, halo);
479
+ if (circlesPoint !== null) {
480
+ let tangent;
481
+ if (wedge.isDegenerate) {
482
+ let sidedness = 0 /* Top */;
483
+ let k = 0;
484
+ while (sidedness === 0 /* Top */ && k < points.length) {
485
+ sidedness = circlesPoint[0].tangent.pointOnSide(points[k], halo);
486
+ k++;
487
+ }
488
+ tangent = circlesPoint[0].tangent.pointOnSide(
489
+ circlesPoint[0].circle.centre,
490
+ halo
491
+ ) !== sidedness ? circlesPoint[0].tangent : circlesPoint[1].tangent;
492
+ } else {
493
+ tangent = circlesPoint[0].tangent;
494
+ }
495
+ if (lineTangentToHull(tangent, points, halo).holds) {
496
+ const joint = wedge.leftArm.intersectionPoint(tangent, halo);
497
+ side = new Line(joint, p2);
498
+ }
499
+ }
500
+ }
501
+ stopVertex = vertex;
502
+ vertex--;
503
+ }
504
+ return side === null ? null : { side, stopVertex };
505
+ }
506
+ function findAntipode(points) {
507
+ let farthestIndex = 0;
508
+ let farthestDist = 0;
509
+ for (let i = 0, n = points.length; i < n; i++) {
510
+ const testDist = new Line(points[0], points[n - 1]).distanceToPoint(
511
+ points[i]
512
+ );
513
+ if (testDist > farthestDist) {
514
+ farthestDist = testDist;
515
+ farthestIndex = i;
516
+ }
517
+ }
518
+ return farthestIndex;
519
+ }
520
+ function minTriangleWithBase(convexHull, err, tol) {
521
+ let AB, AC;
522
+ const n = convexHull.length;
523
+ const BC = new Line(convexHull[0], convexHull[n - 1]);
524
+ const antipodIndex = findAntipode(convexHull);
525
+ const baseParallel = new Line(
526
+ convexHull[antipodIndex],
527
+ convexHull[antipodIndex].plus(BC.delta)
528
+ );
529
+ let wedge = Wedge.new(BC, baseParallel, err);
530
+ let Pn = n - 1;
531
+ let Qn = antipodIndex;
532
+ do {
533
+ const CQinfo = findEnclosingSide(wedge, Pn, antipodIndex, convexHull, err);
534
+ if (CQinfo === null) {
535
+ return null;
536
+ }
537
+ ;
538
+ ({ side: AC, stopVertex: Pn } = CQinfo);
539
+ wedge = Wedge.new(wedge.leftArm, AC, err);
540
+ const BPinfo = findEnclosingSide(wedge, Qn, 0, convexHull, err);
541
+ if (BPinfo === null) {
542
+ return null;
543
+ }
544
+ ;
545
+ ({ side: AB, stopVertex: Qn } = BPinfo);
546
+ wedge = Wedge.new(wedge.leftArm, AB, err);
547
+ } while (AB.length - AC.length > tol);
548
+ const A = AC.intersectionPoint(AB, 0);
549
+ const B = AB.start;
550
+ const C = AC.start;
551
+ return { A, B, C };
552
+ }
553
+ function minTriangle(convexHull, err, tol) {
554
+ if (convexHull.length < 3) {
555
+ return null;
556
+ }
557
+ if (convexHull.length === 3) {
558
+ return { A: convexHull[0], B: convexHull[1], C: convexHull[2] };
559
+ }
560
+ const points = convexHull.map((p) => {
561
+ return new Vec2(p.x, p.y);
562
+ });
563
+ let A = null;
564
+ let B = null;
565
+ let C = null;
566
+ let perimeter = -1;
567
+ let rotations = 0;
568
+ while (rotations < points.length) {
569
+ if (rotations > 0) {
570
+ points.push(points.shift());
571
+ }
572
+ const triangle = minTriangleWithBase(points, err, tol);
573
+ if (triangle !== null) {
574
+ const { A: A1, B: B1, C: C1 } = triangle;
575
+ const perimeter1 = A1.minus(B1).norm + B1.minus(C1).norm + C1.minus(A1).norm;
576
+ if (perimeter1 < perimeter || perimeter === -1) {
577
+ ;
578
+ [A, B, C] = [A1, B1, C1];
579
+ perimeter = perimeter1;
580
+ }
581
+ }
582
+ rotations++;
583
+ }
584
+ return perimeter === -1 ? null : {
585
+ A: { x: A.x, y: A.y },
586
+ B: { x: B.x, y: B.y },
587
+ C: { x: C.x, y: C.y }
588
+ };
589
+ }
590
+ //# sourceMappingURL=index.cjs.map