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.
- package/README.md +8 -0
- package/dist/index.esm.js +335 -39
- package/dist/index.esm.js.map +1 -1
- package/dist/index.esm.min.js +1 -1
- package/dist/index.esm.min.js.map +1 -1
- package/dist/plotboilerplate.browser.min.js +1 -1
- package/dist/plotboilerplate.browser.min.js.map +1 -1
- package/dist/plotboilerplate.js +335 -37
- package/dist/plotboilerplate.min.js +1 -1
- package/package.json +1 -1
- package/src/cjs/Circle.d.ts +70 -3
- package/src/cjs/Circle.js +159 -4
- package/src/cjs/Circle.js.map +1 -1
- package/src/cjs/Triangle.d.ts +40 -4
- package/src/cjs/Triangle.js +127 -31
- package/src/cjs/Triangle.js.map +1 -1
- package/src/cjs/VertTuple.d.ts +4 -1
- package/src/cjs/VertTuple.js +6 -1
- package/src/cjs/VertTuple.js.map +1 -1
- package/src/cjs/Vertex.d.ts +25 -2
- package/src/cjs/Vertex.js +43 -1
- package/src/cjs/Vertex.js.map +1 -1
- package/src/cjs/utils/algorithms/minimalContainingCircleFromPoints.d.ts +21 -0
- package/src/cjs/utils/algorithms/minimalContainingCircleFromPoints.js +73 -0
- package/src/cjs/utils/algorithms/minimalContainingCircleFromPoints.js.map +1 -0
- package/src/cjs/utils/algorithms/minimalEnclosingCircleFromPoints.d.ts +21 -0
- package/src/cjs/utils/algorithms/minimalEnclosingCircleFromPoints.js +73 -0
- package/src/cjs/utils/algorithms/minimalEnclosingCircleFromPoints.js.map +1 -0
- package/src/cjs/utils/algorithms/pointsMinimalContainingCircle.d.ts +21 -0
- package/src/cjs/utils/algorithms/pointsMinimalContainingCircle.js +73 -0
- package/src/cjs/utils/algorithms/pointsMinimalContainingCircle.js.map +1 -0
- package/src/cjs/utils/algorithms/solveApollonius3.d.ts +69 -0
- package/src/cjs/utils/algorithms/solveApollonius3.js +118 -0
- package/src/cjs/utils/algorithms/solveApollonius3.js.map +1 -0
- package/src/esm/Circle.d.ts +70 -3
- package/src/esm/Circle.js +159 -4
- package/src/esm/Circle.js.map +1 -1
- package/src/esm/Triangle.d.ts +40 -4
- package/src/esm/Triangle.js +128 -32
- package/src/esm/Triangle.js.map +1 -1
- package/src/esm/VertTuple.d.ts +4 -1
- package/src/esm/VertTuple.js +6 -1
- package/src/esm/VertTuple.js.map +1 -1
- package/src/esm/Vertex.d.ts +25 -2
- package/src/esm/Vertex.js +44 -2
- package/src/esm/Vertex.js.map +1 -1
- package/src/esm/utils/algorithms/minimalContainingCircleFromPoints.d.ts +21 -0
- package/src/esm/utils/algorithms/minimalContainingCircleFromPoints.js +69 -0
- package/src/esm/utils/algorithms/minimalContainingCircleFromPoints.js.map +1 -0
- package/src/esm/utils/algorithms/minimalEnclosingCircleFromPoints.d.ts +21 -0
- package/src/esm/utils/algorithms/minimalEnclosingCircleFromPoints.js +69 -0
- package/src/esm/utils/algorithms/minimalEnclosingCircleFromPoints.js.map +1 -0
- package/src/esm/utils/algorithms/pointsMinimalContainingCircle.d.ts +21 -0
- package/src/esm/utils/algorithms/pointsMinimalContainingCircle.js +69 -0
- package/src/esm/utils/algorithms/pointsMinimalContainingCircle.js.map +1 -0
- package/src/esm/utils/algorithms/solveApollonius3.d.ts +69 -0
- package/src/esm/utils/algorithms/solveApollonius3.js +114 -0
- package/src/esm/utils/algorithms/solveApollonius3.js.map +1 -0
- package/src/index-gl.js +5 -2
- package/src/index.js +25 -17
- package/src/ts/Circle.ts +178 -5
- package/src/ts/Triangle.ts +148 -41
- package/src/ts/VertTuple.ts +14 -1
- package/src/ts/Vertex.ts +44 -2
- package/src/ts/utils/algorithms/minimalEnclosingCircleFromPoints.ts +69 -0
- 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
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
* @
|
|
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
|
-
|
|
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
|