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
@@ -0,0 +1,114 @@
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
+ import { Circle } from "../../Circle";
69
+ import { Vertex } from "../../Vertex";
70
+ export const solveApollonius3 = (c1, c2, c3, circle1Inside, circle2Inside, circle3Inside) => {
71
+ // https://github.com/DIKU-Steiner/ProGAL/blob/master/src/ProGAL/geom2d/ApolloniusSolver.java
72
+ var x1 = c1.center.x;
73
+ var y1 = c1.center.y;
74
+ var r1 = c1.radius;
75
+ var x2 = c2.center.x;
76
+ var y2 = c2.center.y;
77
+ var r2 = c2.radius;
78
+ var x3 = c3.center.x;
79
+ var y3 = c3.center.y;
80
+ var r3 = c3.radius;
81
+ var s1 = Boolean(circle1Inside) ? 1 : -1;
82
+ var s2 = Boolean(circle2Inside) ? 1 : -1;
83
+ var s3 = Boolean(circle3Inside) ? 1 : -1;
84
+ // Currently optimized for fewest multiplications.
85
+ var v11 = 2 * x2 - 2 * x1;
86
+ var v12 = 2 * y2 - 2 * y1;
87
+ var v13 = x1 * x1 - x2 * x2 + y1 * y1 - y2 * y2 - r1 * r1 + r2 * r2;
88
+ var v14 = 2 * s2 * r2 - 2 * s1 * r1;
89
+ var v21 = 2 * x3 - 2 * x2;
90
+ var v22 = 2 * y3 - 2 * y2;
91
+ var v23 = x2 * x2 - x3 * x3 + y2 * y2 - y3 * y3 - r2 * r2 + r3 * r3;
92
+ var v24 = 2 * s3 * r3 - 2 * s2 * r2;
93
+ var w12 = v12 / v11;
94
+ var w13 = v13 / v11;
95
+ var w14 = v14 / v11;
96
+ var w22 = v22 / v21 - w12;
97
+ var w23 = v23 / v21 - w13;
98
+ var w24 = v24 / v21 - w14;
99
+ var P = -w23 / w22;
100
+ var Q = w24 / w22;
101
+ var M = -w12 * P - w13;
102
+ var N = w14 - w12 * Q;
103
+ var a = N * N + Q * Q - 1;
104
+ var b = 2 * M * N - 2 * N * x1 + 2 * P * Q - 2 * Q * y1 + 2 * s1 * r1;
105
+ var c = x1 * x1 + M * M - 2 * M * x1 + P * P + y1 * y1 - 2 * P * y1 - r1 * r1;
106
+ // Find a root of a quadratic equation. This requires the circle centers not
107
+ // to be e.g. colinear
108
+ var D = b * b - 4 * a * c;
109
+ var rs = (-b - Math.sqrt(D)) / (2 * a);
110
+ var xs = M + N * rs;
111
+ var ys = P + Q * rs;
112
+ return new Circle(new Vertex(xs, ys), Math.abs(rs));
113
+ };
114
+ //# sourceMappingURL=solveApollonius3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"solveApollonius3.js","sourceRoot":"","sources":["../../../ts/utils/algorithms/solveApollonius3.ts"],"names":[],"mappings":"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"}
package/src/index-gl.js CHANGED
@@ -73,8 +73,11 @@
73
73
  // +---------------------------------------------------------------------------------
74
74
  // | Initialize dat.gui
75
75
  // +-------------------------------
76
- pb.createGUI();
77
- // END init dat.gui
76
+ try {
77
+ pb.createGUI();
78
+ } catch (e) {
79
+ console.error("Failed to initialize GUI", e);
80
+ }
78
81
 
79
82
  // +---------------------------------------------------------------------------------
80
83
  // | Add a mouse listener to track the mouse position.
package/src/index.js CHANGED
@@ -81,28 +81,36 @@
81
81
  // +---------------------------------------------------------------------------------
82
82
  // | Initialize dat.gui
83
83
  // +-------------------------------
84
- pb.createGUI();
84
+ try {
85
+ pb.createGUI();
86
+ } catch (e) {
87
+ console.error("Failed to initialize GUI", e);
88
+ }
85
89
 
86
90
  // +---------------------------------------------------------------------------------
87
91
  // | Add stats.
88
92
  // +-------------------------------
89
- var stats = {
90
- mouseX: 0,
91
- mouseY: 0
92
- };
93
- var uiStats = new UIStats(stats);
94
- stats = uiStats.proxy;
95
- uiStats.add("mouseX");
96
- uiStats.add("mouseY");
93
+ try {
94
+ var stats = {
95
+ mouseX: 0,
96
+ mouseY: 0
97
+ };
98
+ var uiStats = new UIStats(stats);
99
+ stats = uiStats.proxy;
100
+ uiStats.add("mouseX");
101
+ uiStats.add("mouseY");
97
102
 
98
- // +---------------------------------------------------------------------------------
99
- // | Add a mouse listener to track the mouse position.
100
- // +-------------------------------
101
- new MouseHandler(pb.eventCatcher).move(function (e) {
102
- var relPos = pb.transformMousePosition(e.params.pos.x, e.params.pos.y);
103
- stats.mouseX = relPos.x;
104
- stats.mouseY = relPos.y;
105
- });
103
+ // +---------------------------------------------------------------------------------
104
+ // | Add a mouse listener to track the mouse position.
105
+ // +-------------------------------
106
+ new MouseHandler(pb.eventCatcher).move(function (e) {
107
+ var relPos = pb.transformMousePosition(e.params.pos.x, e.params.pos.y);
108
+ stats.mouseX = relPos.x;
109
+ stats.mouseY = relPos.y;
110
+ });
111
+ } catch (e) {
112
+ console.error("Failed to initialize stats and/or mouse handler.", e);
113
+ }
106
114
 
107
115
  // Use a helper function to build all demo-drawables.
108
116
  var drawables = createDemoDrawables(pb, "example-image.png", function () {
package/src/ts/Circle.ts CHANGED
@@ -14,7 +14,15 @@
14
14
  * @modified 2022-08-23 Added the `closestPoint` function.
15
15
  * @modified 2025-04-09 Added the `Circle.move(amount: XYCoords)` method.
16
16
  * @modified 2025-04-16 Class `Circle` now implements interface `Intersectable`.
17
- * @version 1.5.0
17
+ * @modified 2026-06-10 Adding the utility function `Circle.circleUtils.containsPoint`.
18
+ * @modified 2026-06-10 Adding the `Circle.clone` method.
19
+ * @modified 2026-01-13 Adding helper function `Circle.circleUtils.containsPoint` and refactored the member method `containsPoint`.
20
+ * @modified 2026-07-03 Adding the optional `epsilon` parameter to the `Circle.containsCircle` method.
21
+ * @modified 2026-07-03 Fixing the `Circle.clone` method; the center had not been cloned at all, this was fixed.
22
+ * @modified 2026-07-08 Adding the `Circle.setRadius` method (for chaining).
23
+ * @mofified 2026-07-31 Adding the `radicalAxis(Circle)` method. Added the `Circle.circleUtils.createRadicalAxisHelperCircle` and `.circleDistance` helper methods.
24
+ * @modified 2026-08-03 Adding `Circle.tangentsFromPoint`.
25
+ * @version 1.7.0
18
26
  **/
19
27
 
20
28
  import { Bounds } from "./Bounds";
@@ -23,8 +31,15 @@ import { UIDGenerator } from "./UIDGenerator";
23
31
  import { Vector } from "./Vector";
24
32
  import { VertTuple } from "./VertTuple";
25
33
  import { Vertex } from "./Vertex";
34
+ import { geomutils } from "./geomutils";
26
35
  import { IBounded, Intersectable, SVGSerializable, UID, XYCoords } from "./interfaces";
27
36
 
37
+ export interface ICircle {
38
+ center: XYCoords;
39
+ radius: number;
40
+ radius_squared?: number;
41
+ }
42
+
28
43
  /**
29
44
  * @classdesc A simple circle: center point and radius.
30
45
  *
@@ -36,7 +51,7 @@ import { IBounded, Intersectable, SVGSerializable, UID, XYCoords } from "./inter
36
51
  * @requires UID
37
52
  * @requires UIDGenerator
38
53
  **/
39
- export class Circle implements IBounded, Intersectable, SVGSerializable {
54
+ export class Circle implements IBounded, ICircle, Intersectable, SVGSerializable {
40
55
  /**
41
56
  * Required to generate proper CSS classes and other class related IDs.
42
57
  **/
@@ -88,6 +103,21 @@ export class Circle implements IBounded, Intersectable, SVGSerializable {
88
103
  this.radius = radius;
89
104
  }
90
105
 
106
+ /**
107
+ * Set the radius of this circle.
108
+ * The method is meant for chaining, you may also alter the `radius` attribute directly.
109
+ *
110
+ * @method setRadius
111
+ * @param {number} radius - The amount to move.
112
+ * @instance
113
+ * @memberof Circle
114
+ * @return {Circle} this for chaining
115
+ **/
116
+ setRadius(radius: number): Circle {
117
+ this.radius = radius;
118
+ return this;
119
+ }
120
+
91
121
  /**
92
122
  * Move the circle by the given amount.
93
123
  *
@@ -112,7 +142,8 @@ export class Circle implements IBounded, Intersectable, SVGSerializable {
112
142
  * @return {boolean} `true` if the given point is inside this circle.
113
143
  */
114
144
  containsPoint(point: XYCoords): boolean {
115
- return this.center.distance(point) < this.radius;
145
+ // return this.center.distance(point) < this.radius;//
146
+ return Circle.circleUtils.containsPoint(this.center, this.radius, point);
116
147
  }
117
148
 
118
149
  /**
@@ -124,8 +155,12 @@ export class Circle implements IBounded, Intersectable, SVGSerializable {
124
155
  * @memberof Circle
125
156
  * @return {boolean} `true` if any only if the given circle is completely inside this circle.
126
157
  */
127
- containsCircle(circle: Circle): boolean {
128
- return this.center.distance(circle.center) + circle.radius < this.radius;
158
+ containsCircle(circle: Circle, epsilon?: number): boolean {
159
+ if (typeof epsilon === "undefined" || Number.isNaN(epsilon)) {
160
+ return this.center.distance(circle.center) + circle.radius < this.radius;
161
+ } else {
162
+ return this.center.distance(circle.center) + circle.radius < this.radius + Math.abs(epsilon);
163
+ }
129
164
  }
130
165
 
131
166
  /**
@@ -347,6 +382,43 @@ export class Circle implements IBounded, Intersectable, SVGSerializable {
347
382
  }
348
383
  //--- END --- Implement interface `Intersectable`
349
384
 
385
+ /**
386
+ * Calculate the radical axis of this and a different circle.
387
+ * The two circles must not be co-centric.
388
+ *
389
+ * See this article for details:
390
+ * https://www.cut-the-knot.org/Curriculum/Geometry/GeoGebra/RadicalAxes.shtml
391
+ *
392
+ * @method radicalAxis
393
+ * @instance
394
+ * @memberof Circle
395
+ * @param {Circle} circleB - The second circle to calculated the radical axis for.
396
+ * @return {Line} A line defining the radical axis of the two circles.
397
+ **/
398
+ radicalAxis(circleB: Circle): Line {
399
+ var helperCircle = Circle.circleUtils.createRadicalAxisHelperCircle(this, circleB);
400
+ var intersectionLineA = this.circleIntersection(helperCircle);
401
+ var intersectionLineB = circleB.circleIntersection(helperCircle);
402
+ var intersection = this.circleIntersection(circleB);
403
+
404
+ if (!intersectionLineA || !intersectionLineB) {
405
+ console.error("Critical error: none of the two intersections must be null.");
406
+ return intersection; // Fallback
407
+ }
408
+ var lineA = new Line(intersectionLineA.a, intersectionLineA.b);
409
+ var lineB = new Line(intersectionLineB.a, intersectionLineB.b);
410
+ var firstRadicalAxisPoint = lineA.intersection(lineB);
411
+ var centerConnectLine = new Line(this.center, circleB.center);
412
+ var secondRadicalAxisPoint = centerConnectLine.getClosestPoint(firstRadicalAxisPoint);
413
+ // Create a mirrored version of the second radical axis point so both are located
414
+ // symmetrical from the circle connect point.
415
+ // (currently the second point is located ON the connect line)
416
+ var secondRadicalAxisPoint_mirrored = secondRadicalAxisPoint.clone().scale(2.0, firstRadicalAxisPoint);
417
+
418
+ var radicalAxis = new Line(firstRadicalAxisPoint, secondRadicalAxisPoint_mirrored);
419
+ return intersection && intersection.length() > radicalAxis.length() ? intersection : radicalAxis;
420
+ }
421
+
350
422
  /**
351
423
  * Calculate the closest point on the outline of this circle to the given point.
352
424
  *
@@ -370,6 +442,77 @@ export class Circle implements IBounded, Intersectable, SVGSerializable {
370
442
  }
371
443
  }
372
444
 
445
+ /**
446
+ * Get the two tangent vectors for the given point.
447
+ * If the point is on or in the circle then null is returned.
448
+ *
449
+ * @method tangentsFromPoint
450
+ * @instance
451
+ * @memberof Circle
452
+ * @param {Vertex} vert - The point to find the two tangents for.
453
+ * @return {[Vector,Vector]} The two tangent vector
454
+ **/
455
+ tangentsFromPoint(vert: Vertex): [Vector, Vector] {
456
+ // Inspired by
457
+ // https://www.omnicalculator.com/math/tangent-circle
458
+ const centerDistance: number = this.center.distance(vert);
459
+ const tangentLength: number = Math.sqrt(centerDistance * centerDistance - this.radius * this.radius);
460
+ // console.log("this.radius ", this.radius, "centerDistance", centerDistance, "tangentLength", tangentLength);
461
+ if (Number.isNaN(tangentLength)) {
462
+ // vertex is inside circle
463
+ console.log("tangentLength is NaN", tangentLength);
464
+ return null;
465
+ }
466
+
467
+ const helperCircle = new Circle(vert, tangentLength);
468
+ const intersection = this.circleIntersection(helperCircle);
469
+ if (!intersection) {
470
+ // No intersection (vertex is inside circle)
471
+ return null;
472
+ }
473
+
474
+ return [new Vector(intersection.a, vert), new Vector(intersection.b, vert)];
475
+ }
476
+
477
+ /**
478
+ * Create a deep copy of this circle.
479
+ *
480
+ * @method clone
481
+ * @return {Circle} A new circle, an exact copy of this one.
482
+ * @instance
483
+ * @memberof Circle
484
+ **/
485
+ clone(): Circle {
486
+ return new Circle(this.center.clone(), this.radius);
487
+ }
488
+
489
+ static fromICircle(obj: ICircle): Circle {
490
+ return new Circle(new Vertex(obj.center), obj.radius);
491
+ }
492
+
493
+ // static fromObject(obj: object): Circle {
494
+ // if (!obj) {
495
+ // return null;
496
+ // }
497
+ // if (typeof obj !== "object") {
498
+ // return null;
499
+ // }
500
+ // if (!obj.hasOwnProperty("center") || typeof (obj as any).center != "object") {
501
+ // return null;
502
+ // }
503
+ // if (!obj.hasOwnProperty("radius") || typeof !(obj as any).radius != "number") {
504
+ // return null;
505
+ // }
506
+ // var center = (obj as any).center;
507
+ // if (!center.hasOwnProperty("x") || typeof (center as any).x != "number") {
508
+ // return null;
509
+ // }
510
+ // if (!center.hasOwnProperty("y") || typeof (center as any).y != "number") {
511
+ // return null;
512
+ // }
513
+ // return new Circle(new Vertex(center), (obj as any).radius);
514
+ // }
515
+
373
516
  /**
374
517
  * This function should invalidate any installed listeners and invalidate this object.
375
518
  * After calling this function the object might not hold valid data any more and
@@ -385,6 +528,36 @@ export class Circle implements IBounded, Intersectable, SVGSerializable {
385
528
  /* return new Vertex( Math.sin(angle) * radius,
386
529
  Math.cos(angle) * radius ); */
387
530
  return new Vertex(Math.cos(angle) * radius, Math.sin(angle) * radius);
531
+ },
532
+
533
+ containsPoint: (circleCenter: XYCoords, circleRadius: number, point: XYCoords): boolean => {
534
+ // TODO: cleanup
535
+ // return (
536
+ // (circle.center.x - point.x) * (circle.center.x - point.x) + (circle.center.y - point.y) * (circle.center.y - point.y) <=
537
+ // circle.radius * circle.radius
538
+ // );
539
+ return geomutils.dist4(point.x, point.y, circleCenter.x, circleCenter.y) < circleRadius;
540
+ },
541
+
542
+ createRadicalAxisHelperCircle: (circleA: Circle, circleB: Circle): Circle => {
543
+ // We need to create a helper circle that intersects BOTH other circles,
544
+ // each in TWO points.
545
+ var circleConnectLine = new Vector(circleA.center.clone(), circleB.center);
546
+ circleConnectLine.a.set(circleConnectLine.vertAt(0.5));
547
+ // Get the perpendicular by half the circle distance.
548
+ var bisector = circleConnectLine.perp();
549
+ var bisectorLength = Math.max(circleA.radius, circleB.radius, bisector.length());
550
+ bisector.setLength(bisectorLength);
551
+ return new Circle(bisector.b, bisector.b.distance(circleA.center));
552
+ },
553
+
554
+ /**
555
+ * Calculate the outer distance between two circles. If the circles touch then the
556
+ * distance is 0.0.
557
+ * If the circles intersect then the distance in negative.
558
+ */
559
+ circleDistance: (circleA: Circle, circleB: Circle): number => {
560
+ return circleA.center.distance(circleB.center) - circleA.radius - circleB.radius;
388
561
  }
389
562
  };
390
563
  } // END class