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
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
|
-
* @
|
|
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
|
-
|
|
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
|
-
* @
|
|
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 {
|
|
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
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
this.
|
|
920
|
-
|
|
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(
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
|