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
package/README.md CHANGED
@@ -579,6 +579,14 @@ new MouseHandler(document.getElementById("mycanvas"))
579
579
  - Thanks to Ana Tudor for the Multi-Range-Slider tutorial: https://css-tricks.com/multi-thumb-sliders-particular-two-thumb-case/
580
580
  - Thanks to Dean Taylor for the css linear gradient parser howto: https://stackoverflow.com/questions/20215440/parse-css-gradient-rule-with-javascript-regex
581
581
  - Thanks to Stefan Gustavson (stegu@itn.liu.se) for the Perlin noise on example C code (https://github.com/MethanePowered/PerlinNoise). And for Peter Eastman (peastman@drizzle.stanford.edu) for Perlin noise optimizations.
582
+ - Thanks to Rowan Winsemius for the (https://github.com/rowanwins/smallest-enclosing-circle "inspirational welzl algorithm implementation")
583
+ - Thanks to [Matt R](https://matthewhr.wordpress.com/author/matthewhr/) for the article about [minimum enclosing circles and circumscribed circles](https://matthewhr.wordpress.com/2013/03/23/circumscribed-circle-vs-minimal-enclosing-circle-ii/)
584
+ - Thanks to [Axel Kemper](https://math.stackexchange.com/users/58610/axel-kemper) for the idea of approximating the [minimum enclosing circle of three circles](https://math.stackexchange.com/questions/1017989/smallest-circle-enclosing-three-disjoint-circles)
585
+ - Thanks to [micycle](https://stackoverflow.com/users/9808792/micycle) for the neat function to solve the [Apollonian Circle](https://stackoverflow.com/questions/79281573/algorithm-for-finding-enclosing-circles-for-3-other-circles)
586
+ - And to [DIKU-Steiner](https://github.com/DIKU-Steiner) for the original Apollonian circle solver in Java.
587
+ - Mathworld for the [Apollonius math](https://mathworld.wolfram.com/ApolloniusProblem.html)
588
+ - Mathworld for the [Homothetic center math](https://mathworld.wolfram.com/HomotheticCenter.html)
589
+ - cut-the-knot.org for the [Radical Axis calculation](https://www.cut-the-knot.org/Curriculum/Geometry/GeoGebra/RadicalAxes.shtml)
582
590
 
583
591
  ## Todos
584
592
 
package/dist/index.esm.js CHANGED
@@ -348,7 +348,15 @@ class VertexListeners {
348
348
  * @modified 2022-08-23 Added the `closestPoint` function.
349
349
  * @modified 2025-04-09 Added the `Circle.move(amount: XYCoords)` method.
350
350
  * @modified 2025-04-16 Class `Circle` now implements interface `Intersectable`.
351
- * @version 1.5.0
351
+ * @modified 2026-06-10 Adding the utility function `Circle.circleUtils.containsPoint`.
352
+ * @modified 2026-06-10 Adding the `Circle.clone` method.
353
+ * @modified 2026-01-13 Adding helper function `Circle.circleUtils.containsPoint` and refactored the member method `containsPoint`.
354
+ * @modified 2026-07-03 Adding the optional `epsilon` parameter to the `Circle.containsCircle` method.
355
+ * @modified 2026-07-03 Fixing the `Circle.clone` method; the center had not been cloned at all, this was fixed.
356
+ * @modified 2026-07-08 Adding the `Circle.setRadius` method (for chaining).
357
+ * @mofified 2026-07-31 Adding the `radicalAxis(Circle)` method. Added the `Circle.circleUtils.createRadicalAxisHelperCircle` and `.circleDistance` helper methods.
358
+ * @modified 2026-08-03 Adding `Circle.tangentsFromPoint`.
359
+ * @version 1.7.0
352
360
  **/
353
361
  /**
354
362
  * @classdesc A simple circle: center point and radius.
@@ -379,6 +387,20 @@ class Circle {
379
387
  this.center = center;
380
388
  this.radius = radius;
381
389
  }
390
+ /**
391
+ * Set the radius of this circle.
392
+ * The method is meant for chaining, you may also alter the `radius` attribute directly.
393
+ *
394
+ * @method setRadius
395
+ * @param {number} radius - The amount to move.
396
+ * @instance
397
+ * @memberof Circle
398
+ * @return {Circle} this for chaining
399
+ **/
400
+ setRadius(radius) {
401
+ this.radius = radius;
402
+ return this;
403
+ }
382
404
  /**
383
405
  * Move the circle by the given amount.
384
406
  *
@@ -402,7 +424,8 @@ class Circle {
402
424
  * @return {boolean} `true` if the given point is inside this circle.
403
425
  */
404
426
  containsPoint(point) {
405
- return this.center.distance(point) < this.radius;
427
+ // return this.center.distance(point) < this.radius;//
428
+ return Circle.circleUtils.containsPoint(this.center, this.radius, point);
406
429
  }
407
430
  /**
408
431
  * Check if the given circle is fully contained inside this circle.
@@ -413,8 +436,13 @@ class Circle {
413
436
  * @memberof Circle
414
437
  * @return {boolean} `true` if any only if the given circle is completely inside this circle.
415
438
  */
416
- containsCircle(circle) {
417
- return this.center.distance(circle.center) + circle.radius < this.radius;
439
+ containsCircle(circle, epsilon) {
440
+ if (typeof epsilon === "undefined" || Number.isNaN(epsilon)) {
441
+ return this.center.distance(circle.center) + circle.radius < this.radius;
442
+ }
443
+ else {
444
+ return this.center.distance(circle.center) + circle.radius < this.radius + Math.abs(epsilon);
445
+ }
418
446
  }
419
447
  /**
420
448
  * Calculate the distance from this circle to the given line.
@@ -617,6 +645,40 @@ class Circle {
617
645
  });
618
646
  }
619
647
  //--- END --- Implement interface `Intersectable`
648
+ /**
649
+ * Calculate the radical axis of this and a different circle.
650
+ * The two circles must not be co-centric.
651
+ *
652
+ * See this article for details:
653
+ * https://www.cut-the-knot.org/Curriculum/Geometry/GeoGebra/RadicalAxes.shtml
654
+ *
655
+ * @method radicalAxis
656
+ * @instance
657
+ * @memberof Circle
658
+ * @param {Circle} circleB - The second circle to calculated the radical axis for.
659
+ * @return {Line} A line defining the radical axis of the two circles.
660
+ **/
661
+ radicalAxis(circleB) {
662
+ var helperCircle = Circle.circleUtils.createRadicalAxisHelperCircle(this, circleB);
663
+ var intersectionLineA = this.circleIntersection(helperCircle);
664
+ var intersectionLineB = circleB.circleIntersection(helperCircle);
665
+ var intersection = this.circleIntersection(circleB);
666
+ if (!intersectionLineA || !intersectionLineB) {
667
+ console.error("Critical error: none of the two intersections must be null.");
668
+ return intersection; // Fallback
669
+ }
670
+ var lineA = new Line(intersectionLineA.a, intersectionLineA.b);
671
+ var lineB = new Line(intersectionLineB.a, intersectionLineB.b);
672
+ var firstRadicalAxisPoint = lineA.intersection(lineB);
673
+ var centerConnectLine = new Line(this.center, circleB.center);
674
+ var secondRadicalAxisPoint = centerConnectLine.getClosestPoint(firstRadicalAxisPoint);
675
+ // Create a mirrored version of the second radical axis point so both are located
676
+ // symmetrical from the circle connect point.
677
+ // (currently the second point is located ON the connect line)
678
+ var secondRadicalAxisPoint_mirrored = secondRadicalAxisPoint.clone().scale(2.0, firstRadicalAxisPoint);
679
+ var radicalAxis = new Line(firstRadicalAxisPoint, secondRadicalAxisPoint_mirrored);
680
+ return intersection && intersection.length() > radicalAxis.length() ? intersection : radicalAxis;
681
+ }
620
682
  /**
621
683
  * Calculate the closest point on the outline of this circle to the given point.
622
684
  *
@@ -640,6 +702,71 @@ class Circle {
640
702
  return lineIntersection.b;
641
703
  }
642
704
  }
705
+ /**
706
+ * Get the two tangent vectors for the given point.
707
+ * If the point is on or in the circle then null is returned.
708
+ *
709
+ * @method tangentsFromPoint
710
+ * @instance
711
+ * @memberof Circle
712
+ * @param {Vertex} vert - The point to find the two tangents for.
713
+ * @return {[Vector,Vector]} The two tangent vector
714
+ **/
715
+ tangentsFromPoint(vert) {
716
+ // Inspired by
717
+ // https://www.omnicalculator.com/math/tangent-circle
718
+ const centerDistance = this.center.distance(vert);
719
+ const tangentLength = Math.sqrt(centerDistance * centerDistance - this.radius * this.radius);
720
+ // console.log("this.radius ", this.radius, "centerDistance", centerDistance, "tangentLength", tangentLength);
721
+ if (Number.isNaN(tangentLength)) {
722
+ // vertex is inside circle
723
+ console.log("tangentLength is NaN", tangentLength);
724
+ return null;
725
+ }
726
+ const helperCircle = new Circle(vert, tangentLength);
727
+ const intersection = this.circleIntersection(helperCircle);
728
+ if (!intersection) {
729
+ // No intersection (vertex is inside circle)
730
+ return null;
731
+ }
732
+ return [new Vector(intersection.a, vert), new Vector(intersection.b, vert)];
733
+ }
734
+ /**
735
+ * Create a deep copy of this circle.
736
+ *
737
+ * @method clone
738
+ * @return {Circle} A new circle, an exact copy of this one.
739
+ * @instance
740
+ * @memberof Circle
741
+ **/
742
+ clone() {
743
+ return new Circle(this.center.clone(), this.radius);
744
+ }
745
+ static fromICircle(obj) {
746
+ return new Circle(new Vertex$1(obj.center), obj.radius);
747
+ }
748
+ // static fromObject(obj: object): Circle {
749
+ // if (!obj) {
750
+ // return null;
751
+ // }
752
+ // if (typeof obj !== "object") {
753
+ // return null;
754
+ // }
755
+ // if (!obj.hasOwnProperty("center") || typeof (obj as any).center != "object") {
756
+ // return null;
757
+ // }
758
+ // if (!obj.hasOwnProperty("radius") || typeof !(obj as any).radius != "number") {
759
+ // return null;
760
+ // }
761
+ // var center = (obj as any).center;
762
+ // if (!center.hasOwnProperty("x") || typeof (center as any).x != "number") {
763
+ // return null;
764
+ // }
765
+ // if (!center.hasOwnProperty("y") || typeof (center as any).y != "number") {
766
+ // return null;
767
+ // }
768
+ // return new Circle(new Vertex(center), (obj as any).radius);
769
+ // }
643
770
  /**
644
771
  * This function should invalidate any installed listeners and invalidate this object.
645
772
  * After calling this function the object might not hold valid data any more and
@@ -655,6 +782,33 @@ Circle.circleUtils = {
655
782
  /* return new Vertex( Math.sin(angle) * radius,
656
783
  Math.cos(angle) * radius ); */
657
784
  return new Vertex$1(Math.cos(angle) * radius, Math.sin(angle) * radius);
785
+ },
786
+ containsPoint: (circleCenter, circleRadius, point) => {
787
+ // TODO: cleanup
788
+ // return (
789
+ // (circle.center.x - point.x) * (circle.center.x - point.x) + (circle.center.y - point.y) * (circle.center.y - point.y) <=
790
+ // circle.radius * circle.radius
791
+ // );
792
+ return geomutils.dist4(point.x, point.y, circleCenter.x, circleCenter.y) < circleRadius;
793
+ },
794
+ createRadicalAxisHelperCircle: (circleA, circleB) => {
795
+ // We need to create a helper circle that intersects BOTH other circles,
796
+ // each in TWO points.
797
+ var circleConnectLine = new Vector(circleA.center.clone(), circleB.center);
798
+ circleConnectLine.a.set(circleConnectLine.vertAt(0.5));
799
+ // Get the perpendicular by half the circle distance.
800
+ var bisector = circleConnectLine.perp();
801
+ var bisectorLength = Math.max(circleA.radius, circleB.radius, bisector.length());
802
+ bisector.setLength(bisectorLength);
803
+ return new Circle(bisector.b, bisector.b.distance(circleA.center));
804
+ },
805
+ /**
806
+ * Calculate the outer distance between two circles. If the circles touch then the
807
+ * distance is 0.0.
808
+ * If the circles intersect then the distance in negative.
809
+ */
810
+ circleDistance: (circleA, circleB) => {
811
+ return circleA.center.distance(circleB.center) - circleA.radius - circleB.radius;
658
812
  }
659
813
  };
660
814
 
@@ -687,7 +841,10 @@ Circle.circleUtils = {
687
841
  * @modified 2025-14-16 Class `Triangle` now implements interface `IBounded`.
688
842
  * @modified 2025-14-16 Class `Triangle` now implements interface `Intersectable`.
689
843
  * @modified 2025-14-16 Added method `Triangle.move`.
690
- * @version 2.10.0
844
+ * @modified 2026-06-10 Refactoring the `Trianble.bounds` method and added a plain `Triangle.utils.bounds` method.
845
+ * @modified 2026-06-10 Refactoring the `Trianble.calcCircumCircle` method and added a plain `Triangle.utils.calcCircumCircle` method.
846
+ * @modified 2026-06-13 Adding `Triangle.getVertices()`.
847
+ * @version 2.11.0
691
848
  *
692
849
  * @file Triangle
693
850
  * @fileoverview A simple triangle class: three vertices.
@@ -832,7 +989,7 @@ class Triangle {
832
989
  * after triangle vertex changes.
833
990
  *
834
991
  * @method getCircumcircle
835
- * @return {Object} - { center:Vertex, radius:float }
992
+ * @return {Circle} - The circle touching exactly all three triangle vertices.
836
993
  * @instance
837
994
  * @memberof Triangle
838
995
  */
@@ -841,6 +998,33 @@ class Triangle {
841
998
  this.calcCircumcircle();
842
999
  return new Circle(this.center.clone(), this.radius);
843
1000
  }
1001
+ /**
1002
+ * Calculates the minimun enclosing circle.
1003
+ *
1004
+ * @method getMinimumEnclosingCircle
1005
+ * @return {Object} - { center:Vertex, radius:float }
1006
+ * @instance
1007
+ * @memberof Triangle
1008
+ * @returns
1009
+ */
1010
+ getMinimumEnclosingCircle() {
1011
+ // First option: two points construct a circle and the third point is contained.
1012
+ var circleAB = VertTuple.vtutils.calcCircumcircle(this.a, this.b);
1013
+ if (Circle.circleUtils.containsPoint(circleAB.center, circleAB.radius, this.c)) {
1014
+ return Circle.fromICircle(circleAB);
1015
+ }
1016
+ var circleBC = VertTuple.vtutils.calcCircumcircle(this.b, this.c);
1017
+ if (Circle.circleUtils.containsPoint(circleBC.center, circleBC.radius, this.c)) {
1018
+ return Circle.fromICircle(circleBC);
1019
+ }
1020
+ var circleCA = VertTuple.vtutils.calcCircumcircle(this.c, this.a);
1021
+ if (Circle.circleUtils.containsPoint(circleCA.center, circleCA.radius, this.c)) {
1022
+ return Circle.fromICircle(circleCA);
1023
+ }
1024
+ // If none of the three upper cases applies: return the circumcircle.
1025
+ var circumCircle = Triangle.utils.calcCircumcircle(this.a, this.b, this.c);
1026
+ return Circle.fromICircle(circumCircle);
1027
+ }
844
1028
  /**
845
1029
  * Check if this triangle and the passed triangle share an
846
1030
  * adjacent edge.
@@ -879,6 +1063,17 @@ class Triangle {
879
1063
  //if( this.c.equals(vert1) && this.a.equals(vert2) || this.c.equals(vert2) && this.a.equals(vert1) )
880
1064
  return this.b;
881
1065
  }
1066
+ /**
1067
+ * Get the three triangele vertices in a 3-element array.
1068
+ *
1069
+ * @method getVertices
1070
+ * @returns {[Vertex, Vertex, Vertex]} - The three vertices – real instances, not copies.
1071
+ * @instance
1072
+ * @memberof Triangle
1073
+ */
1074
+ getVertices() {
1075
+ return [this.a, this.b, this.c];
1076
+ }
882
1077
  /**
883
1078
  * Re-compute the circumcircle of this triangle (if the vertices
884
1079
  * have changed).
@@ -892,32 +1087,35 @@ class Triangle {
892
1087
  * @memberof Triangle
893
1088
  */
894
1089
  calcCircumcircle() {
895
- // From
896
- // http://www.exaflop.org/docs/cgafaq/cga1.html
897
- const A = this.b.x - this.a.x;
898
- const B = this.b.y - this.a.y;
899
- const C = this.c.x - this.a.x;
900
- const D = this.c.y - this.a.y;
901
- const E = A * (this.a.x + this.b.x) + B * (this.a.y + this.b.y);
902
- const F = C * (this.a.x + this.c.x) + D * (this.a.y + this.c.y);
903
- const G = 2.0 * (A * (this.c.y - this.b.y) - B * (this.c.x - this.b.x));
904
- let dx, dy;
905
- if (Math.abs(G) < Triangle.EPSILON) {
906
- // Collinear - find extremes and use the midpoint
907
- const bounds = this.bounds();
908
- this.center = new Vertex$1((bounds.min.x + bounds.max.x) / 2, (bounds.min.y + bounds.max.y) / 2);
909
- dx = this.center.x - bounds.min.x;
910
- dy = this.center.y - bounds.min.y;
911
- }
912
- else {
913
- const cx = (D * E - B * F) / G;
914
- const cy = (A * F - C * E) / G;
915
- this.center = new Vertex$1(cx, cy);
916
- dx = this.center.x - this.a.x;
917
- dy = this.center.y - this.a.y;
918
- }
919
- this.radius_squared = dx * dx + dy * dy;
920
- this.radius = Math.sqrt(this.radius_squared);
1090
+ // // From
1091
+ // // http://www.exaflop.org/docs/cgafaq/cga1.html
1092
+ // const A: number = this.b.x - this.a.x;
1093
+ // const B: number = this.b.y - this.a.y;
1094
+ // const C: number = this.c.x - this.a.x;
1095
+ // const D: number = this.c.y - this.a.y;
1096
+ // const E: number = A * (this.a.x + this.b.x) + B * (this.a.y + this.b.y);
1097
+ // const F: number = C * (this.a.x + this.c.x) + D * (this.a.y + this.c.y);
1098
+ // const G: number = 2.0 * (A * (this.c.y - this.b.y) - B * (this.c.x - this.b.x));
1099
+ // let dx: number, dy: number;
1100
+ // if (Math.abs(G) < Triangle.EPSILON) {
1101
+ // // Collinear - find extremes and use the midpoint
1102
+ // const bounds: Bounds = this.bounds();
1103
+ // this.center = new Vertex((bounds.min.x + bounds.max.x) / 2, (bounds.min.y + bounds.max.y) / 2);
1104
+ // dx = this.center.x - bounds.min.x;
1105
+ // dy = this.center.y - bounds.min.y;
1106
+ // } else {
1107
+ // const cx: number = (D * E - B * F) / G;
1108
+ // const cy: number = (A * F - C * E) / G;
1109
+ // this.center = new Vertex(cx, cy);
1110
+ // dx = this.center.x - this.a.x;
1111
+ // dy = this.center.y - this.a.y;
1112
+ // }
1113
+ // this.radius_squared = dx * dx + dy * dy;
1114
+ // this.radius = Math.sqrt(this.radius_squared);
1115
+ const tmpCircle = Triangle.utils.calcCircumcircle(this.a, this.b, this.c);
1116
+ this.center = new Vertex$1(tmpCircle.center.x, tmpCircle.center.y);
1117
+ this.radius = tmpCircle.radius;
1118
+ this.radius_squared = tmpCircle.radius_squared;
921
1119
  } // END calcCircumcircle
922
1120
  /**
923
1121
  * Check if the passed vertex is inside this triangle's
@@ -944,7 +1142,11 @@ class Triangle {
944
1142
  * @memberof Triangle
945
1143
  */
946
1144
  bounds() {
947
- return new Bounds(new Vertex$1(Triangle.utils.min3(this.a.x, this.b.x, this.c.x), Triangle.utils.min3(this.a.y, this.b.y, this.c.y)), new Vertex$1(Triangle.utils.max3(this.a.x, this.b.x, this.c.x), Triangle.utils.max3(this.a.y, this.b.y, this.c.y)));
1145
+ // return new Bounds(
1146
+ // 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)),
1147
+ // 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))
1148
+ // );
1149
+ return Triangle.utils.bounds(this.a, this.b, this.c);
948
1150
  }
949
1151
  //--- BEGIN --- Implement interface `Intersectable`
950
1152
  /**
@@ -1067,8 +1269,9 @@ class Triangle {
1067
1269
  * @return Vertex The incenter of this triangle.
1068
1270
  **/
1069
1271
  getIncenter() {
1070
- if (!this.center || !this.radius)
1272
+ if (!this.center || !this.radius) {
1071
1273
  this.calcCircumcircle();
1274
+ }
1072
1275
  return this.center.clone();
1073
1276
  }
1074
1277
  /**
@@ -1134,9 +1337,55 @@ Triangle.utils = {
1134
1337
  * @param {XYCords} c - The first vertex.
1135
1338
  * @returns {nmber}
1136
1339
  */
1137
- determinant(a, b, c) {
1340
+ determinant: (a, b, c) => {
1138
1341
  return (b.y - a.y) * (c.x - b.x) - (c.y - b.y) * (b.x - a.x);
1139
- }
1342
+ },
1343
+ bounds: (a, b, c) => {
1344
+ return new Bounds(new Vertex$1(Triangle.utils.min3(a.x, b.x, c.x), Triangle.utils.min3(a.y, b.y, c.y)), new Vertex$1(Triangle.utils.max3(a.x, b.x, c.x), Triangle.utils.max3(a.y, b.y, c.y)));
1345
+ },
1346
+ /**
1347
+ * Re-compute the circumcircle of this triangle (if the vertices
1348
+ * have changed).
1349
+ *
1350
+ * The circumcenter and radius are stored in this.center and
1351
+ * this.radius. There is a third result: radius_squared (for internal computations).
1352
+ *
1353
+ * @method calcCircumcircle
1354
+ * @return void
1355
+ * @instance
1356
+ * @memberof Triangle
1357
+ */
1358
+ calcCircumcircle: (a, b, c) => {
1359
+ // From
1360
+ // http://www.exaflop.org/docs/cgafaq/cga1.html
1361
+ const A = b.x - a.x;
1362
+ const B = b.y - a.y;
1363
+ const C = c.x - a.x;
1364
+ const D = c.y - a.y;
1365
+ const E = A * (a.x + b.x) + B * (a.y + b.y);
1366
+ const F = C * (a.x + c.x) + D * (a.y + c.y);
1367
+ const G = 2.0 * (A * (c.y - b.y) - B * (c.x - b.x));
1368
+ let dx, dy;
1369
+ let center;
1370
+ if (Math.abs(G) < Triangle.EPSILON) {
1371
+ // Collinear - find extremes and use the midpoint
1372
+ // const bounds: Bounds = this.bounds();
1373
+ const bounds = Triangle.utils.bounds(a, b, c);
1374
+ center = new Vertex$1((bounds.min.x + bounds.max.x) / 2, (bounds.min.y + bounds.max.y) / 2);
1375
+ dx = center.x - bounds.min.x;
1376
+ dy = center.y - bounds.min.y;
1377
+ }
1378
+ else {
1379
+ const cx = (D * E - B * F) / G;
1380
+ const cy = (A * F - C * E) / G;
1381
+ center = new Vertex$1(cx, cy);
1382
+ dx = center.x - a.x;
1383
+ dy = center.y - a.y;
1384
+ }
1385
+ const radius_squared = dx * dx + dy * dy;
1386
+ const radius = Math.sqrt(radius_squared);
1387
+ return { center: center, radius: radius, radius_squared: radius_squared };
1388
+ } // END calcCircumcircle
1140
1389
  };
1141
1390
 
1142
1391
  /**
@@ -1298,7 +1547,8 @@ const geomutils = {
1298
1547
  * @modified 2025-03-24 Making the second parameter `center` of the `Vertex.rotate` method optional.
1299
1548
  * @modified 2025-04-13 Adding the `Vertex.move(amount: XYCoords)` method (does the same as `add`, added by naming convention).
1300
1549
  * @modified 2025-05-07 Class `Vertex` is now implementing interface `IBounded` (to meet convention).
1301
- * @version 2.11.0
1550
+ * @modified 2026-06-10 Adding methods `Vertex.findClosestPoint` and `Vertex.findFarestPoint`.
1551
+ * @version 2.12.0
1302
1552
  *
1303
1553
  * @file Vertex
1304
1554
  * @public
@@ -1858,6 +2108,47 @@ class Vertex$1 {
1858
2108
  return Bounds.computeFromVertices([this]);
1859
2109
  }
1860
2110
  //--- END --- Implement interface `IBounded`
2111
+ /**
2112
+ * Find the one of two given points that's closest to this point.
2113
+ *
2114
+ * @method findClosestPoint
2115
+ * @instance
2116
+ * @memberof Vertex
2117
+ * @param {XYCoords | Vertex} pointA
2118
+ * @param {XYCoords | Vertex} pointB
2119
+ * @returns {XYCoords | Vertex}
2120
+ */
2121
+ findClosestPoint(pointA, pointB) {
2122
+ // TODO: put this implementation to geomutils?
2123
+ var distA = this.distance(pointA);
2124
+ var distB = this.distance(pointB);
2125
+ if (distA < distB) {
2126
+ return pointA;
2127
+ }
2128
+ else {
2129
+ return pointB;
2130
+ }
2131
+ }
2132
+ /**
2133
+ * Find the one of two given points that's farest from this point.
2134
+ *
2135
+ * @method findFarestPoint
2136
+ * @instance
2137
+ * @memberof Vertex
2138
+ * @param {XYCoords | Vertex} pointA
2139
+ * @param {XYCoords | Vertex} pointB
2140
+ * @returns {XYCoords | Vertex}
2141
+ */
2142
+ findFarestPoint(pointA, pointB) {
2143
+ var distA = this.distance(pointA);
2144
+ var distB = this.distance(pointB);
2145
+ if (distA > distB) {
2146
+ return pointA;
2147
+ }
2148
+ else {
2149
+ return pointB;
2150
+ }
2151
+ }
1861
2152
  /**
1862
2153
  * Get a string representation of this vertex.
1863
2154
  *
@@ -1944,7 +2235,7 @@ Vertex$1.utils = {
1944
2235
  * @param {number?} precision - (optional) The numeric precision to be used (number of precision digits).
1945
2236
  * @returns {string}
1946
2237
  */
1947
- arrayToJSON(vertices, precision) {
2238
+ arrayToJSON: (vertices, precision) => {
1948
2239
  return JSON.stringify(vertices.map(function (vert) {
1949
2240
  return typeof precision === undefined
1950
2241
  ? { x: vert.x, y: vert.y }
@@ -1971,7 +2262,8 @@ Vertex$1.utils = {
1971
2262
  * @modified 2025-03-31 Added the `VertTuple.revert` method.
1972
2263
  * @modified 2025-04-15 Changed param of `VertTuple.moveTo` method from `Vertex` to `XYCoords`.
1973
2264
  * @modified 2025-04-15 Added method `VertTuple.move` method.
1974
- * @version 1.4.0
2265
+ * @modified 2026-06-10 Adding helper function `VertTuple.utils.calcCircumcircle`.
2266
+ * @version 1.5.0
1975
2267
  */
1976
2268
  /**
1977
2269
  * @classdesc An abstract base classes for vertex tuple constructs, like Lines or Vectors.
@@ -2291,6 +2583,10 @@ class VertTuple {
2291
2583
  VertTuple.vtutils = {
2292
2584
  dist2: (v, w) => {
2293
2585
  return (v.x - w.x) * (v.x - w.x) + (v.y - w.y) * (v.y - w.y);
2586
+ },
2587
+ calcCircumcircle: (p1, p2) => {
2588
+ const p1x = p1.x, p1y = p1.y, p2x = p2.x, p2y = p2.y, cx = 0.5 * (p1x + p2x), cy = 0.5 * (p1y + p2y);
2589
+ return { center: { x: cx, y: cy }, radius: Math.sqrt((p1x - cx) * (p1x - cx) + (p1y - cy) * (p1y - cy)) };
2294
2590
  }
2295
2591
  };
2296
2592