plotboilerplate 1.28.1 → 1.29.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.
Files changed (66) hide show
  1. package/README.md +8 -0
  2. package/dist/index.esm.js +335 -39
  3. package/dist/index.esm.js.map +1 -1
  4. package/dist/index.esm.min.js +1 -1
  5. package/dist/index.esm.min.js.map +1 -1
  6. package/dist/plotboilerplate.browser.min.js +1 -1
  7. package/dist/plotboilerplate.browser.min.js.map +1 -1
  8. package/dist/plotboilerplate.js +335 -37
  9. package/dist/plotboilerplate.min.js +1 -1
  10. package/package.json +1 -1
  11. package/src/cjs/Circle.d.ts +70 -3
  12. package/src/cjs/Circle.js +159 -4
  13. package/src/cjs/Circle.js.map +1 -1
  14. package/src/cjs/Triangle.d.ts +40 -4
  15. package/src/cjs/Triangle.js +127 -31
  16. package/src/cjs/Triangle.js.map +1 -1
  17. package/src/cjs/VertTuple.d.ts +4 -1
  18. package/src/cjs/VertTuple.js +6 -1
  19. package/src/cjs/VertTuple.js.map +1 -1
  20. package/src/cjs/Vertex.d.ts +25 -2
  21. package/src/cjs/Vertex.js +43 -1
  22. package/src/cjs/Vertex.js.map +1 -1
  23. package/src/cjs/utils/algorithms/minimalContainingCircleFromPoints.d.ts +21 -0
  24. package/src/cjs/utils/algorithms/minimalContainingCircleFromPoints.js +73 -0
  25. package/src/cjs/utils/algorithms/minimalContainingCircleFromPoints.js.map +1 -0
  26. package/src/cjs/utils/algorithms/minimalEnclosingCircleFromPoints.d.ts +21 -0
  27. package/src/cjs/utils/algorithms/minimalEnclosingCircleFromPoints.js +73 -0
  28. package/src/cjs/utils/algorithms/minimalEnclosingCircleFromPoints.js.map +1 -0
  29. package/src/cjs/utils/algorithms/pointsMinimalContainingCircle.d.ts +21 -0
  30. package/src/cjs/utils/algorithms/pointsMinimalContainingCircle.js +73 -0
  31. package/src/cjs/utils/algorithms/pointsMinimalContainingCircle.js.map +1 -0
  32. package/src/cjs/utils/algorithms/solveApollonius3.d.ts +69 -0
  33. package/src/cjs/utils/algorithms/solveApollonius3.js +118 -0
  34. package/src/cjs/utils/algorithms/solveApollonius3.js.map +1 -0
  35. package/src/esm/Circle.d.ts +70 -3
  36. package/src/esm/Circle.js +159 -4
  37. package/src/esm/Circle.js.map +1 -1
  38. package/src/esm/Triangle.d.ts +40 -4
  39. package/src/esm/Triangle.js +128 -32
  40. package/src/esm/Triangle.js.map +1 -1
  41. package/src/esm/VertTuple.d.ts +4 -1
  42. package/src/esm/VertTuple.js +6 -1
  43. package/src/esm/VertTuple.js.map +1 -1
  44. package/src/esm/Vertex.d.ts +25 -2
  45. package/src/esm/Vertex.js +44 -2
  46. package/src/esm/Vertex.js.map +1 -1
  47. package/src/esm/utils/algorithms/minimalContainingCircleFromPoints.d.ts +21 -0
  48. package/src/esm/utils/algorithms/minimalContainingCircleFromPoints.js +69 -0
  49. package/src/esm/utils/algorithms/minimalContainingCircleFromPoints.js.map +1 -0
  50. package/src/esm/utils/algorithms/minimalEnclosingCircleFromPoints.d.ts +21 -0
  51. package/src/esm/utils/algorithms/minimalEnclosingCircleFromPoints.js +69 -0
  52. package/src/esm/utils/algorithms/minimalEnclosingCircleFromPoints.js.map +1 -0
  53. package/src/esm/utils/algorithms/pointsMinimalContainingCircle.d.ts +21 -0
  54. package/src/esm/utils/algorithms/pointsMinimalContainingCircle.js +69 -0
  55. package/src/esm/utils/algorithms/pointsMinimalContainingCircle.js.map +1 -0
  56. package/src/esm/utils/algorithms/solveApollonius3.d.ts +69 -0
  57. package/src/esm/utils/algorithms/solveApollonius3.js +114 -0
  58. package/src/esm/utils/algorithms/solveApollonius3.js.map +1 -0
  59. package/src/index-gl.js +5 -2
  60. package/src/index.js +25 -17
  61. package/src/ts/Circle.ts +178 -5
  62. package/src/ts/Triangle.ts +148 -41
  63. package/src/ts/VertTuple.ts +14 -1
  64. package/src/ts/Vertex.ts +44 -2
  65. package/src/ts/utils/algorithms/minimalEnclosingCircleFromPoints.ts +69 -0
  66. package/src/ts/utils/algorithms/solveApollonius3.ts +132 -0
@@ -27,7 +27,10 @@
27
27
  * @modified 2025-14-16 Class `Triangle` now implements interface `IBounded`.
28
28
  * @modified 2025-14-16 Class `Triangle` now implements interface `Intersectable`.
29
29
  * @modified 2025-14-16 Added method `Triangle.move`.
30
- * @version 2.10.0
30
+ * @modified 2026-06-10 Refactoring the `Trianble.bounds` method and added a plain `Triangle.utils.bounds` method.
31
+ * @modified 2026-06-10 Refactoring the `Trianble.calcCircumCircle` method and added a plain `Triangle.utils.calcCircumCircle` method.
32
+ * @modified 2026-06-13 Adding `Triangle.getVertices()`.
33
+ * @version 2.11.0
31
34
  *
32
35
  * @file Triangle
33
36
  * @fileoverview A simple triangle class: three vertices.
@@ -35,7 +38,7 @@
35
38
  **/
36
39
 
37
40
  import { Bounds } from "./Bounds";
38
- import { Circle } from "./Circle";
41
+ import { Circle, ICircle } from "./Circle";
39
42
  import { Line } from "./Line";
40
43
  import { Polygon } from "./Polygon";
41
44
  import { UIDGenerator } from "./UIDGenerator";
@@ -263,7 +266,7 @@ export class Triangle implements IBounded, SVGSerializable, Intersectable {
263
266
  * after triangle vertex changes.
264
267
  *
265
268
  * @method getCircumcircle
266
- * @return {Object} - { center:Vertex, radius:float }
269
+ * @return {Circle} - The circle touching exactly all three triangle vertices.
267
270
  * @instance
268
271
  * @memberof Triangle
269
272
  */
@@ -273,6 +276,35 @@ export class Triangle implements IBounded, SVGSerializable, Intersectable {
273
276
  return new Circle(this.center.clone(), this.radius);
274
277
  }
275
278
 
279
+ /**
280
+ * Calculates the minimun enclosing circle.
281
+ *
282
+ * @method getMinimumEnclosingCircle
283
+ * @return {Object} - { center:Vertex, radius:float }
284
+ * @instance
285
+ * @memberof Triangle
286
+ * @returns
287
+ */
288
+ getMinimumEnclosingCircle(): Circle {
289
+ // First option: two points construct a circle and the third point is contained.
290
+ var circleAB: ICircle = VertTuple.vtutils.calcCircumcircle(this.a, this.b);
291
+ if (Circle.circleUtils.containsPoint(circleAB.center, circleAB.radius, this.c)) {
292
+ return Circle.fromICircle(circleAB);
293
+ }
294
+ var circleBC: ICircle = VertTuple.vtutils.calcCircumcircle(this.b, this.c);
295
+ if (Circle.circleUtils.containsPoint(circleBC.center, circleBC.radius, this.c)) {
296
+ return Circle.fromICircle(circleBC);
297
+ }
298
+ var circleCA: ICircle = VertTuple.vtutils.calcCircumcircle(this.c, this.a);
299
+ if (Circle.circleUtils.containsPoint(circleCA.center, circleCA.radius, this.c)) {
300
+ return Circle.fromICircle(circleCA);
301
+ }
302
+
303
+ // If none of the three upper cases applies: return the circumcircle.
304
+ var circumCircle = Triangle.utils.calcCircumcircle(this.a, this.b, this.c);
305
+ return Circle.fromICircle(circumCircle);
306
+ }
307
+
276
308
  /**
277
309
  * Check if this triangle and the passed triangle share an
278
310
  * adjacent edge.
@@ -311,6 +343,18 @@ export class Triangle implements IBounded, SVGSerializable, Intersectable {
311
343
  return this.b;
312
344
  }
313
345
 
346
+ /**
347
+ * Get the three triangele vertices in a 3-element array.
348
+ *
349
+ * @method getVertices
350
+ * @returns {[Vertex, Vertex, Vertex]} - The three vertices – real instances, not copies.
351
+ * @instance
352
+ * @memberof Triangle
353
+ */
354
+ getVertices(): [Vertex, Vertex, Vertex] {
355
+ return [this.a, this.b, this.c];
356
+ }
357
+
314
358
  /**
315
359
  * Re-compute the circumcircle of this triangle (if the vertices
316
360
  * have changed).
@@ -324,37 +368,41 @@ export class Triangle implements IBounded, SVGSerializable, Intersectable {
324
368
  * @memberof Triangle
325
369
  */
326
370
  calcCircumcircle() {
327
- // From
328
- // http://www.exaflop.org/docs/cgafaq/cga1.html
329
-
330
- const A: number = this.b.x - this.a.x;
331
- const B: number = this.b.y - this.a.y;
332
- const C: number = this.c.x - this.a.x;
333
- const D: number = this.c.y - this.a.y;
334
-
335
- const E: number = A * (this.a.x + this.b.x) + B * (this.a.y + this.b.y);
336
- const F: number = C * (this.a.x + this.c.x) + D * (this.a.y + this.c.y);
337
-
338
- const G: number = 2.0 * (A * (this.c.y - this.b.y) - B * (this.c.x - this.b.x));
339
-
340
- let dx: number, dy: number;
341
-
342
- if (Math.abs(G) < Triangle.EPSILON) {
343
- // Collinear - find extremes and use the midpoint
344
- const bounds: Bounds = this.bounds();
345
- this.center = new Vertex((bounds.min.x + bounds.max.x) / 2, (bounds.min.y + bounds.max.y) / 2);
346
- dx = this.center.x - bounds.min.x;
347
- dy = this.center.y - bounds.min.y;
348
- } else {
349
- const cx: number = (D * E - B * F) / G;
350
- const cy: number = (A * F - C * E) / G;
351
- this.center = new Vertex(cx, cy);
352
- dx = this.center.x - this.a.x;
353
- dy = this.center.y - this.a.y;
354
- }
355
-
356
- this.radius_squared = dx * dx + dy * dy;
357
- this.radius = Math.sqrt(this.radius_squared);
371
+ // // From
372
+ // // http://www.exaflop.org/docs/cgafaq/cga1.html
373
+
374
+ // const A: number = this.b.x - this.a.x;
375
+ // const B: number = this.b.y - this.a.y;
376
+ // const C: number = this.c.x - this.a.x;
377
+ // const D: number = this.c.y - this.a.y;
378
+
379
+ // const E: number = A * (this.a.x + this.b.x) + B * (this.a.y + this.b.y);
380
+ // const F: number = C * (this.a.x + this.c.x) + D * (this.a.y + this.c.y);
381
+
382
+ // const G: number = 2.0 * (A * (this.c.y - this.b.y) - B * (this.c.x - this.b.x));
383
+
384
+ // let dx: number, dy: number;
385
+
386
+ // if (Math.abs(G) < Triangle.EPSILON) {
387
+ // // Collinear - find extremes and use the midpoint
388
+ // const bounds: Bounds = this.bounds();
389
+ // this.center = new Vertex((bounds.min.x + bounds.max.x) / 2, (bounds.min.y + bounds.max.y) / 2);
390
+ // dx = this.center.x - bounds.min.x;
391
+ // dy = this.center.y - bounds.min.y;
392
+ // } else {
393
+ // const cx: number = (D * E - B * F) / G;
394
+ // const cy: number = (A * F - C * E) / G;
395
+ // this.center = new Vertex(cx, cy);
396
+ // dx = this.center.x - this.a.x;
397
+ // dy = this.center.y - this.a.y;
398
+ // }
399
+
400
+ // this.radius_squared = dx * dx + dy * dy;
401
+ // this.radius = Math.sqrt(this.radius_squared);
402
+ const tmpCircle = Triangle.utils.calcCircumcircle(this.a, this.b, this.c);
403
+ this.center = new Vertex(tmpCircle.center.x, tmpCircle.center.y);
404
+ this.radius = tmpCircle.radius;
405
+ this.radius_squared = tmpCircle.radius_squared;
358
406
  } // END calcCircumcircle
359
407
 
360
408
  /**
@@ -383,10 +431,11 @@ export class Triangle implements IBounded, SVGSerializable, Intersectable {
383
431
  * @memberof Triangle
384
432
  */
385
433
  bounds(): Bounds {
386
- return new Bounds(
387
- new Vertex(Triangle.utils.min3(this.a.x, this.b.x, this.c.x), Triangle.utils.min3(this.a.y, this.b.y, this.c.y)),
388
- new Vertex(Triangle.utils.max3(this.a.x, this.b.x, this.c.x), Triangle.utils.max3(this.a.y, this.b.y, this.c.y))
389
- );
434
+ // return new Bounds(
435
+ // new Vertex(Triangle.utils.min3(this.a.x, this.b.x, this.c.x), Triangle.utils.min3(this.a.y, this.b.y, this.c.y)),
436
+ // new Vertex(Triangle.utils.max3(this.a.x, this.b.x, this.c.x), Triangle.utils.max3(this.a.y, this.b.y, this.c.y))
437
+ // );
438
+ return Triangle.utils.bounds(this.a, this.b, this.c);
390
439
  }
391
440
 
392
441
  //--- BEGIN --- Implement interface `Intersectable`
@@ -521,7 +570,9 @@ export class Triangle implements IBounded, SVGSerializable, Intersectable {
521
570
  * @return Vertex The incenter of this triangle.
522
571
  **/
523
572
  getIncenter(): Vertex {
524
- if (!this.center || !this.radius) this.calcCircumcircle();
573
+ if (!this.center || !this.radius) {
574
+ this.calcCircumcircle();
575
+ }
525
576
  return this.center.clone();
526
577
  }
527
578
 
@@ -594,8 +645,64 @@ export class Triangle implements IBounded, SVGSerializable, Intersectable {
594
645
  * @param {XYCords} c - The first vertex.
595
646
  * @returns {nmber}
596
647
  */
597
- determinant(a: XYCoords, b: XYCoords, c: XYCoords): number {
648
+ determinant: (a: XYCoords, b: XYCoords, c: XYCoords): number => {
598
649
  return (b.y - a.y) * (c.x - b.x) - (c.y - b.y) * (b.x - a.x);
599
- }
650
+ },
651
+
652
+ bounds: (a: XYCoords, b: XYCoords, c: XYCoords): Bounds => {
653
+ return new Bounds(
654
+ new Vertex(Triangle.utils.min3(a.x, b.x, c.x), Triangle.utils.min3(a.y, b.y, c.y)),
655
+ new Vertex(Triangle.utils.max3(a.x, b.x, c.x), Triangle.utils.max3(a.y, b.y, c.y))
656
+ );
657
+ },
658
+
659
+ /**
660
+ * Re-compute the circumcircle of this triangle (if the vertices
661
+ * have changed).
662
+ *
663
+ * The circumcenter and radius are stored in this.center and
664
+ * this.radius. There is a third result: radius_squared (for internal computations).
665
+ *
666
+ * @method calcCircumcircle
667
+ * @return void
668
+ * @instance
669
+ * @memberof Triangle
670
+ */
671
+ calcCircumcircle: (a: XYCoords, b: XYCoords, c: XYCoords): ICircle => {
672
+ // From
673
+ // http://www.exaflop.org/docs/cgafaq/cga1.html
674
+
675
+ const A: number = b.x - a.x;
676
+ const B: number = b.y - a.y;
677
+ const C: number = c.x - a.x;
678
+ const D: number = c.y - a.y;
679
+
680
+ const E: number = A * (a.x + b.x) + B * (a.y + b.y);
681
+ const F: number = C * (a.x + c.x) + D * (a.y + c.y);
682
+
683
+ const G: number = 2.0 * (A * (c.y - b.y) - B * (c.x - b.x));
684
+
685
+ let dx: number, dy: number;
686
+
687
+ let center;
688
+ if (Math.abs(G) < Triangle.EPSILON) {
689
+ // Collinear - find extremes and use the midpoint
690
+ // const bounds: Bounds = this.bounds();
691
+ const bounds: Bounds = Triangle.utils.bounds(a, b, c);
692
+ center = new Vertex((bounds.min.x + bounds.max.x) / 2, (bounds.min.y + bounds.max.y) / 2);
693
+ dx = center.x - bounds.min.x;
694
+ dy = center.y - bounds.min.y;
695
+ } else {
696
+ const cx: number = (D * E - B * F) / G;
697
+ const cy: number = (A * F - C * E) / G;
698
+ center = new Vertex(cx, cy);
699
+ dx = center.x - a.x;
700
+ dy = center.y - a.y;
701
+ }
702
+
703
+ const radius_squared = dx * dx + dy * dy;
704
+ const radius = Math.sqrt(radius_squared);
705
+ return { center: center, radius: radius, radius_squared: radius_squared };
706
+ } // END calcCircumcircle
600
707
  };
601
708
  }
@@ -16,12 +16,14 @@
16
16
  * @modified 2025-03-31 Added the `VertTuple.revert` method.
17
17
  * @modified 2025-04-15 Changed param of `VertTuple.moveTo` method from `Vertex` to `XYCoords`.
18
18
  * @modified 2025-04-15 Added method `VertTuple.move` method.
19
- * @version 1.4.0
19
+ * @modified 2026-06-10 Adding helper function `VertTuple.utils.calcCircumcircle`.
20
+ * @version 1.5.0
20
21
  */
21
22
 
22
23
  import { Vertex } from "./Vertex";
23
24
  import { UIDGenerator } from "./UIDGenerator";
24
25
  import { XYCoords, UID } from "./interfaces";
26
+ import { ICircle } from "./Circle";
25
27
 
26
28
  /**
27
29
  * @classdesc An abstract base classes for vertex tuple constructs, like Lines or Vectors.
@@ -396,6 +398,17 @@ export class VertTuple<T extends VertTuple<T>> {
396
398
  static vtutils = {
397
399
  dist2: (v: XYCoords, w: XYCoords) => {
398
400
  return (v.x - w.x) * (v.x - w.x) + (v.y - w.y) * (v.y - w.y);
401
+ },
402
+
403
+ calcCircumcircle: (p1: XYCoords, p2: XYCoords): ICircle => {
404
+ const p1x = p1.x,
405
+ p1y = p1.y,
406
+ p2x = p2.x,
407
+ p2y = p2.y,
408
+ cx = 0.5 * (p1x + p2x),
409
+ cy = 0.5 * (p1y + p2y);
410
+
411
+ return { center: { x: cx, y: cy }, radius: Math.sqrt((p1x - cx) * (p1x - cx) + (p1y - cy) * (p1y - cy)) };
399
412
  }
400
413
  };
401
414
  }
package/src/ts/Vertex.ts CHANGED
@@ -36,7 +36,8 @@
36
36
  * @modified 2025-03-24 Making the second parameter `center` of the `Vertex.rotate` method optional.
37
37
  * @modified 2025-04-13 Adding the `Vertex.move(amount: XYCoords)` method (does the same as `add`, added by naming convention).
38
38
  * @modified 2025-05-07 Class `Vertex` is now implementing interface `IBounded` (to meet convention).
39
- * @version 2.11.0
39
+ * @modified 2026-06-10 Adding methods `Vertex.findClosestPoint` and `Vertex.findFarestPoint`.
40
+ * @version 2.12.0
40
41
  *
41
42
  * @file Vertex
42
43
  * @public
@@ -663,6 +664,47 @@ export class Vertex implements IBounded, XYCoords, SVGSerializable {
663
664
  }
664
665
  //--- END --- Implement interface `IBounded`
665
666
 
667
+ /**
668
+ * Find the one of two given points that's closest to this point.
669
+ *
670
+ * @method findClosestPoint
671
+ * @instance
672
+ * @memberof Vertex
673
+ * @param {XYCoords | Vertex} pointA
674
+ * @param {XYCoords | Vertex} pointB
675
+ * @returns {XYCoords | Vertex}
676
+ */
677
+ findClosestPoint<T extends XYCoords | Vertex>(pointA: T, pointB: T): T {
678
+ // TODO: put this implementation to geomutils?
679
+ var distA = this.distance(pointA);
680
+ var distB = this.distance(pointB);
681
+ if (distA < distB) {
682
+ return pointA;
683
+ } else {
684
+ return pointB;
685
+ }
686
+ }
687
+
688
+ /**
689
+ * Find the one of two given points that's farest from this point.
690
+ *
691
+ * @method findFarestPoint
692
+ * @instance
693
+ * @memberof Vertex
694
+ * @param {XYCoords | Vertex} pointA
695
+ * @param {XYCoords | Vertex} pointB
696
+ * @returns {XYCoords | Vertex}
697
+ */
698
+ findFarestPoint<T extends XYCoords | Vertex>(pointA: T, pointB: T): T {
699
+ var distA = this.distance(pointA);
700
+ var distB = this.distance(pointB);
701
+ if (distA > distB) {
702
+ return pointA;
703
+ } else {
704
+ return pointB;
705
+ }
706
+ }
707
+
666
708
  /**
667
709
  * Get a string representation of this vertex.
668
710
  *
@@ -758,7 +800,7 @@ export class Vertex implements IBounded, XYCoords, SVGSerializable {
758
800
  * @param {number?} precision - (optional) The numeric precision to be used (number of precision digits).
759
801
  * @returns {string}
760
802
  */
761
- arrayToJSON(vertices: Array<XYCoords>, precision?: number) {
803
+ arrayToJSON: (vertices: Array<XYCoords>, precision?: number) => {
762
804
  return JSON.stringify(
763
805
  vertices.map(function (vert) {
764
806
  return typeof precision === undefined
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Calculate the minimal containing circle from a set of points.
3
+ *
4
+ * The number of points must be at least 2. Otherwise the function returns null.
5
+ *
6
+ * Inspired by https://github.com/rowanwins/smallest-enclosing-circle.
7
+ *
8
+ * @param {XYCoords[]} points
9
+ * @returns
10
+ */
11
+
12
+ import { Circle, ICircle } from "../../Circle";
13
+ import { Triangle } from "../../Triangle";
14
+ import { VertTuple } from "../../VertTuple";
15
+ import { Vertex } from "../../Vertex";
16
+ import { XYCoords } from "../../interfaces";
17
+
18
+ /**
19
+ * Calculate the minimal containing circle from a set of points.
20
+ *
21
+ * The number of points must be at least 2. Otherwise the function returns null.
22
+ *
23
+ * @param {XYCoords[]} points
24
+ * @returns {Circle | null} The minimal containing circle or null if the point count is lower than 2.
25
+ */
26
+ export const minimalEnclosingCircleFromPoints = (points: XYCoords[]): Circle | null => {
27
+ if (points.length <= 1) {
28
+ return null;
29
+ }
30
+ const circleObject = wetzlsAlgorithm(points);
31
+ return new Circle(new Vertex(circleObject.center.x, circleObject.center.y), circleObject.radius);
32
+ };
33
+
34
+ /**
35
+ * This initiates the actual algorithm but return a shallow circle representation.
36
+ *
37
+ * @param points
38
+ * @returns
39
+ */
40
+ const wetzlsAlgorithm = (points: XYCoords[]): ICircle | null => {
41
+ return minimumContainingCircle(points, points.length, [], 0);
42
+ };
43
+
44
+ /**
45
+ * Pre: points.length >= 2
46
+ * @param {Vertex[]} points
47
+ * @param n
48
+ * @param boundary
49
+ * @param boundaryLength
50
+ * @returns
51
+ */
52
+ const minimumContainingCircle = (points: XYCoords[], n: number, boundary: XYCoords[], boundaryLength: number): ICircle => {
53
+ if (boundaryLength === 3) {
54
+ return Triangle.utils.calcCircumcircle(boundary[0], boundary[1], boundary[2]);
55
+ } else if (n === 1 && boundaryLength === 0) {
56
+ return { center: { x: points[0].x, y: points[0].y }, radius: 0 };
57
+ } else if (n === 0 && boundaryLength === 2) {
58
+ return VertTuple.vtutils.calcCircumcircle(boundary[0], boundary[1]);
59
+ } else if (n === 1 && boundaryLength === 1) {
60
+ return VertTuple.vtutils.calcCircumcircle(boundary[0], points[0]);
61
+ } else {
62
+ const localCircle: ICircle = minimumContainingCircle(points, n - 1, boundary, boundaryLength);
63
+ if (!Circle.circleUtils.containsPoint(localCircle.center, localCircle.radius, points[n - 1])) {
64
+ boundary[boundaryLength++] = points[n - 1];
65
+ return minimumContainingCircle(points, n - 1, boundary, boundaryLength);
66
+ }
67
+ return localCircle;
68
+ }
69
+ };
@@ -0,0 +1,132 @@
1
+ /**
2
+ * A solver for the Apollonian circle problem:
3
+ * find the circle that touches three other circles in extactly one point (each).
4
+ * The solution is not necesarily unique.
5
+ *
6
+ * For each of the three circles a boolean attribute can be passed that determines if the
7
+ * resultint circle should touch the input circle should be touched on the inside or outside.
8
+ *
9
+ * Not that there can be condition when the circle cannot be calculated (e.g. all circles
10
+ * have the same size and are colinear).
11
+ *
12
+ * Found at
13
+ * https://stackoverflow.com/questions/79281573/algorithm-for-finding-enclosing-circles-for-3-other-circles
14
+ *
15
+ * solveApollonius(Coord c1, Coord c2, Coord c3, int s1, int s2, int s3) {
16
+ // https://github.com/DIKU-Steiner/ProGAL/blob/master/src/ProGAL/geom2d/ApolloniusSolver.java
17
+
18
+ var x1 = c1.x;
19
+ var y1 = c1.y;
20
+ var r1 = c1.z;
21
+ var x2 = c2.x;
22
+ var y2 = c2.y;
23
+ var r2 = c2.z;
24
+ var x3 = c3.x;
25
+ var y3 = c3.y;
26
+ var r3 = c3.z;
27
+
28
+ // Currently optimized for fewest multiplications.
29
+ var v11 = 2 * x2 - 2 * x1;
30
+ var v12 = 2 * y2 - 2 * y1;
31
+ var v13 = x1 * x1 - x2 * x2 + y1 * y1 - y2 * y2 - r1 * r1 + r2 * r2;
32
+ var v14 = 2 * s2 * r2 - 2 * s1 * r1;
33
+
34
+ var v21 = 2 * x3 - 2 * x2;
35
+ var v22 = 2 * y3 - 2 * y2;
36
+ var v23 = x2 * x2 - x3 * x3 + y2 * y2 - y3 * y3 - r2 * r2 + r3 * r3;
37
+ var v24 = 2 * s3 * r3 - 2 * s2 * r2;
38
+
39
+ var w12 = v12 / v11;
40
+ var w13 = v13 / v11;
41
+ var w14 = v14 / v11;
42
+
43
+ var w22 = v22 / v21 - w12;
44
+ var w23 = v23 / v21 - w13;
45
+ var w24 = v24 / v21 - w14;
46
+
47
+ var P = -w23 / w22;
48
+ var Q = w24 / w22;
49
+ var M = -w12 * P - w13;
50
+ var N = w14 - w12 * Q;
51
+
52
+ var a = N * N + Q * Q - 1;
53
+ var b = 2 * M * N - 2 * N * x1 + 2 * P * Q - 2 * Q * y1 + 2 * s1 * r1;
54
+ var c = x1 * x1 + M * M - 2 * M * x1 + P * P + y1 * y1 - 2 * P * y1 - r1 * r1;
55
+
56
+ // Find a root of a quadratic equation. This requires the circle centers not
57
+ // to be e.g. colinear
58
+ var D = b * b - 4 * a * c;
59
+ var rs = (-b - Math.sqrt(D)) / (2 * a);
60
+ var xs = M + N * rs;
61
+ var ys = P + Q * rs;
62
+
63
+ return (xs, ys, rs);
64
+ }
65
+ *
66
+ * @date 2026-07-03
67
+ */
68
+
69
+ import { Circle, ICircle } from "../../Circle";
70
+ import { Vertex } from "../../Vertex";
71
+
72
+ export const solveApollonius3 = (
73
+ c1: ICircle,
74
+ c2: ICircle,
75
+ c3: ICircle,
76
+ circle1Inside?: boolean,
77
+ circle2Inside?: boolean,
78
+ circle3Inside?: boolean
79
+ ): Circle => {
80
+ // https://github.com/DIKU-Steiner/ProGAL/blob/master/src/ProGAL/geom2d/ApolloniusSolver.java
81
+
82
+ var x1 = c1.center.x;
83
+ var y1 = c1.center.y;
84
+ var r1 = c1.radius;
85
+ var x2 = c2.center.x;
86
+ var y2 = c2.center.y;
87
+ var r2 = c2.radius;
88
+ var x3 = c3.center.x;
89
+ var y3 = c3.center.y;
90
+ var r3 = c3.radius;
91
+
92
+ var s1 = Boolean(circle1Inside) ? 1 : -1;
93
+ var s2 = Boolean(circle2Inside) ? 1 : -1;
94
+ var s3 = Boolean(circle3Inside) ? 1 : -1;
95
+
96
+ // Currently optimized for fewest multiplications.
97
+ var v11 = 2 * x2 - 2 * x1;
98
+ var v12 = 2 * y2 - 2 * y1;
99
+ var v13 = x1 * x1 - x2 * x2 + y1 * y1 - y2 * y2 - r1 * r1 + r2 * r2;
100
+ var v14 = 2 * s2 * r2 - 2 * s1 * r1;
101
+
102
+ var v21 = 2 * x3 - 2 * x2;
103
+ var v22 = 2 * y3 - 2 * y2;
104
+ var v23 = x2 * x2 - x3 * x3 + y2 * y2 - y3 * y3 - r2 * r2 + r3 * r3;
105
+ var v24 = 2 * s3 * r3 - 2 * s2 * r2;
106
+
107
+ var w12 = v12 / v11;
108
+ var w13 = v13 / v11;
109
+ var w14 = v14 / v11;
110
+
111
+ var w22 = v22 / v21 - w12;
112
+ var w23 = v23 / v21 - w13;
113
+ var w24 = v24 / v21 - w14;
114
+
115
+ var P = -w23 / w22;
116
+ var Q = w24 / w22;
117
+ var M = -w12 * P - w13;
118
+ var N = w14 - w12 * Q;
119
+
120
+ var a = N * N + Q * Q - 1;
121
+ var b = 2 * M * N - 2 * N * x1 + 2 * P * Q - 2 * Q * y1 + 2 * s1 * r1;
122
+ var c = x1 * x1 + M * M - 2 * M * x1 + P * P + y1 * y1 - 2 * P * y1 - r1 * r1;
123
+
124
+ // Find a root of a quadratic equation. This requires the circle centers not
125
+ // to be e.g. colinear
126
+ var D = b * b - 4 * a * c;
127
+ var rs = (-b - Math.sqrt(D)) / (2 * a);
128
+ var xs = M + N * rs;
129
+ var ys = P + Q * rs;
130
+
131
+ return new Circle(new Vertex(xs, ys), Math.abs(rs));
132
+ };