plotboilerplate 1.28.1 → 1.30.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 +527 -53
- 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 +528 -51
- package/dist/plotboilerplate.min.js +1 -1
- package/package.json +1 -1
- package/src/cjs/Circle.d.ts +86 -3
- package/src/cjs/Circle.js +179 -4
- package/src/cjs/Circle.js.map +1 -1
- package/src/cjs/CubicBezierCurve.d.ts +34 -3
- package/src/cjs/CubicBezierCurve.js +79 -12
- package/src/cjs/CubicBezierCurve.js.map +1 -1
- package/src/cjs/Line.d.ts +46 -1
- package/src/cjs/Line.js +80 -1
- package/src/cjs/Line.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 +16 -2
- package/src/cjs/VertTuple.js +20 -2
- 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 +86 -3
- package/src/esm/Circle.js +179 -4
- package/src/esm/Circle.js.map +1 -1
- package/src/esm/CubicBezierCurve.d.ts +34 -3
- package/src/esm/CubicBezierCurve.js +79 -12
- package/src/esm/CubicBezierCurve.js.map +1 -1
- package/src/esm/Line.d.ts +46 -1
- package/src/esm/Line.js +80 -1
- package/src/esm/Line.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 +16 -2
- package/src/esm/VertTuple.js +20 -2
- 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 +6 -4
- package/src/index.js +25 -17
- package/src/ts/Circle.ts +200 -5
- package/src/ts/CubicBezierCurve.ts +133 -48
- package/src/ts/Line.ts +84 -1
- package/src/ts/Triangle.ts +148 -41
- package/src/ts/VertTuple.ts +29 -2
- 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
package/src/ts/Triangle.ts
CHANGED
|
@@ -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
|
-
* @
|
|
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 {
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
} else {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
|
|
388
|
-
|
|
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)
|
|
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
|
}
|
package/src/ts/VertTuple.ts
CHANGED
|
@@ -16,12 +16,16 @@
|
|
|
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
|
-
* @
|
|
19
|
+
* @modified 2026-06-10 Adding helper function `VertTuple.utils.calcCircumcircle`.
|
|
20
|
+
* @modified 2026-06-17 Adding method `VertTuple.asLine` for converting Vector to Line instances.
|
|
21
|
+
* @version 1.6.0
|
|
20
22
|
*/
|
|
21
23
|
|
|
22
24
|
import { Vertex } from "./Vertex";
|
|
23
25
|
import { UIDGenerator } from "./UIDGenerator";
|
|
24
26
|
import { XYCoords, UID } from "./interfaces";
|
|
27
|
+
import { ICircle } from "./Circle";
|
|
28
|
+
import { Line } from "./Line";
|
|
25
29
|
|
|
26
30
|
/**
|
|
27
31
|
* @classdesc An abstract base classes for vertex tuple constructs, like Lines or Vectors.
|
|
@@ -358,7 +362,7 @@ export class VertTuple<T extends VertTuple<T>> {
|
|
|
358
362
|
/**
|
|
359
363
|
* Create a deep clone of this instance.
|
|
360
364
|
*
|
|
361
|
-
* @method
|
|
365
|
+
* @method clone
|
|
362
366
|
* @return {T} A type safe clone if this instance.
|
|
363
367
|
* @instance
|
|
364
368
|
* @memberof VertTuple
|
|
@@ -367,6 +371,18 @@ export class VertTuple<T extends VertTuple<T>> {
|
|
|
367
371
|
return this.factory(this.a.clone(), this.b.clone());
|
|
368
372
|
}
|
|
369
373
|
|
|
374
|
+
/**
|
|
375
|
+
* Converts this `Vector` to a `Line` (segment).
|
|
376
|
+
*
|
|
377
|
+
* @method asLine
|
|
378
|
+
* @return {T} A type safe clone if this instance.
|
|
379
|
+
* @instance
|
|
380
|
+
* @memberof VertTuple
|
|
381
|
+
**/
|
|
382
|
+
asLine(): Line {
|
|
383
|
+
return new Line(this.a, this.b);
|
|
384
|
+
}
|
|
385
|
+
|
|
370
386
|
/**
|
|
371
387
|
* Create a string representation of this line.
|
|
372
388
|
*
|
|
@@ -396,6 +412,17 @@ export class VertTuple<T extends VertTuple<T>> {
|
|
|
396
412
|
static vtutils = {
|
|
397
413
|
dist2: (v: XYCoords, w: XYCoords) => {
|
|
398
414
|
return (v.x - w.x) * (v.x - w.x) + (v.y - w.y) * (v.y - w.y);
|
|
415
|
+
},
|
|
416
|
+
|
|
417
|
+
calcCircumcircle: (p1: XYCoords, p2: XYCoords): ICircle => {
|
|
418
|
+
const p1x = p1.x,
|
|
419
|
+
p1y = p1.y,
|
|
420
|
+
p2x = p2.x,
|
|
421
|
+
p2y = p2.y,
|
|
422
|
+
cx = 0.5 * (p1x + p2x),
|
|
423
|
+
cy = 0.5 * (p1y + p2y);
|
|
424
|
+
|
|
425
|
+
return { center: { x: cx, y: cy }, radius: Math.sqrt((p1x - cx) * (p1x - cx) + (p1y - cy) * (p1y - cy)) };
|
|
399
426
|
}
|
|
400
427
|
};
|
|
401
428
|
}
|
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
|
-
* @
|
|
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
|
+
};
|