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/Circle.ts
CHANGED
|
@@ -14,7 +14,16 @@
|
|
|
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
|
+
* @modified 2026-08-16 Adding the `Circle.sectorAngleByArcLength` method and the `Circle.circleUtils.sectorAngleByArcLength` helper method.
|
|
26
|
+
* @version 1.8.0
|
|
18
27
|
**/
|
|
19
28
|
|
|
20
29
|
import { Bounds } from "./Bounds";
|
|
@@ -23,8 +32,15 @@ import { UIDGenerator } from "./UIDGenerator";
|
|
|
23
32
|
import { Vector } from "./Vector";
|
|
24
33
|
import { VertTuple } from "./VertTuple";
|
|
25
34
|
import { Vertex } from "./Vertex";
|
|
35
|
+
import { geomutils } from "./geomutils";
|
|
26
36
|
import { IBounded, Intersectable, SVGSerializable, UID, XYCoords } from "./interfaces";
|
|
27
37
|
|
|
38
|
+
export interface ICircle {
|
|
39
|
+
center: XYCoords;
|
|
40
|
+
radius: number;
|
|
41
|
+
radius_squared?: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
28
44
|
/**
|
|
29
45
|
* @classdesc A simple circle: center point and radius.
|
|
30
46
|
*
|
|
@@ -36,7 +52,7 @@ import { IBounded, Intersectable, SVGSerializable, UID, XYCoords } from "./inter
|
|
|
36
52
|
* @requires UID
|
|
37
53
|
* @requires UIDGenerator
|
|
38
54
|
**/
|
|
39
|
-
export class Circle implements IBounded, Intersectable, SVGSerializable {
|
|
55
|
+
export class Circle implements IBounded, ICircle, Intersectable, SVGSerializable {
|
|
40
56
|
/**
|
|
41
57
|
* Required to generate proper CSS classes and other class related IDs.
|
|
42
58
|
**/
|
|
@@ -88,6 +104,21 @@ export class Circle implements IBounded, Intersectable, SVGSerializable {
|
|
|
88
104
|
this.radius = radius;
|
|
89
105
|
}
|
|
90
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Set the radius of this circle.
|
|
109
|
+
* The method is meant for chaining, you may also alter the `radius` attribute directly.
|
|
110
|
+
*
|
|
111
|
+
* @method setRadius
|
|
112
|
+
* @param {number} radius - The amount to move.
|
|
113
|
+
* @instance
|
|
114
|
+
* @memberof Circle
|
|
115
|
+
* @return {Circle} this for chaining
|
|
116
|
+
**/
|
|
117
|
+
setRadius(radius: number): Circle {
|
|
118
|
+
this.radius = radius;
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
121
|
+
|
|
91
122
|
/**
|
|
92
123
|
* Move the circle by the given amount.
|
|
93
124
|
*
|
|
@@ -112,7 +143,8 @@ export class Circle implements IBounded, Intersectable, SVGSerializable {
|
|
|
112
143
|
* @return {boolean} `true` if the given point is inside this circle.
|
|
113
144
|
*/
|
|
114
145
|
containsPoint(point: XYCoords): boolean {
|
|
115
|
-
return this.center.distance(point) < this.radius
|
|
146
|
+
// return this.center.distance(point) < this.radius;//
|
|
147
|
+
return Circle.circleUtils.containsPoint(this.center, this.radius, point);
|
|
116
148
|
}
|
|
117
149
|
|
|
118
150
|
/**
|
|
@@ -124,8 +156,12 @@ export class Circle implements IBounded, Intersectable, SVGSerializable {
|
|
|
124
156
|
* @memberof Circle
|
|
125
157
|
* @return {boolean} `true` if any only if the given circle is completely inside this circle.
|
|
126
158
|
*/
|
|
127
|
-
containsCircle(circle: Circle): boolean {
|
|
128
|
-
|
|
159
|
+
containsCircle(circle: Circle, epsilon?: number): boolean {
|
|
160
|
+
if (typeof epsilon === "undefined" || Number.isNaN(epsilon)) {
|
|
161
|
+
return this.center.distance(circle.center) + circle.radius < this.radius;
|
|
162
|
+
} else {
|
|
163
|
+
return this.center.distance(circle.center) + circle.radius < this.radius + Math.abs(epsilon);
|
|
164
|
+
}
|
|
129
165
|
}
|
|
130
166
|
|
|
131
167
|
/**
|
|
@@ -347,6 +383,43 @@ export class Circle implements IBounded, Intersectable, SVGSerializable {
|
|
|
347
383
|
}
|
|
348
384
|
//--- END --- Implement interface `Intersectable`
|
|
349
385
|
|
|
386
|
+
/**
|
|
387
|
+
* Calculate the radical axis of this and a different circle.
|
|
388
|
+
* The two circles must not be co-centric.
|
|
389
|
+
*
|
|
390
|
+
* See this article for details:
|
|
391
|
+
* https://www.cut-the-knot.org/Curriculum/Geometry/GeoGebra/RadicalAxes.shtml
|
|
392
|
+
*
|
|
393
|
+
* @method radicalAxis
|
|
394
|
+
* @instance
|
|
395
|
+
* @memberof Circle
|
|
396
|
+
* @param {Circle} circleB - The second circle to calculated the radical axis for.
|
|
397
|
+
* @return {Line} A line defining the radical axis of the two circles.
|
|
398
|
+
**/
|
|
399
|
+
radicalAxis(circleB: Circle): Line {
|
|
400
|
+
var helperCircle = Circle.circleUtils.createRadicalAxisHelperCircle(this, circleB);
|
|
401
|
+
var intersectionLineA = this.circleIntersection(helperCircle);
|
|
402
|
+
var intersectionLineB = circleB.circleIntersection(helperCircle);
|
|
403
|
+
var intersection = this.circleIntersection(circleB);
|
|
404
|
+
|
|
405
|
+
if (!intersectionLineA || !intersectionLineB) {
|
|
406
|
+
console.error("Critical error: none of the two intersections must be null.");
|
|
407
|
+
return intersection; // Fallback
|
|
408
|
+
}
|
|
409
|
+
var lineA = new Line(intersectionLineA.a, intersectionLineA.b);
|
|
410
|
+
var lineB = new Line(intersectionLineB.a, intersectionLineB.b);
|
|
411
|
+
var firstRadicalAxisPoint = lineA.intersection(lineB);
|
|
412
|
+
var centerConnectLine = new Line(this.center, circleB.center);
|
|
413
|
+
var secondRadicalAxisPoint = centerConnectLine.getClosestPoint(firstRadicalAxisPoint);
|
|
414
|
+
// Create a mirrored version of the second radical axis point so both are located
|
|
415
|
+
// symmetrical from the circle connect point.
|
|
416
|
+
// (currently the second point is located ON the connect line)
|
|
417
|
+
var secondRadicalAxisPoint_mirrored = secondRadicalAxisPoint.clone().scale(2.0, firstRadicalAxisPoint);
|
|
418
|
+
|
|
419
|
+
var radicalAxis = new Line(firstRadicalAxisPoint, secondRadicalAxisPoint_mirrored);
|
|
420
|
+
return intersection && intersection.length() > radicalAxis.length() ? intersection : radicalAxis;
|
|
421
|
+
}
|
|
422
|
+
|
|
350
423
|
/**
|
|
351
424
|
* Calculate the closest point on the outline of this circle to the given point.
|
|
352
425
|
*
|
|
@@ -370,6 +443,87 @@ export class Circle implements IBounded, Intersectable, SVGSerializable {
|
|
|
370
443
|
}
|
|
371
444
|
}
|
|
372
445
|
|
|
446
|
+
/**
|
|
447
|
+
* Get the two tangent vectors for the given point.
|
|
448
|
+
* If the point is on or in the circle then null is returned.
|
|
449
|
+
*
|
|
450
|
+
* @method tangentsFromPoint
|
|
451
|
+
* @instance
|
|
452
|
+
* @memberof Circle
|
|
453
|
+
* @param {Vertex} vert - The point to find the two tangents for.
|
|
454
|
+
* @return {[Vector,Vector]} The two tangent vector
|
|
455
|
+
**/
|
|
456
|
+
tangentsFromPoint(vert: Vertex): [Vector, Vector] {
|
|
457
|
+
// Inspired by
|
|
458
|
+
// https://www.omnicalculator.com/math/tangent-circle
|
|
459
|
+
const centerDistance: number = this.center.distance(vert);
|
|
460
|
+
const tangentLength: number = Math.sqrt(centerDistance * centerDistance - this.radius * this.radius);
|
|
461
|
+
// console.log("this.radius ", this.radius, "centerDistance", centerDistance, "tangentLength", tangentLength);
|
|
462
|
+
if (Number.isNaN(tangentLength)) {
|
|
463
|
+
// vertex is inside circle
|
|
464
|
+
console.log("tangentLength is NaN", tangentLength);
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const helperCircle = new Circle(vert, tangentLength);
|
|
469
|
+
const intersection = this.circleIntersection(helperCircle);
|
|
470
|
+
if (!intersection) {
|
|
471
|
+
// No intersection (vertex is inside circle)
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return [new Vector(intersection.a, vert), new Vector(intersection.b, vert)];
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Calculate inner sector angle for this circle and a given circle arc length.
|
|
480
|
+
*
|
|
481
|
+
* @param {number} sectorArcLength - The desired arc length (in units).
|
|
482
|
+
* @returns The sector's inner angle (in radians).
|
|
483
|
+
*/
|
|
484
|
+
sectorAngleByArcLength(sectorArcLength: number) {
|
|
485
|
+
return Circle.circleUtils.sectorAngleByArcLength(sectorArcLength, this.radius);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Create a deep copy of this circle.
|
|
490
|
+
*
|
|
491
|
+
* @method clone
|
|
492
|
+
* @return {Circle} A new circle, an exact copy of this one.
|
|
493
|
+
* @instance
|
|
494
|
+
* @memberof Circle
|
|
495
|
+
**/
|
|
496
|
+
clone(): Circle {
|
|
497
|
+
return new Circle(this.center.clone(), this.radius);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
static fromICircle(obj: ICircle): Circle {
|
|
501
|
+
return new Circle(new Vertex(obj.center), obj.radius);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// static fromObject(obj: object): Circle {
|
|
505
|
+
// if (!obj) {
|
|
506
|
+
// return null;
|
|
507
|
+
// }
|
|
508
|
+
// if (typeof obj !== "object") {
|
|
509
|
+
// return null;
|
|
510
|
+
// }
|
|
511
|
+
// if (!obj.hasOwnProperty("center") || typeof (obj as any).center != "object") {
|
|
512
|
+
// return null;
|
|
513
|
+
// }
|
|
514
|
+
// if (!obj.hasOwnProperty("radius") || typeof !(obj as any).radius != "number") {
|
|
515
|
+
// return null;
|
|
516
|
+
// }
|
|
517
|
+
// var center = (obj as any).center;
|
|
518
|
+
// if (!center.hasOwnProperty("x") || typeof (center as any).x != "number") {
|
|
519
|
+
// return null;
|
|
520
|
+
// }
|
|
521
|
+
// if (!center.hasOwnProperty("y") || typeof (center as any).y != "number") {
|
|
522
|
+
// return null;
|
|
523
|
+
// }
|
|
524
|
+
// return new Circle(new Vertex(center), (obj as any).radius);
|
|
525
|
+
// }
|
|
526
|
+
|
|
373
527
|
/**
|
|
374
528
|
* This function should invalidate any installed listeners and invalidate this object.
|
|
375
529
|
* After calling this function the object might not hold valid data any more and
|
|
@@ -385,6 +539,47 @@ export class Circle implements IBounded, Intersectable, SVGSerializable {
|
|
|
385
539
|
/* return new Vertex( Math.sin(angle) * radius,
|
|
386
540
|
Math.cos(angle) * radius ); */
|
|
387
541
|
return new Vertex(Math.cos(angle) * radius, Math.sin(angle) * radius);
|
|
542
|
+
},
|
|
543
|
+
|
|
544
|
+
containsPoint: (circleCenter: XYCoords, circleRadius: number, point: XYCoords): boolean => {
|
|
545
|
+
// TODO: cleanup
|
|
546
|
+
// return (
|
|
547
|
+
// (circle.center.x - point.x) * (circle.center.x - point.x) + (circle.center.y - point.y) * (circle.center.y - point.y) <=
|
|
548
|
+
// circle.radius * circle.radius
|
|
549
|
+
// );
|
|
550
|
+
return geomutils.dist4(point.x, point.y, circleCenter.x, circleCenter.y) < circleRadius;
|
|
551
|
+
},
|
|
552
|
+
|
|
553
|
+
createRadicalAxisHelperCircle: (circleA: Circle, circleB: Circle): Circle => {
|
|
554
|
+
// We need to create a helper circle that intersects BOTH other circles,
|
|
555
|
+
// each in TWO points.
|
|
556
|
+
var circleConnectLine = new Vector(circleA.center.clone(), circleB.center);
|
|
557
|
+
circleConnectLine.a.set(circleConnectLine.vertAt(0.5));
|
|
558
|
+
// Get the perpendicular by half the circle distance.
|
|
559
|
+
var bisector = circleConnectLine.perp();
|
|
560
|
+
var bisectorLength = Math.max(circleA.radius, circleB.radius, bisector.length());
|
|
561
|
+
bisector.setLength(bisectorLength);
|
|
562
|
+
return new Circle(bisector.b, bisector.b.distance(circleA.center));
|
|
563
|
+
},
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Calculate the outer distance between two circles. If the circles touch then the
|
|
567
|
+
* distance is 0.0.
|
|
568
|
+
* If the circles intersect then the distance in negative.
|
|
569
|
+
*/
|
|
570
|
+
circleDistance: (circleA: Circle, circleB: Circle): number => {
|
|
571
|
+
return circleA.center.distance(circleB.center) - circleA.radius - circleB.radius;
|
|
572
|
+
},
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Calculate the inner sector angle for a given circle arc length and radius.
|
|
576
|
+
*
|
|
577
|
+
* @param {number} sectorArcLength - The desired arc length.
|
|
578
|
+
* @param {number} circleRadius - The circle's radius.
|
|
579
|
+
* @returns The sector angle in radians.
|
|
580
|
+
*/
|
|
581
|
+
sectorAngleByArcLength: (sectorArcLength: number, circleRadius: number) => {
|
|
582
|
+
return sectorArcLength / circleRadius;
|
|
388
583
|
}
|
|
389
584
|
};
|
|
390
585
|
} // END class
|
|
@@ -32,8 +32,9 @@
|
|
|
32
32
|
* @modified 2025-04-18 Added evaluation method for cubic Bézier curves `CubicBezierCurve.utils.evaluateT`.
|
|
33
33
|
* @modified 2025-04-18 Refactored method `CubicBezierCurve.getPointAt` to use `evaluateT`.
|
|
34
34
|
* @modified 2025-04-18 Fixed the `CubicBezierCurve.getBounds` method: now returning the real bounding box. Before it was an approximated one.
|
|
35
|
-
* @modified 2025
|
|
36
|
-
* @
|
|
35
|
+
* @modified 2025-04-18 Added helper methods for bounding box calculation `CubucBezierCurve.util.cubicPolyMinMax` and `cubicPoly`.
|
|
36
|
+
* @modified 2026-09-09 Adding methods `CubicBezierCurve.trimStartEnd` and `CubicBezierCurve.trimStartEndAt`.
|
|
37
|
+
* @version 2.10.0
|
|
37
38
|
*
|
|
38
39
|
* @file CubicBezierCurve
|
|
39
40
|
* @public
|
|
@@ -401,30 +402,30 @@ export class CubicBezierCurve implements IBounded, Intersectable, PathSegment {
|
|
|
401
402
|
// Thanks to Richard "RM" for the Bézier bounds calculatin
|
|
402
403
|
// https://jsfiddle.net/SalixAlba/QQnvm/4/
|
|
403
404
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
405
|
+
const xMinMax = CubicBezierCurve.utils.cubicPolyMinMax(
|
|
406
|
+
this.startPoint.x,
|
|
407
|
+
this.startControlPoint.x,
|
|
408
|
+
this.endControlPoint.x,
|
|
409
|
+
this.endPoint.x
|
|
410
|
+
);
|
|
411
|
+
const xl = xMinMax.min;
|
|
412
|
+
const xh = xMinMax.max;
|
|
413
|
+
|
|
414
|
+
const yMinMax = CubicBezierCurve.utils.cubicPolyMinMax(
|
|
415
|
+
this.startPoint.y,
|
|
416
|
+
this.startControlPoint.y,
|
|
417
|
+
this.endControlPoint.y,
|
|
418
|
+
this.endPoint.y
|
|
419
|
+
);
|
|
420
|
+
const yl = yMinMax.min;
|
|
421
|
+
const yh = yMinMax.max;
|
|
422
|
+
|
|
423
|
+
return Bounds.computeFromVertices([
|
|
424
|
+
{ x: xl, y: yl },
|
|
425
|
+
{ x: xl, y: yh },
|
|
426
|
+
{ x: xh, y: yh },
|
|
427
|
+
{ x: xh, y: yl }
|
|
428
|
+
]);
|
|
428
429
|
}
|
|
429
430
|
//--- END --- Implement interface `IBounded`
|
|
430
431
|
|
|
@@ -526,8 +527,20 @@ export class CubicBezierCurve implements IBounded, Intersectable, PathSegment {
|
|
|
526
527
|
// this.startControlPoint.y * 3 * t * Math.pow(1.0 - t, 2) +
|
|
527
528
|
// this.endControlPoint.y * 3 * Math.pow(t, 2) * (1.0 - t) +
|
|
528
529
|
// this.endPoint.y * Math.pow(t, 3);
|
|
529
|
-
const x: number = CubicBezierCurve.utils.evaluateT(
|
|
530
|
-
|
|
530
|
+
const x: number = CubicBezierCurve.utils.evaluateT(
|
|
531
|
+
this.startPoint.x,
|
|
532
|
+
this.startControlPoint.x,
|
|
533
|
+
this.endControlPoint.x,
|
|
534
|
+
this.endPoint.x,
|
|
535
|
+
t
|
|
536
|
+
);
|
|
537
|
+
const y: number = CubicBezierCurve.utils.evaluateT(
|
|
538
|
+
this.startPoint.y,
|
|
539
|
+
this.startControlPoint.y,
|
|
540
|
+
this.endControlPoint.y,
|
|
541
|
+
this.endPoint.y,
|
|
542
|
+
t
|
|
543
|
+
);
|
|
531
544
|
return new Vertex(x, y);
|
|
532
545
|
}
|
|
533
546
|
|
|
@@ -611,6 +624,7 @@ export class CubicBezierCurve implements IBounded, Intersectable, PathSegment {
|
|
|
611
624
|
this.endControlPoint.set(subCurbePoints[3]);
|
|
612
625
|
this.updateArcLengths();
|
|
613
626
|
return this;
|
|
627
|
+
// return this.trimStartEndAt(t, null);
|
|
614
628
|
}
|
|
615
629
|
|
|
616
630
|
/**
|
|
@@ -648,8 +662,88 @@ export class CubicBezierCurve implements IBounded, Intersectable, PathSegment {
|
|
|
648
662
|
this.endControlPoint.set(subCurbePoints[3]);
|
|
649
663
|
this.updateArcLengths();
|
|
650
664
|
return this;
|
|
665
|
+
// return this.trimStartEndAt(null, t);
|
|
651
666
|
}
|
|
652
667
|
|
|
668
|
+
/**
|
|
669
|
+
* Trim off a start and end section of this curve. The position parameters `uStart` and `uEnd` are the absolute positions in [0..arcLength].
|
|
670
|
+
* The remaining curve will be the one in the bounds `[uStart,uEnd]` (so `[0.0,uStart]` and `[uEnd,1.0]` are cut off).
|
|
671
|
+
*
|
|
672
|
+
* Parameters out of bounds (< 0.0 or > arcLength) are ignored.
|
|
673
|
+
* If `uEnd` is smaller than `uStart` then a curve with length zero (0) at `uStart` is returned.
|
|
674
|
+
*
|
|
675
|
+
* @method trimStartEndAt
|
|
676
|
+
* @instance
|
|
677
|
+
* @memberof CubicBezierCurve
|
|
678
|
+
* @param {number} tStart - The relative position parameter where to cut off the head curve.
|
|
679
|
+
* @param {number} tEnd - The relative position parameter where to cut off the tail curve.
|
|
680
|
+
* @returns {CubicBezierCurve} `this` for chanining.
|
|
681
|
+
*/
|
|
682
|
+
|
|
683
|
+
trimStartEnd(uStart: number, uEnd: number): CubicBezierCurve {
|
|
684
|
+
return this.trimStartEndAt(this.convertU2T(uStart), this.convertU2T(uEnd));
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Trim off a start and end section of this curve. The position parameters `tStart` and `tEnd` are the relative positions in [0..1].
|
|
689
|
+
* The remaining curve will be the one in the bounds `[tStart,tEnd]` (so `[0.0,tStart]` and `[tEnd,1.0]` are cut off).
|
|
690
|
+
*
|
|
691
|
+
* Parameters out of bounds (< 0.0 or > 1.0) are ignored.
|
|
692
|
+
* If `tEnd` is smaller than `tStart` then a curve with length zero (0) at `tStart` is returned.
|
|
693
|
+
*
|
|
694
|
+
* @method trimStartEndAt
|
|
695
|
+
* @instance
|
|
696
|
+
* @memberof CubicBezierCurve
|
|
697
|
+
* @param {number} tStart - The relative position parameter where to cut off the head curve.
|
|
698
|
+
* @param {number} tEnd - The relative position parameter where to cut off the tail curve.
|
|
699
|
+
* @returns {CubicBezierCurve} `this` for chanining.
|
|
700
|
+
*/
|
|
701
|
+
|
|
702
|
+
trimStartEndAt(tStart: number, tEnd: number): CubicBezierCurve {
|
|
703
|
+
const cleanTrimStart = Math.min(Math.max(0.0, tStart), 1.0);
|
|
704
|
+
const cleanTrimEnd = Math.min(Math.max(cleanTrimStart, tEnd), 1.0);
|
|
705
|
+
this.trimStartAt(cleanTrimStart);
|
|
706
|
+
const relativeTrimEnd = (cleanTrimEnd - cleanTrimStart) / (1.0 - cleanTrimStart);
|
|
707
|
+
this.trimEndAt(relativeTrimEnd);
|
|
708
|
+
return this;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// __trimStartEndAt(tStart: number, tEnd: number): CubicBezierCurve {
|
|
712
|
+
// var finalCurvePoints = [
|
|
713
|
+
// this.startPoint.clone(),
|
|
714
|
+
// this.endPoint.clone(),
|
|
715
|
+
// this.startControlPoint.clone(),
|
|
716
|
+
// this.endControlPoint.clone()
|
|
717
|
+
// ];
|
|
718
|
+
// if (typeof tStart === "number" && !Number.isNaN(tStart)) {
|
|
719
|
+
// const subCurvePointsStart = CubicBezierCurve.utils.getSubCurvePointsAt(this, tStart, 1.0);
|
|
720
|
+
// finalCurvePoints[0].set(subCurvePointsStart[0]);
|
|
721
|
+
// finalCurvePoints[2].set(subCurvePointsStart[2]);
|
|
722
|
+
// // this.startPoint.set(subCurvePointsStart[0]);
|
|
723
|
+
// // this.startControlPoint.set(subCurvePointsStart[2]);
|
|
724
|
+
// // this.endPoint.set(subCurvePointsStart[1]);
|
|
725
|
+
// // this.endControlPoint.set(subCurvePointsStart[3]);
|
|
726
|
+
// }
|
|
727
|
+
|
|
728
|
+
// if (typeof tEnd === "number" && !Number.isNaN(tEnd)) {
|
|
729
|
+
// const subCurvePointsEnd = CubicBezierCurve.utils.getSubCurvePointsAt(this, 0.0, tEnd);
|
|
730
|
+
// // this.startPoint.set(subCurvePointsEnd[0]);
|
|
731
|
+
// // this.startControlPoint.set(subCurvePointsEnd[2]);
|
|
732
|
+
// // this.endPoint.set(subCurvePointsEnd[1]);
|
|
733
|
+
// // this.endControlPoint.set(subCurvePointsEnd[3]);
|
|
734
|
+
// finalCurvePoints[1].set(subCurvePointsEnd[1]);
|
|
735
|
+
// finalCurvePoints[3].set(subCurvePointsEnd[3]);
|
|
736
|
+
// }
|
|
737
|
+
|
|
738
|
+
// this.startPoint.set(finalCurvePoints[0]);
|
|
739
|
+
// this.endPoint.set(finalCurvePoints[1]);
|
|
740
|
+
// this.startControlPoint.set(finalCurvePoints[2]);
|
|
741
|
+
// this.endControlPoint.set(finalCurvePoints[3]);
|
|
742
|
+
|
|
743
|
+
// this.updateArcLengths();
|
|
744
|
+
// return this;
|
|
745
|
+
// }
|
|
746
|
+
|
|
653
747
|
/**
|
|
654
748
|
* Get a sub curve at the given start end end positions (values on the curve's length, between 0 and curve.arcLength).
|
|
655
749
|
*
|
|
@@ -1027,15 +1121,13 @@ export class CubicBezierCurve implements IBounded, Intersectable, PathSegment {
|
|
|
1027
1121
|
* Helper utils.
|
|
1028
1122
|
*/
|
|
1029
1123
|
public static utils = {
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
p2 * 3 * Math.pow(t, 2) * (1.0 - t) +
|
|
1035
|
-
p3 * Math.pow(t, 3);
|
|
1124
|
+
evaluateT: (p0: number, p1: number, p2: number, p3: number, t: number): number => {
|
|
1125
|
+
return (
|
|
1126
|
+
p0 * Math.pow(1.0 - t, 3) + p1 * 3 * t * Math.pow(1.0 - t, 2) + p2 * 3 * Math.pow(t, 2) * (1.0 - t) + p3 * Math.pow(t, 3)
|
|
1127
|
+
);
|
|
1036
1128
|
},
|
|
1037
1129
|
|
|
1038
|
-
cubicPolyMinMax: (p0:number, p1:number, p2:number, p3:number)
|
|
1130
|
+
cubicPolyMinMax: (p0: number, p1: number, p2: number, p3: number): { min: number; max: number } => {
|
|
1039
1131
|
// var polyX = CubicBezierCurve.utils.cubicPoly2(
|
|
1040
1132
|
// p0, // P[0].X, // bezierCurve.startPoint.x,
|
|
1041
1133
|
// p1, // P[1].X, // bezierCurve.startControlPoint.x,
|
|
@@ -1048,7 +1140,6 @@ export class CubicBezierCurve implements IBounded, Intersectable, PathSegment {
|
|
|
1048
1140
|
// var c = polyX.c;
|
|
1049
1141
|
// var disc = polyX.b * polyX.b - 4 * polyX.a * polyX.c;
|
|
1050
1142
|
|
|
1051
|
-
|
|
1052
1143
|
var polyX = CubicBezierCurve.utils.cubicPoly(
|
|
1053
1144
|
p0, // P[0].X, // bezierCurve.startPoint.x,
|
|
1054
1145
|
p1, // P[1].X, // bezierCurve.startControlPoint.x,
|
|
@@ -1062,7 +1153,6 @@ export class CubicBezierCurve implements IBounded, Intersectable, PathSegment {
|
|
|
1062
1153
|
// var disc = b * b - 4 * a * c;
|
|
1063
1154
|
var disc = polyX[1] * polyX[1] - 4 * polyX[0] * polyX[2];
|
|
1064
1155
|
|
|
1065
|
-
|
|
1066
1156
|
// var polyX = CubicBezierCurve.utils.bezierCoeffs(p3,p2,p1,p0);
|
|
1067
1157
|
// var a = polyX[0]; //polyX.a;
|
|
1068
1158
|
// var b = polyX[1]; // .b;
|
|
@@ -1111,7 +1201,7 @@ export class CubicBezierCurve implements IBounded, Intersectable, PathSegment {
|
|
|
1111
1201
|
* @param {number} tEnd – The end offset if the desired cub curve (must be in [0..1]).
|
|
1112
1202
|
* @instance
|
|
1113
1203
|
* @memberof CubicBezierCurve
|
|
1114
|
-
* @return {
|
|
1204
|
+
* @return {[Vertex, Vertex, Vertex, Vertex]} The sub curve as curve vertices.
|
|
1115
1205
|
**/
|
|
1116
1206
|
getSubCurvePointsAt: (curve: CubicBezierCurve, tStart: number, tEnd: number): [Vertex, Vertex, Vertex, Vertex] => {
|
|
1117
1207
|
const startVec: Vector = new Vector(curve.getPointAt(tStart), curve.getTangentAt(tStart));
|
|
@@ -1196,8 +1286,8 @@ export class CubicBezierCurve implements IBounded, Intersectable, PathSegment {
|
|
|
1196
1286
|
* @param {number} p3 - The end point coordinate.
|
|
1197
1287
|
* @returns {[number,number,number,number]}
|
|
1198
1288
|
*/
|
|
1199
|
-
bezierCoeffs: (p0: number, p1: number, p2: number, p3: number): [number,number,number,number] => {
|
|
1200
|
-
const coeffs
|
|
1289
|
+
bezierCoeffs: (p0: number, p1: number, p2: number, p3: number): [number, number, number, number] => {
|
|
1290
|
+
const coeffs: [number, number, number, number] = [NaN, NaN, NaN, NaN]; //Array(4);
|
|
1201
1291
|
coeffs[0] = -p0 + 3 * p1 + -3 * p2 + p3;
|
|
1202
1292
|
coeffs[1] = 3 * p0 - 6 * p1 + 3 * p2;
|
|
1203
1293
|
coeffs[2] = -3 * p0 + 3 * p1;
|
|
@@ -1207,22 +1297,17 @@ export class CubicBezierCurve implements IBounded, Intersectable, PathSegment {
|
|
|
1207
1297
|
|
|
1208
1298
|
/**
|
|
1209
1299
|
* Calculate the cubic polynomial coefficients used to find the bounding box.
|
|
1210
|
-
*
|
|
1300
|
+
*
|
|
1211
1301
|
* @param {number} p0 - The start point coordinate.
|
|
1212
1302
|
* @param {number} p1 - The start control point coordinate.
|
|
1213
1303
|
* @param {number} p2 - The end control point coordinate.
|
|
1214
1304
|
* @param {number} p3 - The end point coordinate.
|
|
1215
1305
|
* @returns {[number,number,number]}
|
|
1216
1306
|
*/
|
|
1217
|
-
cubicPoly: (p0:number, p1:number, p2:number, p3:number)
|
|
1218
|
-
return [
|
|
1219
|
-
3 * p3 - 9 * p2 + 9 * p1 - 3 * p0,
|
|
1220
|
-
6 * p0 - 12 * p1 + 6 * p2,
|
|
1221
|
-
3 * p1 - 3 * p0
|
|
1222
|
-
]
|
|
1307
|
+
cubicPoly: (p0: number, p1: number, p2: number, p3: number): [number, number, number] => {
|
|
1308
|
+
return [3 * p3 - 9 * p2 + 9 * p1 - 3 * p0, 6 * p0 - 12 * p1 + 6 * p2, 3 * p1 - 3 * p0];
|
|
1223
1309
|
},
|
|
1224
1310
|
|
|
1225
|
-
|
|
1226
1311
|
/**
|
|
1227
1312
|
* sign of number, but is division safe: no zero returned :)
|
|
1228
1313
|
*/
|
package/src/ts/Line.ts
CHANGED
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
* @modified 2023-09-25 Changed param type of `intersection()` from Line to VertTuple.
|
|
19
19
|
* @modified 2025-04-15 Class `Line` now implements interface `Intersectable`.
|
|
20
20
|
* @modified 2025-04-16 Class `Line` now implements interface `IBounded`.
|
|
21
|
-
* @
|
|
21
|
+
* @modified 2026-08-16 Adding methods `Line.trimStart` and `Line.trimEnd`. Adding methods `Line.trimStartAt` and `Line.trimEndAt`.
|
|
22
|
+
* @version 2.5.0
|
|
22
23
|
*
|
|
23
24
|
* @file Line
|
|
24
25
|
* @public
|
|
@@ -165,6 +166,88 @@ export class Line extends VertTuple<Line> implements IBounded, Intersectable, Pa
|
|
|
165
166
|
}
|
|
166
167
|
//--- END Implement PathSegment ---
|
|
167
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Trim this line segment from the start point by the given amount.
|
|
171
|
+
* The amount must be positive and should be withing the segment's length. If the amount exceeds the segment's length
|
|
172
|
+
* then the length of the resulting line will be zero (0.0).
|
|
173
|
+
*
|
|
174
|
+
* @method trimStart
|
|
175
|
+
* @memberof Line
|
|
176
|
+
* @param {number} amount - The positive amount to trim the line from the start point `a`.
|
|
177
|
+
* @returns {Line} This for chaining, with updated point `a`.
|
|
178
|
+
*/
|
|
179
|
+
trimStart(amount: number) {
|
|
180
|
+
// Calculate the relative position `t` on this line.
|
|
181
|
+
var t: number = amount / this.length();
|
|
182
|
+
// `t` should be inside 0..1 – otherwise the amount was too large or negative.
|
|
183
|
+
if (t < 0.0) {
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
186
|
+
if (t > 1.0) {
|
|
187
|
+
// Set the line to length zero (endpoint only)
|
|
188
|
+
this.a = this.b.clone();
|
|
189
|
+
return this;
|
|
190
|
+
}
|
|
191
|
+
this.a = this.vertAt(t);
|
|
192
|
+
return this;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Trim this line segment from the start point by the given relative amount.
|
|
197
|
+
* The amount must be positive and should be within 0.0 and 1.0. If the amount exceeds the segment's length
|
|
198
|
+
* then the length of the resulting line will be zero (0.0).
|
|
199
|
+
*
|
|
200
|
+
* @method trimStartAt
|
|
201
|
+
* @memberof Line
|
|
202
|
+
* @param {number} amount - The positive amount to trim the line from the start point `a`.
|
|
203
|
+
* @returns {Line} This for chaining, with updated point `a`.
|
|
204
|
+
*/
|
|
205
|
+
trimStartAt(relativeAmount: number) {
|
|
206
|
+
// Calculate the relative position `t` on this line.
|
|
207
|
+
return this.trimStart(relativeAmount * this.length());
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Trim this line segment from the end point by the given amount.
|
|
212
|
+
* The amount must be positive and should be withing the segment's length. If the amount exceeds the segment's length
|
|
213
|
+
* then the length of the resulting line will be zero (0.0).
|
|
214
|
+
*
|
|
215
|
+
* @method trimEnd
|
|
216
|
+
* @memberof Line
|
|
217
|
+
* @param {number} amount - The positive amount to trim the line from the end point `b`.
|
|
218
|
+
* @returns {Line} This for chaining, with updated point `b`.
|
|
219
|
+
*/
|
|
220
|
+
trimEnd(amount: number) {
|
|
221
|
+
// Calculate the relative position `t` on this line.
|
|
222
|
+
var t: number = 1.0 - amount / this.length();
|
|
223
|
+
// `t` should be inside 0..1 – otherwise the amount was too large or negative.
|
|
224
|
+
if (t < 0.0) {
|
|
225
|
+
return this;
|
|
226
|
+
}
|
|
227
|
+
if (t > 1.0) {
|
|
228
|
+
// Set the line to length zero (endpoint only)
|
|
229
|
+
this.b = this.a.clone();
|
|
230
|
+
return this;
|
|
231
|
+
}
|
|
232
|
+
this.b = this.vertAt(t);
|
|
233
|
+
return this;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Trim this line segment from the end point by the given relative amount.
|
|
238
|
+
* The amount must be positive and should be within 0.0 and 1.0. If the amount exceeds the segment's length
|
|
239
|
+
* then the length of the resulting line will be zero (0.0).
|
|
240
|
+
*
|
|
241
|
+
* @method trimEndAt
|
|
242
|
+
* @memberof Line
|
|
243
|
+
* @param {number} amount - The positive amount to trim the line from the start point `a`.
|
|
244
|
+
* @returns {Line} This for chaining, with updated point `a`.
|
|
245
|
+
*/
|
|
246
|
+
trimEndAt(relativeAmount: number) {
|
|
247
|
+
// Calculate the relative position `t` on this line.
|
|
248
|
+
return this.trimEnd(relativeAmount * this.length());
|
|
249
|
+
}
|
|
250
|
+
|
|
168
251
|
//--- BEGIN --- Implement interface `Intersectable`
|
|
169
252
|
/**
|
|
170
253
|
* Get all line intersections with this polygon.
|