plotboilerplate 1.23.3 → 1.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +6 -0
  2. package/dist/index.esm.js +1390 -265
  3. package/dist/index.esm.js.map +1 -1
  4. package/dist/index.esm.min.js +1 -1
  5. package/dist/index.esm.min.js.map +1 -1
  6. package/dist/plotboilerplate.browser.min.js +1 -1
  7. package/dist/plotboilerplate.browser.min.js.map +1 -1
  8. package/dist/plotboilerplate.js +1264 -99
  9. package/dist/plotboilerplate.min.js +1 -1
  10. package/package.json +1 -1
  11. package/src/cjs/BezierPath.d.ts +41 -5
  12. package/src/cjs/BezierPath.js +52 -2
  13. package/src/cjs/BezierPath.js.map +1 -1
  14. package/src/cjs/Bounds.d.ts +67 -3
  15. package/src/cjs/Bounds.js +101 -3
  16. package/src/cjs/Bounds.js.map +1 -1
  17. package/src/cjs/Circle.d.ts +46 -4
  18. package/src/cjs/Circle.js +82 -3
  19. package/src/cjs/Circle.js.map +1 -1
  20. package/src/cjs/CircleSector.d.ts +51 -2
  21. package/src/cjs/CircleSector.js +112 -3
  22. package/src/cjs/CircleSector.js.map +1 -1
  23. package/src/cjs/CubicBezierCurve.d.ts +113 -7
  24. package/src/cjs/CubicBezierCurve.js +281 -33
  25. package/src/cjs/CubicBezierCurve.js.map +1 -1
  26. package/src/cjs/Line.d.ts +38 -3
  27. package/src/cjs/Line.js +64 -1
  28. package/src/cjs/Line.js.map +1 -1
  29. package/src/cjs/PlotBoilerplate.js +2 -9
  30. package/src/cjs/PlotBoilerplate.js.map +1 -1
  31. package/src/cjs/Polygon.d.ts +48 -4
  32. package/src/cjs/Polygon.js +91 -16
  33. package/src/cjs/Polygon.js.map +1 -1
  34. package/src/cjs/Triangle.d.ts +52 -3
  35. package/src/cjs/Triangle.js +80 -1
  36. package/src/cjs/Triangle.js.map +1 -1
  37. package/src/cjs/VEllipse.d.ts +62 -3
  38. package/src/cjs/VEllipse.js +181 -17
  39. package/src/cjs/VEllipse.js.map +1 -1
  40. package/src/cjs/VEllipseSector.d.ts +86 -2
  41. package/src/cjs/VEllipseSector.js +148 -4
  42. package/src/cjs/VEllipseSector.js.map +1 -1
  43. package/src/cjs/Vector.d.ts +12 -2
  44. package/src/cjs/Vector.js +15 -2
  45. package/src/cjs/Vector.js.map +1 -1
  46. package/src/cjs/VertTuple.d.ts +28 -4
  47. package/src/cjs/VertTuple.js +36 -3
  48. package/src/cjs/VertTuple.js.map +1 -1
  49. package/src/cjs/Vertex.d.ts +17 -2
  50. package/src/cjs/Vertex.js +18 -1
  51. package/src/cjs/Vertex.js.map +1 -1
  52. package/src/cjs/interfaces/core.d.ts +39 -0
  53. package/src/cjs/interfaces/core.js +2 -0
  54. package/src/cjs/interfaces/core.js.map +1 -1
  55. package/src/cjs/utils/algorithms/convexPolygonIncircle.d.ts +2 -1
  56. package/src/cjs/utils/algorithms/convexPolygonIncircle.js +14 -6
  57. package/src/cjs/utils/algorithms/convexPolygonIncircle.js.map +1 -1
  58. package/src/cjs/utils/createRandomizedPolygon.d.ts +14 -0
  59. package/src/cjs/utils/createRandomizedPolygon.js +32 -0
  60. package/src/cjs/utils/createRandomizedPolygon.js.map +1 -0
  61. package/src/cjs/utils/helpers/BezierPathInteractionHelper.d.ts +9 -7
  62. package/src/cjs/utils/helpers/BezierPathInteractionHelper.js +37 -23
  63. package/src/cjs/utils/helpers/BezierPathInteractionHelper.js.map +1 -1
  64. package/src/cjs/utils/helpers/CircleSectorHelper.d.ts +10 -1
  65. package/src/cjs/utils/helpers/CircleSectorHelper.js +19 -20
  66. package/src/cjs/utils/helpers/CircleSectorHelper.js.map +1 -1
  67. package/src/cjs/utils/helpers/VEllipseHelper.d.ts +30 -0
  68. package/src/cjs/utils/helpers/VEllipseHelper.js +86 -0
  69. package/src/cjs/utils/helpers/VEllipseHelper.js.map +1 -0
  70. package/src/cjs/utils/helpers/VEllipseSectorHelper.d.ts +42 -3
  71. package/src/cjs/utils/helpers/VEllipseSectorHelper.js +107 -25
  72. package/src/cjs/utils/helpers/VEllipseSectorHelper.js.map +1 -1
  73. package/src/createDemoDrawables.js +3 -1
  74. package/src/esm/BezierPath.d.ts +41 -5
  75. package/src/esm/BezierPath.js +50 -2
  76. package/src/esm/BezierPath.js.map +1 -1
  77. package/src/esm/Bounds.d.ts +67 -3
  78. package/src/esm/Bounds.js +101 -3
  79. package/src/esm/Bounds.js.map +1 -1
  80. package/src/esm/Circle.d.ts +46 -4
  81. package/src/esm/Circle.js +79 -3
  82. package/src/esm/Circle.js.map +1 -1
  83. package/src/esm/CircleSector.d.ts +51 -2
  84. package/src/esm/CircleSector.js +108 -3
  85. package/src/esm/CircleSector.js.map +1 -1
  86. package/src/esm/CubicBezierCurve.d.ts +113 -7
  87. package/src/esm/CubicBezierCurve.js +277 -33
  88. package/src/esm/CubicBezierCurve.js.map +1 -1
  89. package/src/esm/Line.d.ts +38 -3
  90. package/src/esm/Line.js +62 -1
  91. package/src/esm/Line.js.map +1 -1
  92. package/src/esm/PlotBoilerplate.js +2 -9
  93. package/src/esm/PlotBoilerplate.js.map +1 -1
  94. package/src/esm/Polygon.d.ts +48 -4
  95. package/src/esm/Polygon.js +89 -16
  96. package/src/esm/Polygon.js.map +1 -1
  97. package/src/esm/Triangle.d.ts +52 -3
  98. package/src/esm/Triangle.js +77 -1
  99. package/src/esm/Triangle.js.map +1 -1
  100. package/src/esm/VEllipse.d.ts +62 -3
  101. package/src/esm/VEllipse.js +178 -17
  102. package/src/esm/VEllipse.js.map +1 -1
  103. package/src/esm/VEllipseSector.d.ts +86 -2
  104. package/src/esm/VEllipseSector.js +146 -7
  105. package/src/esm/VEllipseSector.js.map +1 -1
  106. package/src/esm/Vector.d.ts +12 -2
  107. package/src/esm/Vector.js +15 -2
  108. package/src/esm/Vector.js.map +1 -1
  109. package/src/esm/VertTuple.d.ts +28 -4
  110. package/src/esm/VertTuple.js +36 -3
  111. package/src/esm/VertTuple.js.map +1 -1
  112. package/src/esm/Vertex.d.ts +17 -2
  113. package/src/esm/Vertex.js +18 -1
  114. package/src/esm/Vertex.js.map +1 -1
  115. package/src/esm/interfaces/core.d.ts +39 -0
  116. package/src/esm/interfaces/core.js +2 -0
  117. package/src/esm/interfaces/core.js.map +1 -1
  118. package/src/esm/utils/algorithms/convexPolygonIncircle.d.ts +2 -1
  119. package/src/esm/utils/algorithms/convexPolygonIncircle.js +15 -7
  120. package/src/esm/utils/algorithms/convexPolygonIncircle.js.map +1 -1
  121. package/src/esm/utils/algorithms/createRandomizedPolygon.d.ts +14 -0
  122. package/src/esm/utils/algorithms/createRandomizedPolygon.js +28 -0
  123. package/src/esm/utils/algorithms/createRandomizedPolygon.js.map +1 -0
  124. package/src/esm/utils/createRandomizedPolygon.d.ts +14 -0
  125. package/src/esm/utils/createRandomizedPolygon.js +28 -0
  126. package/src/esm/utils/createRandomizedPolygon.js.map +1 -0
  127. package/src/esm/utils/helpers/BezierPathInteractionHelper.d.ts +9 -7
  128. package/src/esm/utils/helpers/BezierPathInteractionHelper.js +35 -22
  129. package/src/esm/utils/helpers/BezierPathInteractionHelper.js.map +1 -1
  130. package/src/esm/utils/helpers/CircleSectorHelper.d.ts +10 -1
  131. package/src/esm/utils/helpers/CircleSectorHelper.js +19 -20
  132. package/src/esm/utils/helpers/CircleSectorHelper.js.map +1 -1
  133. package/src/esm/utils/helpers/VEllipseHelper.d.ts +30 -0
  134. package/src/esm/utils/helpers/VEllipseHelper.js +81 -0
  135. package/src/esm/utils/helpers/VEllipseHelper.js.map +1 -0
  136. package/src/esm/utils/helpers/VEllipseSectorHelper.d.ts +42 -3
  137. package/src/esm/utils/helpers/VEllipseSectorHelper.js +106 -24
  138. package/src/esm/utils/helpers/VEllipseSectorHelper.js.map +1 -1
  139. package/src/ts/BezierPath.ts +58 -5
  140. package/src/ts/Bounds.ts +110 -6
  141. package/src/ts/Circle.ts +87 -5
  142. package/src/ts/CircleSector.ts +115 -5
  143. package/src/ts/CubicBezierCurve.ts +332 -42
  144. package/src/ts/Line.ts +66 -3
  145. package/src/ts/PlotBoilerplate.ts +3 -9
  146. package/src/ts/Polygon.ts +103 -22
  147. package/src/ts/Triangle.ts +85 -3
  148. package/src/ts/VEllipse.ts +214 -19
  149. package/src/ts/VEllipseSector.ts +154 -8
  150. package/src/ts/Vector.ts +16 -2
  151. package/src/ts/VertTuple.ts +39 -4
  152. package/src/ts/Vertex.ts +20 -2
  153. package/src/ts/interfaces/core.ts +42 -0
  154. package/src/ts/utils/algorithms/convexPolygonIncircle.ts +82 -78
  155. package/src/ts/utils/createRandomizedPolygon.ts +31 -0
  156. package/src/ts/utils/helpers/BezierPathInteractionHelper.ts +45 -29
  157. package/src/ts/utils/helpers/CircleSectorHelper.ts +115 -125
  158. package/src/ts/utils/helpers/VEllipseHelper.ts +113 -0
  159. package/src/ts/utils/helpers/VEllipseSectorHelper.ts +142 -25
package/dist/index.esm.js CHANGED
@@ -328,168 +328,6 @@ class VertexListeners {
328
328
  }
329
329
  }
330
330
 
331
- /**
332
- * @author Ikaros Kappler
333
- * @date 2019-01-30
334
- * @modified 2019-02-23 Added the toSVGString function, overriding Line.toSVGString.
335
- * @modified 2019-03-20 Added JSDoc tags.
336
- * @modified 2019-04-19 Added the clone function (overriding Line.clone()).
337
- * @modified 2019-09-02 Added the Vector.perp() function.
338
- * @modified 2019-09-02 Added the Vector.inverse() function.
339
- * @modified 2019-12-04 Added the Vector.inv() function.
340
- * @modified 2020-03-23 Ported to Typescript from JS.
341
- * @modified 2021-01-20 Added UID.
342
- * @modified 2022-02-02 Added the `destroy` method.
343
- * @modified 2022-02-02 Cleared the `Vector.toSVGString` function (deprecated). Use `drawutilssvg` instead.
344
- * @modified 2022-10-25 Added the `getOrthogonal` method.
345
- * @version 1.5.0
346
- *
347
- * @file Vector
348
- * @public
349
- **/
350
- /**
351
- * @classdesc A vector (Vertex,Vertex) is a line with a visible direction.<br>
352
- * <br>
353
- * Vectors are drawn with an arrow at their end point.<br>
354
- * <b>The Vector class extends the Line class.</b>
355
- *
356
- * @requires VertTuple
357
- * @requires Vertex
358
- **/
359
- class Vector extends VertTuple {
360
- /**
361
- * The constructor.
362
- *
363
- * @constructor
364
- * @name Vector
365
- * @extends Line
366
- * @param {Vertex} vertA - The start vertex of the vector.
367
- * @param {Vertex} vertB - The end vertex of the vector.
368
- **/
369
- constructor(vertA, vertB) {
370
- super(vertA, vertB, (a, b) => new Vector(a, b));
371
- /**
372
- * Required to generate proper CSS classes and other class related IDs.
373
- **/
374
- this.className = "Vector";
375
- }
376
- /**
377
- * Get the perpendicular of this vector which is located at a.
378
- *
379
- * @param {Number} t The position on the vector.
380
- * @return {Vector} A new vector being the perpendicular of this vector sitting on a.
381
- **/
382
- perp() {
383
- var v = this.clone();
384
- v.sub(this.a);
385
- v = new Vector(new Vertex(), new Vertex(-v.b.y, v.b.x));
386
- v.a.add(this.a);
387
- v.b.add(this.a);
388
- return v;
389
- }
390
- /**
391
- * The inverse of a vector is a vector with the same magnitude but oppose direction.
392
- *
393
- * Please not that the origin of this vector changes here: a->b becomes b->a.
394
- *
395
- * @return {Vector}
396
- **/
397
- inverse() {
398
- var tmp = this.a;
399
- this.a = this.b;
400
- this.b = tmp;
401
- return this;
402
- }
403
- /**
404
- * This function computes the inverse of the vector, which means 'a' stays untouched.
405
- *
406
- * @return {Vector} this for chaining.
407
- **/
408
- inv() {
409
- this.b.x = this.a.x - (this.b.x - this.a.x);
410
- this.b.y = this.a.y - (this.b.y - this.a.y);
411
- return this;
412
- }
413
- /**
414
- * Get the intersection if this vector and the specified vector.
415
- *
416
- * @method intersection
417
- * @param {Vector} line The second vector.
418
- * @return {Vertex} The intersection (may lie outside the end-points).
419
- * @instance
420
- * @memberof Line
421
- **/
422
- intersection(line) {
423
- var denominator = this.denominator(line);
424
- if (denominator == 0)
425
- return null;
426
- var a = this.a.y - line.a.y;
427
- var b = this.a.x - line.a.x;
428
- var numerator1 = (line.b.x - line.a.x) * a - (line.b.y - line.a.y) * b;
429
- var numerator2 = (this.b.x - this.a.x) * a - (this.b.y - this.a.y) * b;
430
- a = numerator1 / denominator; // NaN if parallel lines
431
- b = numerator2 / denominator;
432
- // TODO:
433
- // FOR A VECTOR THE LINE-INTERSECTION MUST BE ON BOTH VECTORS
434
- // if we cast these lines infinitely in both directions, they intersect here:
435
- return new Vertex(this.a.x + a * (this.b.x - this.a.x), this.a.y + a * (this.b.y - this.a.y));
436
- }
437
- /**
438
- * Get the orthogonal "vector" of this vector (rotated by 90° clockwise).
439
- *
440
- * @name getOrthogonal
441
- * @method getOrthogonal
442
- * @return {Vector} A new vector with the same length that stands on this vector's point a.
443
- * @instance
444
- * @memberof Vector
445
- **/
446
- getOrthogonal() {
447
- // Orthogonal of vector (0,0)->(x,y) is (0,0)->(-y,x)
448
- const linePoint = this.a.clone();
449
- const startPoint = this.b.clone().sub(this.a);
450
- const tmp = startPoint.x;
451
- startPoint.x = -startPoint.y;
452
- startPoint.y = tmp;
453
- return new Vector(linePoint, startPoint.add(this.a));
454
- }
455
- }
456
- Vector.utils = {
457
- /**
458
- * Generate a four-point arrow head, starting at the vector end minus the
459
- * arrow head length.
460
- *
461
- * The first vertex in the returned array is guaranteed to be the located
462
- * at the vector line end minus the arrow head length.
463
- *
464
- *
465
- * Due to performance all params are required.
466
- *
467
- * The params scaleX and scaleY are required for the case that the scaling is not uniform (x and y
468
- * scaling different). Arrow heads should not look distored on non-uniform scaling.
469
- *
470
- * If unsure use 1.0 for scaleX and scaleY (=no distortion).
471
- * For headlen use 8, it's a good arrow head size.
472
- *
473
- * Example:
474
- * buildArrowHead( new Vertex(0,0), new Vertex(50,100), 8, 1.0, 1.0 )
475
- *
476
- * @param {XYCoords} zA - The start vertex of the vector to calculate the arrow head for.
477
- * @param {XYCoords} zB - The end vertex of the vector.
478
- * @param {number} headlen - The length of the arrow head (along the vector direction. A good value is 12).
479
- * @param {number} scaleX - The horizontal scaling during draw.
480
- * @param {number} scaleY - the vertical scaling during draw.
481
- **/
482
- buildArrowHead: (zA, zB, headlen, scaleX, scaleY) => {
483
- const angle = Math.atan2((zB.y - zA.y) * scaleY, (zB.x - zA.x) * scaleX);
484
- const vertices = [];
485
- vertices.push(new Vertex(zB.x * scaleX - headlen * Math.cos(angle), zB.y * scaleY - headlen * Math.sin(angle)));
486
- vertices.push(new Vertex(zB.x * scaleX - headlen * 1.35 * Math.cos(angle - Math.PI / 8), zB.y * scaleY - headlen * 1.35 * Math.sin(angle - Math.PI / 8)));
487
- vertices.push(new Vertex(zB.x * scaleX, zB.y * scaleY));
488
- vertices.push(new Vertex(zB.x * scaleX - headlen * 1.35 * Math.cos(angle + Math.PI / 8), zB.y * scaleY - headlen * 1.35 * Math.sin(angle + Math.PI / 8)));
489
- return vertices;
490
- }
491
- };
492
-
493
331
  /**
494
332
  * @author Ikaros Kappler
495
333
  * @date 2020-05-04
@@ -504,7 +342,9 @@ Vector.utils = {
504
342
  * @modified 2022-08-15 Added the `containsPoint` function.
505
343
  * @modified 2022-08-23 Added the `lineIntersection` function.
506
344
  * @modified 2022-08-23 Added the `closestPoint` function.
507
- * @version 1.4.0
345
+ * @modified 2025-04-09 Added the `Circle.move(amount: XYCoords)` method.
346
+ * @modified 2025-04-16 Class `Circle` now implements interface `Intersectable`.
347
+ * @version 1.5.0
508
348
  **/
509
349
  /**
510
350
  * @classdesc A simple circle: center point and radius.
@@ -535,6 +375,19 @@ class Circle {
535
375
  this.center = center;
536
376
  this.radius = radius;
537
377
  }
378
+ /**
379
+ * Move the circle by the given amount.
380
+ *
381
+ * @method move
382
+ * @param {XYCoords} amount - The amount to move.
383
+ * @instance
384
+ * @memberof Circle
385
+ * @return {Circle} this for chaining
386
+ **/
387
+ move(amount) {
388
+ this.center.add(amount);
389
+ return this;
390
+ }
538
391
  /**
539
392
  * Check if the given circle is fully contained inside this circle.
540
393
  *
@@ -605,7 +458,21 @@ class Circle {
605
458
  const pointA = Circle.circleUtils.vertAt(angle, this.radius);
606
459
  // Construct the perpendicular of the line in point a. Then move relative to center.
607
460
  return new Vector(pointA, new Vertex(0, 0)).add(this.center).perp();
461
+ // return (new Vector(this.center.clone(), pointA).add(pointA) as Vector).perp() as Vector;
462
+ }
463
+ //--- BEGIN --- Implement interface `Intersectable`
464
+ /**
465
+ * Get the bounding box (bounds) of this Circle.
466
+ *
467
+ * @method getBounds
468
+ * @instance
469
+ * @memberof Circle
470
+ * @return {Bounds} The rectangular bounds of this Circle.
471
+ **/
472
+ getBounds() {
473
+ return new Bounds(this.center.clone().subXY(Math.abs(this.radius), Math.abs(this.radius)), this.center.clone().addXY(Math.abs(this.radius), Math.abs(this.radius)));
608
474
  }
475
+ //--- END --- Implement interface `Intersectable`
609
476
  /**
610
477
  * Calculate the intersection points (if exists) with the given circle.
611
478
  *
@@ -665,7 +532,7 @@ class Circle {
665
532
  * @instance
666
533
  * @memberof Circle
667
534
  * @param {Vertex} a- The first of the two points defining the line.
668
- * @param {Vertex} b - The second of the two points defining the line.
535
+ * @param {XYCoords} b - The second of the two points defining the line.
669
536
  * @return {Line|null} The intersection points (as a line) or null if this circle does not intersect the line given.
670
537
  **/
671
538
  lineIntersection(a, b) {
@@ -698,8 +565,54 @@ class Circle {
698
565
  interA.y = (-det * diff.x + Math.abs(diff.y) * sqrt) / distSquared;
699
566
  interB.y = (-det * diff.x - Math.abs(diff.y) * sqrt) / distSquared;
700
567
  return new Line(interA.add(this.center), interB.add(this.center));
701
- // return new Line(interA, interB);
702
568
  }
569
+ //--- BEGIN --- Implement interface `Intersectable`
570
+ /**
571
+ * Get all line intersections with this circle.
572
+ *
573
+ * This method returns all intersections (as vertices) with this shape. The returned array of vertices is in no specific order.
574
+ *
575
+ * @param {VertTuple} line - The line to find intersections with.
576
+ * @param {boolean} inVectorBoundsOnly - If set to true only intersecion points on the passed vector are returned (located strictly between start and end vertex).
577
+ * @returns {Array<Vertex>} - An array of all intersections with the circle outline.
578
+ */
579
+ lineIntersections(line, inVectorBoundsOnly = false) {
580
+ // Find the intersections of all lines inside the edge bounds
581
+ const intersectioLine = this.lineIntersection(line.a, line.b);
582
+ if (!intersectioLine) {
583
+ return [];
584
+ }
585
+ if (inVectorBoundsOnly) {
586
+ // const maxDist = line.length();
587
+ return [intersectioLine.a, intersectioLine.b].filter((vert) => line.hasPoint(vert, true));
588
+ }
589
+ else {
590
+ return [intersectioLine.a, intersectioLine.b];
591
+ }
592
+ }
593
+ /**
594
+ * Get all line intersections of this polygon and their tangents along the shape.
595
+ *
596
+ * This method returns all intersection tangents (as vectors) with this shape. The returned array of vectors is in no specific order.
597
+ *
598
+ * @param line
599
+ * @param lineIntersectionTangents
600
+ * @returns
601
+ */
602
+ lineIntersectionTangents(line, inVectorBoundsOnly = false) {
603
+ // Find the intersections of all lines plus their tangents inside the circle bounds
604
+ const interSectionPoints = this.lineIntersections(line, inVectorBoundsOnly);
605
+ return interSectionPoints.map((vert) => {
606
+ // Calculate angle
607
+ const lineFromCenter = new Line(this.center, vert);
608
+ const angle = lineFromCenter.angle();
609
+ // console.log("angle", (angle / Math.PI) * 180.0);
610
+ // const angle = Math.random() * Math.PI * 2; // TODO
611
+ // Calculate tangent at angle
612
+ return this.tangentAt(angle);
613
+ });
614
+ }
615
+ //--- END --- Implement interface `Intersectable`
703
616
  /**
704
617
  * Calculate the closest point on the outline of this circle to the given point.
705
618
  *
@@ -766,7 +679,11 @@ Circle.circleUtils = {
766
679
  * @modified 2022-02-02 Cleared the `Triangle.toSVGString` function (deprecated). Use `drawutilssvg` instead.
767
680
  * @modified 2024-11-22 Added static utility function Triangle.utils.determinant; adapted method `determinant`.
768
681
  * @modified 2024-11-22 Changing visibility of `Triangle.utils` from `private` to `public`.
769
- * @version 2.8.0
682
+ * @modified 2025-14-16 Class `Triangle` now implements interface `Intersectable`.
683
+ * @modified 2025-14-16 Class `Triangle` now implements interface `IBounded`.
684
+ * @modified 2025-14-16 Class `Triangle` now implements interface `Intersectable`.
685
+ * @modified 2025-14-16 Added method `Triangle.move`.
686
+ * @version 2.10.0
770
687
  *
771
688
  * @file Triangle
772
689
  * @fileoverview A simple triangle class: three vertices.
@@ -871,6 +788,35 @@ class Triangle {
871
788
  this.c.scale(factor, centroid);
872
789
  return this;
873
790
  }
791
+ //--- BEGIN --- Implement interface `IBounded`
792
+ /**
793
+ * Get the bounding box (bounds) of this Triangle.
794
+ *
795
+ * @method getBounds
796
+ * @instance
797
+ * @memberof Triangle
798
+ * @return {Bounds} The rectangular bounds of this Triangle.
799
+ **/
800
+ getBounds() {
801
+ // return Bounds.computeFromVertices([this.a, this.b, this.c]);
802
+ return this.bounds();
803
+ }
804
+ //--- END --- Implement interface `IBounded`
805
+ /**
806
+ * Move the Triangle's vertices by the given amount.
807
+ *
808
+ * @method move
809
+ * @param {XYCoords} amount - The amount to move.
810
+ * @instance
811
+ * @memberof Triangle
812
+ * @return {Triangle} this for chaining
813
+ **/
814
+ move(amount) {
815
+ this.a.add(amount);
816
+ this.b.add(amount);
817
+ this.c.add(amount);
818
+ return this;
819
+ }
874
820
  /**
875
821
  * Get the circumcircle of this triangle.
876
822
  *
@@ -996,6 +942,48 @@ class Triangle {
996
942
  bounds() {
997
943
  return new Bounds(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)), 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)));
998
944
  }
945
+ //--- BEGIN --- Implement interface `Intersectable`
946
+ /**
947
+ * Get all line intersections with this polygon.
948
+ *
949
+ * This method returns all intersections (as vertices) with this shape. The returned array of vertices is in no specific order.
950
+ *
951
+ * See demo `47-closest-vector-projection-on-polygon` for how it works.
952
+ *
953
+ * @param {VertTuple} line - The line to find intersections with.
954
+ * @param {boolean} inVectorBoundsOnly - If set to true only intersecion points on the passed vector are returned (located strictly between start and end vertex).
955
+ * @returns {Array<Vertex>} - An array of all intersections within the polygon bounds.
956
+ */
957
+ lineIntersections(line, inVectorBoundsOnly = false) {
958
+ // Find the intersections of all lines inside the edge bounds
959
+ return Polygon.utils
960
+ .locateLineIntersecion(line, [this.a, this.b, this.c], false, inVectorBoundsOnly)
961
+ .map(intersectionTuple => intersectionTuple.intersectionPoint);
962
+ }
963
+ /**
964
+ * Get all line intersections of this polygon and their tangents along the shape.
965
+ *
966
+ * This method returns all intersection tangents (as vectors) with this shape. The returned array of vectors is in no specific order.
967
+ *
968
+ * @param line
969
+ * @param inVectorBoundsOnly
970
+ * @returns
971
+ */
972
+ lineIntersectionTangents(line, inVectorBoundsOnly = false) {
973
+ // Find the intersection tangents of all lines inside the edge bounds
974
+ return Polygon.utils
975
+ .locateLineIntersecion(line, [this.a, this.b, this.c], false, inVectorBoundsOnly)
976
+ .map(intersectionTuple => {
977
+ // const polyLine = this.getEdgeAt(intersectionTuple.edgeIndex);
978
+ const polyLine = this.getEdgeAt(intersectionTuple.edgeIndex);
979
+ return new Vector(polyLine.a.clone(), polyLine.b.clone()).moveTo(intersectionTuple.intersectionPoint);
980
+ });
981
+ }
982
+ //--- END --- Implement interface `Intersectable`
983
+ getEdgeAt(edgeIndex) {
984
+ var modIndex = edgeIndex % 3;
985
+ return modIndex === 0 ? new Line(this.a, this.b) : modIndex === 1 ? new Line(this.b, this.c) : new Line(this.c, this.a);
986
+ }
999
987
  /**
1000
988
  * Convert this triangle to a polygon instance.
1001
989
  *
@@ -1303,7 +1291,9 @@ const geomutils = {
1303
1291
  * @modified 2023-09-29 Added the `Vertex.abs()` method as it seems useful.
1304
1292
  * @modified 2024-03-08 Added the optional `precision` param to the `toString` method.
1305
1293
  * @modified 2024-12-17 Outsourced the euclidean distance calculation of `Vertex.distance` to `geomutils.dist4`.
1306
- * @version 2.9.1
1294
+ * @modified 2025-03-24 Making the second parameter `center` of the `Vertex.rotate` method optional.
1295
+ * @modified 2025-04-13 Adding the `Vertex.move(amount: XYCoords)` method (does the same as `add`, added by naming convention).
1296
+ * @version 2.10.0
1307
1297
  *
1308
1298
  * @file Vertex
1309
1299
  * @public
@@ -1494,6 +1484,21 @@ class Vertex {
1494
1484
  }
1495
1485
  return this;
1496
1486
  }
1487
+ /**
1488
+ * Move this point by the given amount.
1489
+ *
1490
+ * This method just calls `add(amount).
1491
+ *
1492
+ *
1493
+ * @method move
1494
+ * @param {Vertex} amount - The amount to move this vertex.
1495
+ * @return {Vertex} this - For chaining.
1496
+ * @instance
1497
+ * @memberof Vertex
1498
+ */
1499
+ move(amount) {
1500
+ return this.add(amount);
1501
+ }
1497
1502
  /**
1498
1503
  * Add the passed amounts to the x- and y- components of this vertex.
1499
1504
  *
@@ -1943,7 +1948,10 @@ Vertex.utils = {
1943
1948
  * @modified 2024-09-10 Chaging the first param of `pointDistance` from `Vertex` to less strict type `XYCoords`. This should not break anything.
1944
1949
  * @modified 2024-09-10 Adding the optional `epsilon` param to the `hasPoint` method.
1945
1950
  * @modified 2024-12-02 Added the `epsilon` param to the `colinear` method. Default is 1.0e-6.
1946
- * @version 1.3.0
1951
+ * @modified 2025-03-31 Added the `VertTuple.revert` method.
1952
+ * @modified 2025-04-15 Changed param of `VertTuple.moveTo` method from `Vertex` to `XYCoords`.
1953
+ * @modified 2025-04-15 Added method `VertTuple.move` method.
1954
+ * @version 1.4.0
1947
1955
  */
1948
1956
  /**
1949
1957
  * @classdesc An abstract base classes for vertex tuple constructs, like Lines or Vectors.
@@ -2008,9 +2016,9 @@ class VertTuple {
2008
2016
  *
2009
2017
  * @method add
2010
2018
  * @param {XYCoords} amount The amount (x,y) to add.
2011
- * @return {Line} this
2012
2019
  * @instance
2013
2020
  * @memberof VertTuple
2021
+ * @return {VertTuple<T>} this
2014
2022
  **/
2015
2023
  add(amount) {
2016
2024
  this.a.add(amount);
@@ -2018,10 +2026,26 @@ class VertTuple {
2018
2026
  return this;
2019
2027
  }
2020
2028
  /**
2021
- * Normalize this line (set to length 1).
2029
+ * Reverse this vertex tuple: a becomes b, and b becomes a.
2030
+ * This operation is in-place.
2022
2031
  *
2023
- * @method normalize
2024
- * @return {VertTuple} this
2032
+ * @method add
2033
+ * @param {XYCoords} amount The amount (x,y) to add.
2034
+ * @instance
2035
+ * @memberof VertTuple
2036
+ * @return {VertTuple<T>} this
2037
+ */
2038
+ revert() {
2039
+ const tmp = this.a;
2040
+ this.a = this.b;
2041
+ this.b = tmp;
2042
+ return this;
2043
+ }
2044
+ /**
2045
+ * Normalize this line (set to length 1).
2046
+ *
2047
+ * @method normalize
2048
+ * @return {VertTuple} this
2025
2049
  * @instance
2026
2050
  * @memberof VertTuple
2027
2051
  **/
@@ -2046,7 +2070,7 @@ class VertTuple {
2046
2070
  * Move this line to a new location.
2047
2071
  *
2048
2072
  * @method moveTo
2049
- * @param {Vertex} newA - The new desired location of 'a'. Vertex 'b' will be moved, too.
2073
+ * @param {XYCoords} newA - The new desired location of 'a'. Vertex 'b' will be moved, too.
2050
2074
  * @return {VertTuple} this
2051
2075
  * @instance
2052
2076
  * @memberof VertTuple
@@ -2057,6 +2081,20 @@ class VertTuple {
2057
2081
  this.b.add(diff);
2058
2082
  return this;
2059
2083
  }
2084
+ /**
2085
+ * Move this line by the given amount
2086
+ *
2087
+ * @method move
2088
+ * @param {XYCoords} amount - The amount to move both point of this tuple.
2089
+ * @return {VertTuple} this
2090
+ * @instance
2091
+ * @memberof VertTuple
2092
+ **/
2093
+ move(amount) {
2094
+ this.a.add(amount);
2095
+ this.b.add(amount);
2096
+ return this;
2097
+ }
2060
2098
  /**
2061
2099
  * Get the angle between this and the passed line (in radians).
2062
2100
  *
@@ -2236,6 +2274,181 @@ VertTuple.vtutils = {
2236
2274
  }
2237
2275
  };
2238
2276
 
2277
+ /**
2278
+ * @author Ikaros Kappler
2279
+ * @date 2019-01-30
2280
+ * @modified 2019-02-23 Added the toSVGString function, overriding Line.toSVGString.
2281
+ * @modified 2019-03-20 Added JSDoc tags.
2282
+ * @modified 2019-04-19 Added the clone function (overriding Line.clone()).
2283
+ * @modified 2019-09-02 Added the Vector.perp() function.
2284
+ * @modified 2019-09-02 Added the Vector.inverse() function.
2285
+ * @modified 2019-12-04 Added the Vector.inv() function.
2286
+ * @modified 2020-03-23 Ported to Typescript from JS.
2287
+ * @modified 2021-01-20 Added UID.
2288
+ * @modified 2022-02-02 Added the `destroy` method.
2289
+ * @modified 2022-02-02 Cleared the `Vector.toSVGString` function (deprecated). Use `drawutilssvg` instead.
2290
+ * @modified 2022-10-25 Added the `getOrthogonal` method.
2291
+ * @modified 2025-04-14 Added the `Vector.rotate(number)` method.
2292
+ * @version 1.6.0
2293
+ *
2294
+ * @file Vector
2295
+ * @public
2296
+ **/
2297
+ /**
2298
+ * @classdesc A vector (Vertex,Vertex) is a line with a visible direction.<br>
2299
+ * <br>
2300
+ * Vectors are drawn with an arrow at their end point.<br>
2301
+ * <b>The Vector class extends the Line class.</b>
2302
+ *
2303
+ * @requires VertTuple
2304
+ * @requires Vertex
2305
+ **/
2306
+ class Vector extends VertTuple {
2307
+ /**
2308
+ * The constructor.
2309
+ *
2310
+ * @constructor
2311
+ * @name Vector
2312
+ * @extends Line
2313
+ * @param {Vertex} vertA - The start vertex of the vector.
2314
+ * @param {Vertex} vertB - The end vertex of the vector.
2315
+ **/
2316
+ constructor(vertA, vertB) {
2317
+ super(vertA, vertB, (a, b) => new Vector(a, b));
2318
+ /**
2319
+ * Required to generate proper CSS classes and other class related IDs.
2320
+ **/
2321
+ this.className = "Vector";
2322
+ }
2323
+ /**
2324
+ * Get the perpendicular of this vector which is located at a.
2325
+ *
2326
+ * @return {Vector} A new vector being the perpendicular of this vector sitting on a.
2327
+ **/
2328
+ perp() {
2329
+ var v = this.clone();
2330
+ v.sub(this.a);
2331
+ v = new Vector(new Vertex(), new Vertex(-v.b.y, v.b.x));
2332
+ v.a.add(this.a);
2333
+ v.b.add(this.a);
2334
+ return v;
2335
+ }
2336
+ /**
2337
+ * The inverse of a vector is a vector with the same magnitude but oppose direction.
2338
+ *
2339
+ * Please not that the origin of this vector changes here: a->b becomes b->a.
2340
+ *
2341
+ * @return {Vector}
2342
+ **/
2343
+ inverse() {
2344
+ var tmp = this.a;
2345
+ this.a = this.b;
2346
+ this.b = tmp;
2347
+ return this;
2348
+ }
2349
+ /**
2350
+ * This function computes the inverse of the vector, which means 'a' stays untouched.
2351
+ *
2352
+ * @return {Vector} this for chaining.
2353
+ **/
2354
+ inv() {
2355
+ this.b.x = this.a.x - (this.b.x - this.a.x);
2356
+ this.b.y = this.a.y - (this.b.y - this.a.y);
2357
+ return this;
2358
+ }
2359
+ /**
2360
+ * Get the intersection if this vector and the specified vector.
2361
+ *
2362
+ * @method intersection
2363
+ * @param {Vector} line The second vector.
2364
+ * @return {Vertex} The intersection (may lie outside the end-points).
2365
+ * @instance
2366
+ * @memberof Line
2367
+ **/
2368
+ intersection(line) {
2369
+ var denominator = this.denominator(line);
2370
+ if (denominator == 0)
2371
+ return null;
2372
+ var a = this.a.y - line.a.y;
2373
+ var b = this.a.x - line.a.x;
2374
+ var numerator1 = (line.b.x - line.a.x) * a - (line.b.y - line.a.y) * b;
2375
+ var numerator2 = (this.b.x - this.a.x) * a - (this.b.y - this.a.y) * b;
2376
+ a = numerator1 / denominator; // NaN if parallel lines
2377
+ b = numerator2 / denominator;
2378
+ // TODO:
2379
+ // FOR A VECTOR THE LINE-INTERSECTION MUST BE ON BOTH VECTORS
2380
+ // if we cast these lines infinitely in both directions, they intersect here:
2381
+ return new Vertex(this.a.x + a * (this.b.x - this.a.x), this.a.y + a * (this.b.y - this.a.y));
2382
+ }
2383
+ /**
2384
+ * Get the orthogonal "vector" of this vector (rotated by 90° clockwise).
2385
+ *
2386
+ * @name getOrthogonal
2387
+ * @method getOrthogonal
2388
+ * @return {Vector} A new vector with the same length that stands on this vector's point a.
2389
+ * @instance
2390
+ * @memberof Vector
2391
+ **/
2392
+ getOrthogonal() {
2393
+ // Orthogonal of vector (0,0)->(x,y) is (0,0)->(-y,x)
2394
+ const linePoint = this.a.clone();
2395
+ const startPoint = this.b.clone().sub(this.a);
2396
+ const tmp = startPoint.x;
2397
+ startPoint.x = -startPoint.y;
2398
+ startPoint.y = tmp;
2399
+ return new Vector(linePoint, startPoint.add(this.a));
2400
+ }
2401
+ /**
2402
+ * Rotate this vector by the given angle around the first point `a`.
2403
+ *
2404
+ * @name rotate
2405
+ * @method rotate
2406
+ * @return {Vector} this - for chaining.
2407
+ * @instance
2408
+ * @memberof Vector
2409
+ */
2410
+ rotate(angle) {
2411
+ this.b.rotate(angle, this.a);
2412
+ return this;
2413
+ }
2414
+ }
2415
+ Vector.utils = {
2416
+ /**
2417
+ * Generate a four-point arrow head, starting at the vector end minus the
2418
+ * arrow head length.
2419
+ *
2420
+ * The first vertex in the returned array is guaranteed to be the located
2421
+ * at the vector line end minus the arrow head length.
2422
+ *
2423
+ *
2424
+ * Due to performance all params are required.
2425
+ *
2426
+ * The params scaleX and scaleY are required for the case that the scaling is not uniform (x and y
2427
+ * scaling different). Arrow heads should not look distored on non-uniform scaling.
2428
+ *
2429
+ * If unsure use 1.0 for scaleX and scaleY (=no distortion).
2430
+ * For headlen use 8, it's a good arrow head size.
2431
+ *
2432
+ * Example:
2433
+ * buildArrowHead( new Vertex(0,0), new Vertex(50,100), 8, 1.0, 1.0 )
2434
+ *
2435
+ * @param {XYCoords} zA - The start vertex of the vector to calculate the arrow head for.
2436
+ * @param {XYCoords} zB - The end vertex of the vector.
2437
+ * @param {number} headlen - The length of the arrow head (along the vector direction. A good value is 12).
2438
+ * @param {number} scaleX - The horizontal scaling during draw.
2439
+ * @param {number} scaleY - the vertical scaling during draw.
2440
+ **/
2441
+ buildArrowHead: (zA, zB, headlen, scaleX, scaleY) => {
2442
+ const angle = Math.atan2((zB.y - zA.y) * scaleY, (zB.x - zA.x) * scaleX);
2443
+ const vertices = [];
2444
+ vertices.push(new Vertex(zB.x * scaleX - headlen * Math.cos(angle), zB.y * scaleY - headlen * Math.sin(angle)));
2445
+ vertices.push(new Vertex(zB.x * scaleX - headlen * 1.35 * Math.cos(angle - Math.PI / 8), zB.y * scaleY - headlen * 1.35 * Math.sin(angle - Math.PI / 8)));
2446
+ vertices.push(new Vertex(zB.x * scaleX, zB.y * scaleY));
2447
+ vertices.push(new Vertex(zB.x * scaleX - headlen * 1.35 * Math.cos(angle + Math.PI / 8), zB.y * scaleY - headlen * 1.35 * Math.sin(angle + Math.PI / 8)));
2448
+ return vertices;
2449
+ }
2450
+ };
2451
+
2239
2452
  /**
2240
2453
  * @author Ikaros Kappler
2241
2454
  * @date 2016-03-12
@@ -2254,7 +2467,9 @@ VertTuple.vtutils = {
2254
2467
  * @modified 2022-10-09 Changed the actual return value of the `intersection` function to null (was undefined before).
2255
2468
  * @modified 2022-10-17 Adding these methods from the `PathSegment` interface: getStartPoint, getEndPoint, revert.
2256
2469
  * @modified 2023-09-25 Changed param type of `intersection()` from Line to VertTuple.
2257
- * @version 2.3.0
2470
+ * @modified 2025-04-15 Class `Line` now implements interface `Intersectable`.
2471
+ * @modified 2025-04-16 Class `Line` now implements interface `IBounded`.
2472
+ * @version 2.4.0
2258
2473
  *
2259
2474
  * @file Line
2260
2475
  * @public
@@ -2313,6 +2528,19 @@ class Line extends VertTuple {
2313
2528
  // if we cast these lines infinitely in both directions, they intersect here:
2314
2529
  return new Vertex(x, y);
2315
2530
  }
2531
+ //--- BEGIN --- Implement interface `IBounded`
2532
+ /**
2533
+ * Get the bounding box (bounds) of this Line.
2534
+ *
2535
+ * @method getBounds
2536
+ * @instance
2537
+ * @memberof Line
2538
+ * @return {Bounds} The rectangular bounds of this Line.
2539
+ **/
2540
+ getBounds() {
2541
+ return Bounds.computeFromVertices([this.a, this.b]);
2542
+ }
2543
+ //--- END --- Implement interface `IBounded`
2316
2544
  //--- Implement PathSegment ---
2317
2545
  /**
2318
2546
  * Get the start point of this path segment.
@@ -2367,6 +2595,50 @@ class Line extends VertTuple {
2367
2595
  this.b = tmp;
2368
2596
  return this;
2369
2597
  }
2598
+ //--- END Implement PathSegment ---
2599
+ //--- BEGIN --- Implement interface `Intersectable`
2600
+ /**
2601
+ * Get all line intersections with this polygon.
2602
+ *
2603
+ * This method returns all intersections (as vertices) with this shape. The returned array of vertices is in no specific order.
2604
+ *
2605
+ * See demo `47-closest-vector-projection-on-polygon` for how it works.
2606
+ *
2607
+ * @param {VertTuple} line - The line to find intersections with.
2608
+ * @param {boolean} inVectorBoundsOnly - If set to true only intersecion points on the passed vector are returned (located strictly between start and end vertex).
2609
+ * @returns {Array<Vertex>} - An array of all intersections within the polygon bounds.
2610
+ */
2611
+ lineIntersections(line, inVectorBoundsOnly = false) {
2612
+ // Find the intersections of all lines inside the edge bounds
2613
+ const intersection = this.intersection(line);
2614
+ if (!intersection) {
2615
+ return []; // Both lines parallel
2616
+ }
2617
+ if (this.hasPoint(intersection, true) && (!inVectorBoundsOnly || line.hasPoint(intersection, inVectorBoundsOnly))) {
2618
+ return [intersection];
2619
+ }
2620
+ else {
2621
+ return [];
2622
+ }
2623
+ }
2624
+ /**
2625
+ * Get all line intersections of this polygon and their tangents along the shape.
2626
+ *
2627
+ * This method returns all intersection tangents (as vectors) with this shape. The returned array of vectors is in no specific order.
2628
+ *
2629
+ * @param line
2630
+ * @param inVectorBoundsOnly
2631
+ * @returns
2632
+ */
2633
+ lineIntersectionTangents(line, inVectorBoundsOnly = false) {
2634
+ // Find the intersection tangents of all lines inside the edge bounds
2635
+ const intersections = this.lineIntersections(line, inVectorBoundsOnly);
2636
+ if (intersections.length === 0) {
2637
+ return [];
2638
+ }
2639
+ const intrsctn = intersections[0];
2640
+ return [new Vector(this.a.clone(), this.b.clone()).moveTo(intrsctn)];
2641
+ }
2370
2642
  }
2371
2643
 
2372
2644
  /**
@@ -2403,7 +2675,11 @@ class Line extends VertTuple {
2403
2675
  * @modified 2024-10-30 Added the `getEdges` method.
2404
2676
  * @modified 2024-12-02 Added the `elimitateColinearEdges` method.
2405
2677
  * @modified 2025-02-12 Added the `containsVerts` method to test multiple vertices for containment.
2406
- * @version 1.14.0
2678
+ * @modified 2025-03-28 Added the `Polygon.utils.locateLineIntersecion` static helper method.
2679
+ * @modified 2025-03-28 Added the `Polygon.lineIntersectionTangents` method.
2680
+ * @modified 2025-04-09 Added the `Polygon.getCentroid` method.
2681
+ * @modified 2025-05-16 Class `Polygon` now implements `IBounded`.
2682
+ * @version 1.15.0
2407
2683
  *
2408
2684
  * @file Polygon
2409
2685
  * @public
@@ -2775,9 +3051,44 @@ class Polygon {
2775
3051
  center.y /= this.vertices.length;
2776
3052
  return center;
2777
3053
  }
3054
+ /**
3055
+ * Get centroid.
3056
+ * Centroids define the barycenter of any non self-intersecting convex polygon.
3057
+ *
3058
+ * If the polygon is self intersecting or non konvex then the barycenter is not well defined.
3059
+ *
3060
+ * https://mathworld.wolfram.com/PolygonCentroid.html
3061
+ *
3062
+ * @method getCentroid
3063
+ * @instance
3064
+ * @memberof Polygon
3065
+ * @returns {Vertex|null}
3066
+ */
3067
+ getCentroid() {
3068
+ if (this.vertices.length === 0) {
3069
+ return null;
3070
+ }
3071
+ const center = new Vertex(0.0, 0.0);
3072
+ const n = this.vertices.length;
3073
+ for (var i = 0; i < n; i++) {
3074
+ // center.add(this.vertices[i]);
3075
+ const cur = this.vertices[i];
3076
+ const next = this.vertices[(i + 1) % n];
3077
+ var factor = cur.x * next.y - next.x * cur.y;
3078
+ center.x += (cur.x + next.x) * factor;
3079
+ center.y += (cur.y + next.y) * factor;
3080
+ }
3081
+ const area = this.area();
3082
+ center.x *= 1 / (6 * area);
3083
+ center.y *= 1 / (6 * area);
3084
+ return center;
3085
+ }
3086
+ //--- BEGIN --- Implement interface `Intersectable`
2778
3087
  /**
2779
3088
  * Get all line intersections with this polygon.
2780
3089
  *
3090
+ * This method returns all intersections (as vertices) with this shape. The returned array of vertices is in no specific order.
3091
+ *
2781
3092
  * See demo `47-closest-vector-projection-on-polygon` for how it works.
2782
3093
  *
2783
3094
  * @param {VertTuple} line - The line to find intersections with.
@@ -2786,21 +3097,27 @@ class Polygon {
2786
3097
  */
2787
3098
  lineIntersections(line, inVectorBoundsOnly = false) {
2788
3099
  // Find the intersections of all lines inside the edge bounds
2789
- const intersectionPoints = [];
2790
- for (var i = 0; i < this.vertices.length; i++) {
2791
- const polyLine = new Line(this.vertices[i], this.vertices[(i + 1) % this.vertices.length]);
2792
- const intersection = polyLine.intersection(line);
2793
- // true => only inside bounds
2794
- // ignore last edge if open
2795
- if ((!this.isOpen || i + 1 !== this.vertices.length) &&
2796
- intersection !== null &&
2797
- polyLine.hasPoint(intersection, true) &&
2798
- (!inVectorBoundsOnly || line.hasPoint(intersection, inVectorBoundsOnly))) {
2799
- intersectionPoints.push(intersection);
2800
- }
2801
- }
2802
- return intersectionPoints;
3100
+ return Polygon.utils
3101
+ .locateLineIntersecion(line, this.vertices, this.isOpen, inVectorBoundsOnly)
3102
+ .map(intersectionTuple => intersectionTuple.intersectionPoint);
2803
3103
  }
3104
+ /**
3105
+ * Get all line intersections of this polygon and their tangents along the shape.
3106
+ *
3107
+ * This method returns all intersection tangents (as vectors) with this shape. The returned array of vectors is in no specific order.
3108
+ *
3109
+ * @param line
3110
+ * @param inVectorBoundsOnly
3111
+ * @returns
3112
+ */
3113
+ lineIntersectionTangents(line, inVectorBoundsOnly = false) {
3114
+ // Find the intersection tangents of all lines inside the edge bounds
3115
+ return Polygon.utils.locateLineIntersecion(line, this.vertices, this.isOpen, inVectorBoundsOnly).map(intersectionTuple => {
3116
+ const polyLine = this.getEdgeAt(intersectionTuple.edgeIndex);
3117
+ return new Vector(polyLine.a.clone(), polyLine.b.clone()).moveTo(intersectionTuple.intersectionPoint);
3118
+ });
3119
+ }
3120
+ //--- END --- Implement interface `Intersectable`
2804
3121
  /**
2805
3122
  * Get the closest line-polygon-intersection point (closest the line point A).
2806
3123
  *
@@ -2901,6 +3218,7 @@ class Polygon {
2901
3218
  }
2902
3219
  return result;
2903
3220
  }
3221
+ //--- BEGIN --- Implement interface `IBounded`
2904
3222
  /**
2905
3223
  * Get the bounding box (bounds) of this polygon.
2906
3224
  *
@@ -2912,6 +3230,7 @@ class Polygon {
2912
3230
  getBounds() {
2913
3231
  return Bounds.computeFromVertices(this.vertices);
2914
3232
  }
3233
+ //--- END --- Implement interface `IBounded`
2915
3234
  /**
2916
3235
  * Create a deep copy of this polygon.
2917
3236
  *
@@ -2966,7 +3285,6 @@ class Polygon {
2966
3285
  // -> delete all vertices in between
2967
3286
  if (j - i > 2) {
2968
3287
  // Means: there have been 'colinear vertices' in between
2969
- // console.log("Splice", "i", i, "j", j, i + 1, j - i - 1);
2970
3288
  verts.splice(i + 1, j - i - 2);
2971
3289
  }
2972
3290
  i++;
@@ -3163,6 +3481,32 @@ Polygon.utils = {
3163
3481
  sum += (vertices[j].x - vertices[i].x) * (vertices[i].y + vertices[j].y);
3164
3482
  }
3165
3483
  return sum;
3484
+ },
3485
+ /**
3486
+ * Find intersections of a line with a polygon (vertices).
3487
+ *
3488
+ * @param {VertTuple<any>} line - The line to find intersections with.
3489
+ * @param {Array<Vertex>} vertices - The polygon's vertices.
3490
+ * @param {boolean} isOpen - True if the polygon is open, false otherwise.
3491
+ * @param {boolean} inVectorBoundsOnly - If only intersections in strict vector bounds should be returned.
3492
+ * @returns
3493
+ */
3494
+ locateLineIntersecion(line, vertices, isOpen, inVectorBoundsOnly) {
3495
+ // Find the intersections of all lines inside the edge bounds
3496
+ const intersectionPoints = [];
3497
+ var n = isOpen ? vertices.length - 1 : vertices.length;
3498
+ for (var i = 0; i < n; i++) {
3499
+ const polyLine = new Line(vertices[i % n], vertices[(i + 1) % n]);
3500
+ const intersection = polyLine.intersection(line);
3501
+ // true => only inside bounds
3502
+ // ignore last edge if open
3503
+ if (intersection !== null &&
3504
+ polyLine.hasPoint(intersection, true) &&
3505
+ (!inVectorBoundsOnly || line.hasPoint(intersection, inVectorBoundsOnly))) {
3506
+ intersectionPoints.push({ edgeIndex: i, intersectionPoint: intersection });
3507
+ }
3508
+ }
3509
+ return intersectionPoints;
3166
3510
  }
3167
3511
  };
3168
3512
 
@@ -3177,7 +3521,11 @@ Polygon.utils = {
3177
3521
  * @modified 2022-10-09 Added the `fromDimension` function.
3178
3522
  * @modified 2022-11-28 Added the `clone` method.
3179
3523
  * @modified 2023-09-29 Added the `randomPoint` method.
3180
- * @version 1.7.0
3524
+ * @modified 2025-03-23 Added the `getMinDimension` and `getMaxDimension` methods.
3525
+ * @modified 2025-04-18 Change parameter type in `Bounds.computeFromVertices` from `Vertex` to more general `XYCoords`.
3526
+ * @modified 2025-04-19 Added methods to `Bounds` class: `getNorthPoint`, `getSouthPoint`, `getEastPoint` and `getWestPoint`.
3527
+ * @modified 2025-04-26 Added static method `Bounds.computeFromBoundsSet` to calculate containing bounds for a set of bounding boxes.
3528
+ * @version 1.8.0
3181
3529
  **/
3182
3530
  /**
3183
3531
  * @classdesc A bounds class with min and max values. Implementing IBounds.
@@ -3201,6 +3549,54 @@ class Bounds {
3201
3549
  this.width = max.x - min.x;
3202
3550
  this.height = max.y - min.y;
3203
3551
  }
3552
+ /**
3553
+ * Get the center point of the north bound.
3554
+ *
3555
+ * @method getNorthPoint
3556
+ * @instance
3557
+ * @memberof Bounds
3558
+ * @return {Vertex} The "northmost" centered point of this bounding box.
3559
+ */
3560
+ getNorthPoint() {
3561
+ return new Vertex(this.min.x + this.width / 2.0, this.min.y);
3562
+ }
3563
+ ;
3564
+ /**
3565
+ * Get the center point of the south bound.
3566
+ *
3567
+ * @method getNorthPoint
3568
+ * @instance
3569
+ * @memberof Bounds
3570
+ * @return {Vertex} The "southhmost" centered point of this bounding box.
3571
+ */
3572
+ getSouthPoint() {
3573
+ return new Vertex(this.min.x + this.width / 2.0, this.max.y);
3574
+ }
3575
+ ;
3576
+ /**
3577
+ * Get the center point of the west bound.
3578
+ *
3579
+ * @method getWestPoint
3580
+ * @instance
3581
+ * @memberof Bounds
3582
+ * @return {Vertex} The "westhmost" centered point of this bounding box.
3583
+ */
3584
+ getWestPoint() {
3585
+ return new Vertex(this.min.x, this.min.y + this.height / 2.0);
3586
+ }
3587
+ ;
3588
+ /**
3589
+ * Get the center point of the east bound.
3590
+ *
3591
+ * @method getEastPoint
3592
+ * @instance
3593
+ * @memberof Bounds
3594
+ * @return {Vertex} The "easthmost" centered point of this bounding box.
3595
+ */
3596
+ getEastPoint() {
3597
+ return new Vertex(this.max.x, this.min.y + this.height / 2.0);
3598
+ }
3599
+ ;
3204
3600
  /**
3205
3601
  * Convert this rectangular bounding box to a polygon with four vertices.
3206
3602
  *
@@ -3223,6 +3619,22 @@ class Bounds {
3223
3619
  getCenter() {
3224
3620
  return new Vertex(this.min.x + (this.max.x - this.min.x) / 2.0, this.min.y + (this.max.y - this.min.y) / 2);
3225
3621
  }
3622
+ /**
3623
+ * Get the minimum of `width` and `height`.
3624
+ *
3625
+ * @returns {number} The value of Math.min( this.width, this.height )
3626
+ */
3627
+ getMinDimension() {
3628
+ return Math.min(this.width, this.height);
3629
+ }
3630
+ /**
3631
+ * Get the minimum of `width` and `height`.
3632
+ *
3633
+ * @returns {number} The value of Math.min( this.width, this.height )
3634
+ */
3635
+ getMaxDimension() {
3636
+ return Math.max(this.width, this.height);
3637
+ }
3226
3638
  /**
3227
3639
  * Generate a random point inside this bounds object. Safe areas at the border to avoid
3228
3640
  * included.
@@ -3273,12 +3685,13 @@ class Bounds {
3273
3685
  * @static
3274
3686
  * @method computeFromVertices
3275
3687
  * @memberof Bounds
3276
- * @param {Array<Vertex>} vertices - The set of vertices you want to get the bounding box for.
3688
+ * @param {Array<XYCoords>} vertices - The set of vertices you want to get the bounding box for.
3277
3689
  * @return The minimal Bounds for the given vertices.
3278
3690
  **/
3279
3691
  static computeFromVertices(vertices) {
3280
- if (vertices.length == 0)
3692
+ if (vertices.length == 0) {
3281
3693
  return new Bounds(new Vertex(0, 0), new Vertex(0, 0));
3694
+ }
3282
3695
  let xMin = vertices[0].x;
3283
3696
  let xMax = vertices[0].x;
3284
3697
  let yMin = vertices[0].y;
@@ -3293,6 +3706,35 @@ class Bounds {
3293
3706
  }
3294
3707
  return new Bounds(new Vertex(xMin, yMin), new Vertex(xMax, yMax));
3295
3708
  }
3709
+ /**
3710
+ * Compute the minimal bounding box for a given set of existing bounding boxes.
3711
+ *
3712
+ * An empty vertex array will return an empty bounding box located at (0,0).
3713
+ *
3714
+ * @static
3715
+ * @method computeFromBoundsSet
3716
+ * @memberof Bounds
3717
+ * @param {Array<IBounds>} boundingBoxes - The set of existing bounding boxes to get the containing bounding box for.
3718
+ * @return The minimal Bounds for the given bounds instances.
3719
+ **/
3720
+ static computeFromBoundsSet(boundingBoxes) {
3721
+ if (boundingBoxes.length == 0) {
3722
+ return new Bounds(new Vertex(0, 0), new Vertex(0, 0));
3723
+ }
3724
+ let xMin = boundingBoxes[0].min.x;
3725
+ let xMax = boundingBoxes[0].max.x;
3726
+ let yMin = boundingBoxes[0].min.y;
3727
+ let yMax = boundingBoxes[0].min.y;
3728
+ let bounds;
3729
+ for (var i in boundingBoxes) {
3730
+ bounds = boundingBoxes[i];
3731
+ xMin = Math.min(xMin, bounds.min.x);
3732
+ xMax = Math.max(xMax, bounds.max.x);
3733
+ yMin = Math.min(yMin, bounds.min.y);
3734
+ yMax = Math.max(yMax, bounds.min.y);
3735
+ }
3736
+ return new Bounds(new Vertex(xMin, yMin), new Vertex(xMax, yMax));
3737
+ }
3296
3738
  /**
3297
3739
  * Create a new `Bounds` instance just from `width` and `height`, located at (0,0) or the optionally given origin.
3298
3740
  *
@@ -3331,7 +3773,17 @@ class Bounds {
3331
3773
  * @modified 2022-10-17 The `CubicBezierCurve` class now implements the new `PathSegment` interface.
3332
3774
  * @modified 2023-09-30 Added the function `CubicbezierCurve.getSubCurve(number,number)` – similar to `getSubCurveAt(...)` but with absolute position parameters.
3333
3775
  * @modified 2023-10-07 Added the `trimEnd`, `trimEndAt`, `trimStart`, `trimStartAt` methods.
3334
- * @version 2.8.0
3776
+ * @modified 2025-04-09 Added the `CubicBezierCurve.move` method to match the convention – which just calls `translate`.
3777
+ * @modified 2025-04-09 Modified the `CubicBezierCurve.translate` method: chaning parameter `Vertex` to more generalized `XYCoords`.
3778
+ * @modified 2025-04-13 Changed visibility of `CubicBezierCurve.utils` from 'private' to 'public'.
3779
+ * @modified 2025-04-13 Added helper function `CubicBezierCurve.utils.bezierCoeffs`.
3780
+ * @modified 2025-04-13 Added helper functopn `CubicBezierCurve.utils.sgn(number)` for division safe sign calculation.
3781
+ * @modified 2025-03-13 Class `CubicBezierCurve` is now implementing interface `Intersectable`.
3782
+ * @modified 2025-04-18 Added evaluation method for cubic Bézier curves `CubicBezierCurve.utils.evaluateT`.
3783
+ * @modified 2025-04-18 Refactored method `CubicBezierCurve.getPointAt` to use `evaluateT`.
3784
+ * @modified 2025-04-18 Fixed the `CubicBezierCurve.getBounds` method: now returning the real bounding box. Before it was an approximated one.
3785
+ * @modified 2025-ß4-18 Added helper methods for bounding box calculation `CubucBezierCurve.util.cubicPolyMinMax` and `cubicPoly`.
3786
+ * @version 2.9.0
3335
3787
  *
3336
3788
  * @file CubicBezierCurve
3337
3789
  * @public
@@ -3420,7 +3872,7 @@ class CubicBezierCurve {
3420
3872
  * Translate the whole curve by the given {x,y} amount: moves all four points.
3421
3873
  *
3422
3874
  * @method translate
3423
- * @param {Vertex} amount - The amount to translate this curve by.
3875
+ * @param {XYCoords} amount - The amount to translate this curve by.
3424
3876
  * @instance
3425
3877
  * @memberof CubicBezierCurve
3426
3878
  * @return {CubicBezierCurve} this (for chaining).
@@ -3432,6 +3884,18 @@ class CubicBezierCurve {
3432
3884
  this.endPoint.add(amount);
3433
3885
  return this;
3434
3886
  }
3887
+ /**
3888
+ * Translate the whole curve by the given {x,y} amount: moves all four points.
3889
+ *
3890
+ * @method translate
3891
+ * @param {XYCoords} amount - The amount to translate this curve by.
3892
+ * @instance
3893
+ * @memberof CubicBezierCurve
3894
+ * @return {CubicBezierCurve} this (for chaining).
3895
+ **/
3896
+ move(amount) {
3897
+ return this.translate(amount);
3898
+ }
3435
3899
  /**
3436
3900
  * Reverse this curve, means swapping start- and end-point and swapping
3437
3901
  * start-control- and end-control-point.
@@ -3512,6 +3976,9 @@ class CubicBezierCurve {
3512
3976
  *
3513
3977
  * This function uses a recursive approach by cutting the curve into several linear segments.
3514
3978
  *
3979
+ * @method getClosestT
3980
+ * @instance
3981
+ * @memberof CubicBezierCurve
3515
3982
  * @param {Vertex} p - The point to find the closest position ('t' on the curve).
3516
3983
  * @return {number}
3517
3984
  **/
@@ -3561,27 +4028,35 @@ class CubicBezierCurve {
3561
4028
  tNext: tStart + tDiff * (Math.min(stepCount, minIndex + 1) / stepCount)
3562
4029
  };
3563
4030
  }
4031
+ //--- BEGIN --- Implement interface `IBounded`
3564
4032
  /**
3565
4033
  * Get the bounds of this bezier curve.
3566
4034
  *
3567
4035
  * The bounds are approximated by the underlying segment buffer; the more segment there are,
3568
4036
  * the more accurate will be the returned bounds.
3569
4037
  *
3570
- * @return {Bounds} The bounds of this curve.
4038
+ * @method getClosestT
4039
+ * @instance
4040
+ * @memberof CubicBezierCurve
4041
+ * @return {Bounds} The bounds of this ellipse.
3571
4042
  **/
3572
4043
  getBounds() {
3573
- var min = new Vertex(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY);
3574
- var max = new Vertex(Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY);
3575
- let v;
3576
- for (var i = 0; i < this.segmentCache.length; i++) {
3577
- v = this.segmentCache[i];
3578
- min.x = Math.min(min.x, v.x);
3579
- min.y = Math.min(min.y, v.y);
3580
- max.x = Math.max(max.x, v.x);
3581
- max.y = Math.max(max.y, v.y);
3582
- }
3583
- return new Bounds(min, max);
3584
- }
4044
+ // Thanks to Richard "RM" for the Bézier bounds calculatin
4045
+ // https://jsfiddle.net/SalixAlba/QQnvm/4/
4046
+ const xMinMax = CubicBezierCurve.utils.cubicPolyMinMax(this.startPoint.x, this.startControlPoint.x, this.endControlPoint.x, this.endPoint.x);
4047
+ const xl = xMinMax.min;
4048
+ const xh = xMinMax.max;
4049
+ const yMinMax = CubicBezierCurve.utils.cubicPolyMinMax(this.startPoint.y, this.startControlPoint.y, this.endControlPoint.y, this.endPoint.y);
4050
+ const yl = yMinMax.min;
4051
+ const yh = yMinMax.max;
4052
+ return Bounds.computeFromVertices([
4053
+ { x: xl, y: yl },
4054
+ { x: xl, y: yh },
4055
+ { x: xh, y: yh },
4056
+ { x: xh, y: yl }
4057
+ ]);
4058
+ }
4059
+ //--- END --- Implement interface `IBounded`
3585
4060
  /**
3586
4061
  * Get the start point of the curve.<br>
3587
4062
  * <br>
@@ -3668,14 +4143,19 @@ class CubicBezierCurve {
3668
4143
  **/
3669
4144
  getPointAt(t) {
3670
4145
  // Perform some powerful math magic
3671
- const x = this.startPoint.x * Math.pow(1.0 - t, 3) +
3672
- this.startControlPoint.x * 3 * t * Math.pow(1.0 - t, 2) +
3673
- this.endControlPoint.x * 3 * Math.pow(t, 2) * (1.0 - t) +
3674
- this.endPoint.x * Math.pow(t, 3);
3675
- const y = this.startPoint.y * Math.pow(1.0 - t, 3) +
3676
- this.startControlPoint.y * 3 * t * Math.pow(1.0 - t, 2) +
3677
- this.endControlPoint.y * 3 * Math.pow(t, 2) * (1.0 - t) +
3678
- this.endPoint.y * Math.pow(t, 3);
4146
+ // TODO: cleanup
4147
+ // const x: number =
4148
+ // this.startPoint.x * Math.pow(1.0 - t, 3) +
4149
+ // this.startControlPoint.x * 3 * t * Math.pow(1.0 - t, 2) +
4150
+ // this.endControlPoint.x * 3 * Math.pow(t, 2) * (1.0 - t) +
4151
+ // this.endPoint.x * Math.pow(t, 3);
4152
+ // const y: number =
4153
+ // this.startPoint.y * Math.pow(1.0 - t, 3) +
4154
+ // this.startControlPoint.y * 3 * t * Math.pow(1.0 - t, 2) +
4155
+ // this.endControlPoint.y * 3 * Math.pow(t, 2) * (1.0 - t) +
4156
+ // this.endPoint.y * Math.pow(t, 3);
4157
+ const x = CubicBezierCurve.utils.evaluateT(this.startPoint.x, this.startControlPoint.x, this.endControlPoint.x, this.endPoint.x, t);
4158
+ const y = CubicBezierCurve.utils.evaluateT(this.startPoint.y, this.startControlPoint.y, this.endControlPoint.y, this.endPoint.y, t);
3679
4159
  return new Vertex(x, y);
3680
4160
  }
3681
4161
  /**
@@ -3816,17 +4296,7 @@ class CubicBezierCurve {
3816
4296
  * @return {CubicBezierCurve} The sub curve as a new curve.
3817
4297
  **/
3818
4298
  getSubCurveAt(tStart, tEnd) {
3819
- // const startVec: Vector = new Vector(this.getPointAt(tStart), this.getTangentAt(tStart));
3820
- // const endVec: Vector = new Vector(this.getPointAt(tEnd), this.getTangentAt(tEnd).inv());
3821
- // // Tangents are relative. Make absolute.
3822
- // startVec.b.add(startVec.a);
3823
- // endVec.b.add(endVec.a);
3824
- // // This 'splits' the curve at the given point at t.
3825
- // startVec.scale(0.33333333 * (tEnd - tStart));
3826
- // endVec.scale(0.33333333 * (tEnd - tStart));
3827
- // // Draw the bezier curve
3828
- // // pb.draw.cubicBezier( startVec.a, endVec.a, startVec.b, endVec.b, '#8800ff', 2 );
3829
- // return new CubicBezierCurve(startVec.a, endVec.a, startVec.b, endVec.b);
4299
+ // This 'splits' the curve at the given point at t.
3830
4300
  const subCurbePoints = CubicBezierCurve.utils.getSubCurvePointsAt(this, tStart, tEnd);
3831
4301
  return new CubicBezierCurve(subCurbePoints[0], subCurbePoints[1], subCurbePoints[2], subCurbePoints[3]);
3832
4302
  }
@@ -3918,6 +4388,69 @@ class CubicBezierCurve {
3918
4388
  return this.endControlPoint;
3919
4389
  }
3920
4390
  //---END PathSegment-------------------------
4391
+ //--- BEGIN --- Implement interface `Intersectable`
4392
+ /**
4393
+ * Get all line intersections with this shape.
4394
+ *
4395
+ * This method returns all intersections (as vertices) with this shape. The returned array of vertices is in no specific order.
4396
+ *
4397
+ * @param {VertTuple} line - The line to find intersections with.
4398
+ * @param {boolean} inVectorBoundsOnly - If set to true only intersecion points on the passed vector are returned (located strictly between start and end vertex).
4399
+ * @returns {Array<Vertex>} - An array of all intersections with the shape outline.
4400
+ */
4401
+ lineIntersections(line, inVectorBoundsOnly = false) {
4402
+ const intersectionTs = this.lineIntersectionTs(line);
4403
+ const intersectionPoints = intersectionTs.map((t) => {
4404
+ return this.getPointAt(t);
4405
+ });
4406
+ if (inVectorBoundsOnly) {
4407
+ // const maxDist = line.length();
4408
+ return intersectionPoints.filter((vert) => line.hasPoint(vert, true));
4409
+ }
4410
+ else {
4411
+ return intersectionPoints;
4412
+ }
4413
+ }
4414
+ /**
4415
+ * Get all line intersections of this polygon and their tangents along the shape.
4416
+ *
4417
+ * This method returns all intersection tangents (as vectors) with this shape. The returned array of vectors is in no specific order.
4418
+ *
4419
+ * @param line
4420
+ * @param lineIntersectionTangents
4421
+ * @returns
4422
+ */
4423
+ lineIntersectionTangents(line, inVectorBoundsOnly = false) {
4424
+ const intersectionTs = this.lineIntersectionTs(line);
4425
+ const intersectionTangents = intersectionTs.map((t) => {
4426
+ const startPoint = this.getPointAt(t);
4427
+ const endPoint = this.getTangentAt(t);
4428
+ return new Vector(startPoint, endPoint.add(startPoint));
4429
+ });
4430
+ if (inVectorBoundsOnly) {
4431
+ return intersectionTangents.filter((vec) => line.hasPoint(vec.a, true));
4432
+ }
4433
+ else {
4434
+ return intersectionTangents;
4435
+ }
4436
+ }
4437
+ //--- END --- Implement interface `Intersectable`
4438
+ lineIntersectionTs(line) {
4439
+ var A = line.b.y - line.a.y; // A=y2-y1
4440
+ var B = line.a.x - line.b.x; // B=x1-x2
4441
+ var C = line.a.x * (line.a.y - line.b.y) + line.a.y * (line.b.x - line.a.x); //C=x1*(y1-y2)+y1*(x2-x1)
4442
+ // var bx = bezierCoeffs(px[0], px[1], px[2], px[3]);
4443
+ // var by = bezierCoeffs(py[0], py[1], py[2], py[3]);
4444
+ var bx = CubicBezierCurve.utils.bezierCoeffs(this.startPoint.x, this.startControlPoint.x, this.endControlPoint.x, this.endPoint.x);
4445
+ var by = CubicBezierCurve.utils.bezierCoeffs(this.startPoint.y, this.startControlPoint.y, this.endControlPoint.y, this.endPoint.y);
4446
+ const poly = Array(4);
4447
+ poly[0] = A * bx[0] + B * by[0]; /*t^3*/
4448
+ poly[1] = A * bx[1] + B * by[1]; /*t^2*/
4449
+ poly[2] = A * bx[2] + B * by[2]; /*t*/
4450
+ poly[3] = A * bx[3] + B * by[3] + C; /*1*/
4451
+ var roots = CubicBezierCurve.utils.cubicRoots(poly);
4452
+ return roots.filter((root) => root != -1);
4453
+ }
3921
4454
  /**
3922
4455
  * Check if this and the specified curve are equal.<br>
3923
4456
  * <br>
@@ -4085,6 +4618,71 @@ CubicBezierCurve.END_POINT = 3;
4085
4618
  * Helper utils.
4086
4619
  */
4087
4620
  CubicBezierCurve.utils = {
4621
+ evaluateT: (p0, p1, p2, p3, t) => {
4622
+ return p0 * Math.pow(1.0 - t, 3) +
4623
+ p1 * 3 * t * Math.pow(1.0 - t, 2) +
4624
+ p2 * 3 * Math.pow(t, 2) * (1.0 - t) +
4625
+ p3 * Math.pow(t, 3);
4626
+ },
4627
+ cubicPolyMinMax: (p0, p1, p2, p3) => {
4628
+ // var polyX = CubicBezierCurve.utils.cubicPoly2(
4629
+ // p0, // P[0].X, // bezierCurve.startPoint.x,
4630
+ // p1, // P[1].X, // bezierCurve.startControlPoint.x,
4631
+ // p2, // P[2].X, // bezierCurve.endControlPoint.x,
4632
+ // p3 // P[3].X // bezierCurve.endPoint.x
4633
+ // );
4634
+ // var a = polyX.a;
4635
+ // var b = polyX.b;
4636
+ // var c = polyX.c;
4637
+ // var disc = polyX.b * polyX.b - 4 * polyX.a * polyX.c;
4638
+ var polyX = CubicBezierCurve.utils.cubicPoly(p0, // P[0].X, // bezierCurve.startPoint.x,
4639
+ p1, // P[1].X, // bezierCurve.startControlPoint.x,
4640
+ p2, // P[2].X, // bezierCurve.endControlPoint.x,
4641
+ p3 // P[3].X // bezierCurve.endPoint.x
4642
+ );
4643
+ var a = polyX[0]; // .a;
4644
+ var b = polyX[1]; // .b;
4645
+ polyX[2]; // .c;
4646
+ //alert("a "+a+" "+b+" "+c);
4647
+ // var disc = b * b - 4 * a * c;
4648
+ var disc = polyX[1] * polyX[1] - 4 * polyX[0] * polyX[2];
4649
+ // var polyX = CubicBezierCurve.utils.bezierCoeffs(p3,p2,p1,p0);
4650
+ // var a = polyX[0]; //polyX.a;
4651
+ // var b = polyX[1]; // .b;
4652
+ // var c = polyX[2]; //.c;
4653
+ // var disc = polyX[1] * polyX[1] - 4 * polyX[0] * polyX[2];
4654
+ // var xl = Math.min(bCurve.endPoint.x, bCurve.startPoint.x); // P[0].X;
4655
+ // var xh = Math.max(bCurve.endPoint.x, bCurve.startPoint.x); // P[0].X;
4656
+ var xl = Math.min(p3, p0); // P[0].X;
4657
+ var xh = Math.max(p3, p0); // P[0].X;
4658
+ // if (P[3].X < xl) xl = P[3].X;
4659
+ // if (P[3].X > xh) xh = P[3].X;
4660
+ if (disc >= 0) {
4661
+ var t1 = (-b + Math.sqrt(disc)) / (2 * a);
4662
+ // alert("t1 " + t1);
4663
+ if (t1 > 0 && t1 < 1) {
4664
+ // var x1 = evalBez(PX, t1);
4665
+ // var x1 = bCurve.getPointAt(t1).x;
4666
+ var x1 = CubicBezierCurve.utils.evaluateT(p0, p1, p2, p3, t1); // bCurve.getPointAt(t1).x;
4667
+ if (x1 < xl)
4668
+ xl = x1;
4669
+ if (x1 > xh)
4670
+ xh = x1;
4671
+ }
4672
+ var t2 = (-b - Math.sqrt(disc)) / (2 * a);
4673
+ // alert("t2 " + t2);
4674
+ if (t2 > 0 && t2 < 1) {
4675
+ // var x2 = evalBez(PX, t2);
4676
+ // var x2 = bCurve.getPointAt(t2).x;
4677
+ var x2 = CubicBezierCurve.utils.evaluateT(p0, p1, p2, p3, t2); //
4678
+ if (x2 < xl)
4679
+ xl = x2;
4680
+ if (x2 > xh)
4681
+ xh = x2;
4682
+ }
4683
+ }
4684
+ return { min: xl, max: xh };
4685
+ },
4088
4686
  /**
4089
4687
  * Get the points of a sub curve at the given start end end offsets (values between 0.0 and 1.0).
4090
4688
  *
@@ -4108,6 +4706,94 @@ CubicBezierCurve.utils = {
4108
4706
  startVec.scale(0.33333333 * (tEnd - tStart));
4109
4707
  endVec.scale(0.33333333 * (tEnd - tStart));
4110
4708
  return [startVec.a, endVec.a, startVec.b, endVec.b];
4709
+ },
4710
+ /**
4711
+ * Compute the cubic roots for the given cubic polynomial coefficients.
4712
+ *
4713
+ * Based on
4714
+ * http://mysite.verizon.net/res148h4j/javascript/script_exact_cubic.html#the%20source%20code
4715
+ * Inspired by
4716
+ * https://www.particleincell.com/2013/cubic-line-intersection/
4717
+ * Thanks to Stephan Schmitt and Particle-In-Cell!
4718
+ *
4719
+ * @param poly
4720
+ * @returns
4721
+ */
4722
+ cubicRoots: (poly) => {
4723
+ const a = poly[0];
4724
+ const b = poly[1];
4725
+ const c = poly[2];
4726
+ const d = poly[3];
4727
+ const A = b / a;
4728
+ const B = c / a;
4729
+ const C = d / a;
4730
+ var S, T, Im;
4731
+ const Q = (3 * B - Math.pow(A, 2)) / 9;
4732
+ const R = (9 * A * B - 27 * C - 2 * Math.pow(A, 3)) / 54;
4733
+ const D = Math.pow(Q, 3) + Math.pow(R, 2); // polynomial discriminant
4734
+ const ts = []; // Array();
4735
+ if (D >= 0) {
4736
+ // complex or duplicate roots
4737
+ S = CubicBezierCurve.utils.sgn(R + Math.sqrt(D)) * Math.pow(Math.abs(R + Math.sqrt(D)), 1 / 3);
4738
+ T = CubicBezierCurve.utils.sgn(R - Math.sqrt(D)) * Math.pow(Math.abs(R - Math.sqrt(D)), 1 / 3);
4739
+ ts[0] = -A / 3 + (S + T); // real root
4740
+ ts[1] = -A / 3 - (S + T) / 2; // real part of complex root
4741
+ ts[2] = -A / 3 - (S + T) / 2; // real part of complex root
4742
+ Im = Math.abs((Math.sqrt(3) * (S - T)) / 2); // complex part of root pair
4743
+ // Mark complex roots to be discarded
4744
+ if (Im != 0) {
4745
+ ts[1] = -1;
4746
+ ts[2] = -1;
4747
+ }
4748
+ } // distinct real roots
4749
+ else {
4750
+ const th = Math.acos(R / Math.sqrt(-Math.pow(Q, 3)));
4751
+ ts[0] = 2 * Math.sqrt(-Q) * Math.cos(th / 3) - A / 3;
4752
+ ts[1] = 2 * Math.sqrt(-Q) * Math.cos((th + 2 * Math.PI) / 3) - A / 3;
4753
+ ts[2] = 2 * Math.sqrt(-Q) * Math.cos((th + 4 * Math.PI) / 3) - A / 3;
4754
+ Im = 0.0;
4755
+ }
4756
+ // Discard all t's out of spec and sort the rest
4757
+ return ts.filter((t) => t >= 0 && t <= 1.0).sort();
4758
+ },
4759
+ /**
4760
+ * Compute the Bézier coefficients from the given Bézier point coordinates.
4761
+ *
4762
+ * @param {number} p0 - The start point coordinate.
4763
+ * @param {number} p1 - The start control point coordinate.
4764
+ * @param {number} p2 - The end control point coordinate.
4765
+ * @param {number} p3 - The end point coordinate.
4766
+ * @returns {[number,number,number,number]}
4767
+ */
4768
+ bezierCoeffs: (p0, p1, p2, p3) => {
4769
+ const coeffs = [NaN, NaN, NaN, NaN]; //Array(4);
4770
+ coeffs[0] = -p0 + 3 * p1 + -3 * p2 + p3;
4771
+ coeffs[1] = 3 * p0 - 6 * p1 + 3 * p2;
4772
+ coeffs[2] = -3 * p0 + 3 * p1;
4773
+ coeffs[3] = p0;
4774
+ return coeffs;
4775
+ },
4776
+ /**
4777
+ * Calculate the cubic polynomial coefficients used to find the bounding box.
4778
+ *
4779
+ * @param {number} p0 - The start point coordinate.
4780
+ * @param {number} p1 - The start control point coordinate.
4781
+ * @param {number} p2 - The end control point coordinate.
4782
+ * @param {number} p3 - The end point coordinate.
4783
+ * @returns {[number,number,number]}
4784
+ */
4785
+ cubicPoly: (p0, p1, p2, p3) => {
4786
+ return [
4787
+ 3 * p3 - 9 * p2 + 9 * p1 - 3 * p0,
4788
+ 6 * p0 - 12 * p1 + 6 * p2,
4789
+ 3 * p1 - 3 * p0
4790
+ ];
4791
+ },
4792
+ /**
4793
+ * sign of number, but is division safe: no zero returned :)
4794
+ */
4795
+ sgn(x) {
4796
+ return x < 0.0 ? -1 : 1;
4111
4797
  }
4112
4798
  };
4113
4799
 
@@ -4138,7 +4824,10 @@ CubicBezierCurve.utils = {
4138
4824
  * @modified 2022-02-02 Cleared the `toSVGString` function (deprecated). Use `drawutilssvg` instead.
4139
4825
  * @modified 2023-10-06 Adding the `BezierPath.toPathPoints()` method.
4140
4826
  * @modified 2023-10-07 Adding the `BezierPath.fromCurve(CubicBezierCurve)` static function.
4141
- * @version 2.6.0
4827
+ * @modified 2025-04-09 Added the `BezierPath.move` method to match the convention – which just calls `translate`.
4828
+ * @modified 2025-04-09 Modified the `BezierPath.translate` method: chaning parameter `Vertex` to more generalized `XYCoords`.
4829
+ * @modified 2025-04-14 Class `BezierPath` is now implementing interface `Intersectable`.
4830
+ * @version 2.7.0
4142
4831
  *
4143
4832
  * @file BezierPath
4144
4833
  * @public
@@ -4343,7 +5032,7 @@ class BezierPath {
4343
5032
  * Move the whole bezier path by the given (x,y)-amount.
4344
5033
  *
4345
5034
  * @method translate
4346
- * @param {Vertex} amount - The amount to be added (amount.x and amount.y)
5035
+ * @param {XYCoords} amount - The amount to be added (amount.x and amount.y)
4347
5036
  * to each vertex of the curve.
4348
5037
  * @instance
4349
5038
  * @memberof BezierPath
@@ -4362,6 +5051,19 @@ class BezierPath {
4362
5051
  this.updateArcLengths();
4363
5052
  return this;
4364
5053
  }
5054
+ /**
5055
+ * Move the whole bezier path by the given (x,y)-amount.
5056
+ *
5057
+ * @method move
5058
+ * @param {XYCoords} amount - The amount to be added (amount.x and amount.y)
5059
+ * to each vertex of the curve.
5060
+ * @instance
5061
+ * @memberof BezierPath
5062
+ * @return {BezierPath} this for chaining
5063
+ **/
5064
+ move(amount) {
5065
+ return this.translate(amount);
5066
+ }
4365
5067
  /**
4366
5068
  * Scale the whole bezier path by the given uniform factor.
4367
5069
  *
@@ -4572,6 +5274,36 @@ class BezierPath {
4572
5274
  var relativeU = u - uResult.uPart;
4573
5275
  return bCurve.getPerpendicular(relativeU);
4574
5276
  }
5277
+ //--- BEGIN --- Implement interface `Intersectable`
5278
+ /**
5279
+ * Get all line intersections with this shape.
5280
+ *
5281
+ * This method returns all intersections (as vertices) with this shape. The returned array of vertices is in no specific order.
5282
+ *
5283
+ * @param {VertTuple} line - The line to find intersections with.
5284
+ * @param {boolean} inVectorBoundsOnly - If set to true only intersecion points on the passed vector are returned (located strictly between start and end vertex).
5285
+ * @returns {Array<Vertex>} - An array of all intersections with the shape's outline.
5286
+ */
5287
+ lineIntersections(line, inVectorBoundsOnly = false) {
5288
+ return this.bezierCurves.reduce((accu, curCurve) => {
5289
+ return accu.concat(curCurve.lineIntersections(line, inVectorBoundsOnly));
5290
+ }, []);
5291
+ }
5292
+ /**
5293
+ * Get all line intersections of this polygon and their tangents along the shape.
5294
+ *
5295
+ * This method returns all intersection tangents (as vectors) with this shape. The returned array of vectors is in no specific order.
5296
+ *
5297
+ * @param line
5298
+ * @param lineIntersectionTangents
5299
+ * @returns
5300
+ */
5301
+ lineIntersectionTangents(line, inVectorBoundsOnly = false) {
5302
+ return this.bezierCurves.reduce((accu, curCurve) => {
5303
+ return accu.concat(curCurve.lineIntersectionTangents(line, inVectorBoundsOnly));
5304
+ }, []);
5305
+ }
5306
+ //--- END --- Implement interface `Intersectable`
4575
5307
  /**
4576
5308
  * This is a helper function to locate the curve index for a given
4577
5309
  * absolute path position u.
@@ -4785,6 +5517,7 @@ class BezierPath {
4785
5517
  }
4786
5518
  neighbourCurve.updateArcLengths();
4787
5519
  }
5520
+ //--- BEGIN --- Implement interface `IBounded`
4788
5521
  /**
4789
5522
  * Get the bounds of this Bézier path.
4790
5523
  *
@@ -4806,6 +5539,7 @@ class BezierPath {
4806
5539
  }
4807
5540
  return new Bounds(min, max);
4808
5541
  }
5542
+ //--- END --- Implement interface `IBounded`
4809
5543
  /**
4810
5544
  * Get n 'equally' distributed vertices along this Bézier path.
4811
5545
  *
@@ -5189,6 +5923,10 @@ BezierPath.END_POINT = 3;
5189
5923
  * @modified 2024-03-08 Added the `containsAngle` method.
5190
5924
  * @modified 2024-03-09 Added the `circleSectorIntersection` method to find coherent sector intersections..
5191
5925
  * @modified 2024-03-09 Added the `angleAt` method to determine any angle at some ratio.
5926
+ * @modified 2025-04-02 Adding the `CircleSector.lineIntersections` and `CircleSector.lineIntersectionTangents` and implementing `Intersectable`.
5927
+ * @modified 2025-04-09 Adding the `CircleSector.move()` method.
5928
+ * @modified 2025-04-19 Tweaking the `CircleSector.containsAngle` method: all values (input angle, start- and end- angle) are wrapped into [0,2*PI) now.
5929
+ * @modified 2025-04-19 Class `CircleSector` implements interface `Bounded` now (method `getBounds` added).
5192
5930
  * @version 1.2.0
5193
5931
  **/
5194
5932
  /**
@@ -5220,6 +5958,49 @@ class CircleSector {
5220
5958
  this.startAngle = startAngle;
5221
5959
  this.endAngle = endAngle;
5222
5960
  }
5961
+ //--- BEGIN --- Implement interface `IBounded`
5962
+ /**
5963
+ * Get the bounds of this ellipse.
5964
+ *
5965
+ * The bounds are approximated by the underlying segment buffer; the more segment there are,
5966
+ * the more accurate will be the returned bounds.
5967
+ *
5968
+ * @method getBounds
5969
+ * @instance
5970
+ * @memberof VEllipse
5971
+ * @return {Bounds} The bounds of this curve.
5972
+ **/
5973
+ getBounds() {
5974
+ const _self = this;
5975
+ const circleBounds = this.circle.getBounds();
5976
+ // Calculage angles from east, west, north and south box points and check if they are inside
5977
+ const candidates = [
5978
+ circleBounds.getNorthPoint(),
5979
+ circleBounds.getSouthPoint(),
5980
+ circleBounds.getWestPoint(),
5981
+ circleBounds.getEastPoint()
5982
+ ].filter((point) => {
5983
+ // Check for each candidate points if they are contained in this sector. Drop if not.
5984
+ const angle = new Line(_self.circle.center, point).angle();
5985
+ return _self.containsAngle(angle);
5986
+ });
5987
+ // Compute bounds and inlcude start end end point (they are definitely part of the bounds)
5988
+ return Bounds.computeFromVertices(candidates.concat([this.getStartPoint(), this.getEndPoint()]));
5989
+ }
5990
+ //--- BEGIN --- Implement interface `IBounded`
5991
+ /**
5992
+ * Move the circle sector by the given amount.
5993
+ *
5994
+ * @method move
5995
+ * @param {XYCoords} amount - The amount to move.
5996
+ * @instance
5997
+ * @memberof CircleSector
5998
+ * @return {CircleSector} this for chaining
5999
+ **/
6000
+ move(amount) {
6001
+ this.circle.move(amount);
6002
+ return this;
6003
+ }
5223
6004
  /**
5224
6005
  * Checks wether the given angle (must be inside 0 and PI*2) is contained inside this sector.
5225
6006
  *
@@ -5230,12 +6011,22 @@ class CircleSector {
5230
6011
  * @return {boolean} True if (and only if) this sector contains the given angle.
5231
6012
  */
5232
6013
  containsAngle(angle) {
5233
- if (this.startAngle <= this.endAngle) {
5234
- return angle >= this.startAngle && angle < this.endAngle;
6014
+ var wrappedAngle = geomutils.mapAngleTo2PI(angle);
6015
+ var wrappedStart = geomutils.mapAngleTo2PI(this.startAngle);
6016
+ var wrappedEnd = geomutils.mapAngleTo2PI(this.endAngle);
6017
+ // TODO: cleanup
6018
+ // if (this.startAngle <= this.endAngle) {
6019
+ // return angle >= this.startAngle && angle < this.endAngle;
6020
+ // } else {
6021
+ // // startAngle > endAngle
6022
+ // return angle >= this.startAngle || angle < this.endAngle;
6023
+ // }
6024
+ if (wrappedStart <= wrappedEnd) {
6025
+ return wrappedAngle >= wrappedStart && wrappedAngle < wrappedEnd;
5235
6026
  }
5236
6027
  else {
5237
6028
  // startAngle > endAngle
5238
- return angle >= this.startAngle || angle < this.endAngle;
6029
+ return wrappedAngle >= wrappedStart || wrappedAngle < wrappedEnd;
5239
6030
  }
5240
6031
  }
5241
6032
  /**
@@ -5329,6 +6120,50 @@ class CircleSector {
5329
6120
  }
5330
6121
  return resultSector;
5331
6122
  }
6123
+ //--- BEGIN --- Implement interface `Intersectable`
6124
+ /**
6125
+ * Get the line intersections as vectors with this ellipse.
6126
+ *
6127
+ * @method lineIntersections
6128
+ * @instance
6129
+ * @param {VertTuple<Vector> ray - The line/ray to intersect this ellipse with.
6130
+ * @param {boolean} inVectorBoundsOnly - (default=false) Set to true if only intersections within the vector bounds are of interest.
6131
+ * @returns
6132
+ */
6133
+ lineIntersections(ray, inVectorBoundsOnly = false) {
6134
+ // First get all line intersections from underlying ellipse.
6135
+ const ellipseIntersections = this.circle.lineIntersections(ray, inVectorBoundsOnly);
6136
+ // Drop all intersection points that are not contained in the circle sectors bounds.
6137
+ const tmpLine = new Line(this.circle.center, new Vertex());
6138
+ return ellipseIntersections.filter((intersectionPoint) => {
6139
+ tmpLine.b.set(intersectionPoint);
6140
+ const lineAngle = tmpLine.angle();
6141
+ return this.containsAngle(geomutils.wrapMinMax(lineAngle, 0, Math.PI * 2));
6142
+ });
6143
+ }
6144
+ /**
6145
+ * Get all line intersections of this polygon and their tangents along the shape.
6146
+ *
6147
+ * This method returns all intersection tangents (as vectors) with this shape. The returned array of vectors is in no specific order.
6148
+ *
6149
+ * @param line
6150
+ * @param lineIntersectionTangents
6151
+ * @returns
6152
+ */
6153
+ lineIntersectionTangents(line, inVectorBoundsOnly = false) {
6154
+ // Find the intersections of all lines plus their tangents inside the circle bounds
6155
+ const interSectionPoints = this.lineIntersections(line, inVectorBoundsOnly);
6156
+ return interSectionPoints.map((vert) => {
6157
+ // Calculate angle
6158
+ const lineFromCenter = new Line(this.circle.center, vert);
6159
+ const angle = lineFromCenter.angle();
6160
+ // console.log("angle", (angle / Math.PI) * 180.0);
6161
+ // const angle = Math.random() * Math.PI * 2; // TODO
6162
+ // Calculate tangent at angle
6163
+ return this.circle.tangentAt(angle);
6164
+ });
6165
+ }
6166
+ //--- END --- Implement interface `Intersectable`
5332
6167
  /**
5333
6168
  * This function should invalidate any installed listeners and invalidate this object.
5334
6169
  * After calling this function the object might not hold valid data any more and
@@ -10445,7 +11280,11 @@ class AlloyFinger {
10445
11280
  * @modified 2021-03-19 Added the `VEllipse.rotate` function.
10446
11281
  * @modified 2022-02-02 Added the `destroy` method.
10447
11282
  * @modified 2022-02-02 Cleared the `VEllipse.toSVGString` function (deprecated). Use `drawutilssvg` instead.
10448
- * @version 1.3.0
11283
+ * @modified 2025-03-31 ATTENTION: modified the winding direction of the `tangentAt` method to match with the Circle method. This is a breaking change!
11284
+ * @modified 2025-03-31 Adding the `VEllipse.move(amount: XYCoords)` method.
11285
+ * @modified 2025-04-19 Adding the `VEllipse.getBounds()` method.
11286
+ * @modified 2025-04-24 Adding the `VEllipse.getExtremePoints()` method for calculating minima and maxima.
11287
+ * @version 1.4.0
10449
11288
  *
10450
11289
  * @file VEllipse
10451
11290
  * @fileoverview Ellipses with a center and an x- and a y-axis (stored as a vertex).
@@ -10531,11 +11370,96 @@ class VEllipse {
10531
11370
  * @return {number} The signed vertical radius of this ellipse.
10532
11371
  */
10533
11372
  signedRadiusV() {
10534
- // return Math.abs(this.axis.y - this.center.y);
10535
11373
  // Rotate axis back to origin before calculating radius
10536
- // return Math.abs(new Vertex(this.axis).rotate(-this.rotation,this.center).y - this.center.y);
10537
11374
  return new Vertex(this.axis).rotate(-this.rotation, this.center).y - this.center.y;
10538
11375
  }
11376
+ /**
11377
+ * Get the the minima and maxima (points) of this (rotated) ellipse.
11378
+ *
11379
+ * @method getExtremePoints
11380
+ * @instance
11381
+ * @memberof VEllipse
11382
+ * @return {[Vertex, Vertex, Vertex, Vertex]} Get the the minima and maxima (points) of this (rotated) ellipse.
11383
+ */
11384
+ getExtremePoints() {
11385
+ const a = this.radiusH();
11386
+ const b = this.radiusV();
11387
+ // Calculate t_x values
11388
+ const t_x1 = Math.atan2(-b * Math.sin(this.rotation), a * Math.cos(this.rotation));
11389
+ const t_x2 = t_x1 + Math.PI;
11390
+ // Calculate x values at t_x
11391
+ const x_x1 = this.center.x + a * Math.cos(t_x1) * Math.cos(this.rotation) - b * Math.sin(t_x1) * Math.sin(this.rotation);
11392
+ const y_x1 = this.center.y + a * Math.cos(t_x1) * Math.sin(this.rotation) + b * Math.sin(t_x1) * Math.cos(this.rotation);
11393
+ const x_x2 = this.center.x + a * Math.cos(t_x2) * Math.cos(this.rotation) - b * Math.sin(t_x2) * Math.sin(this.rotation);
11394
+ const y_x2 = this.center.y + a * Math.cos(t_x2) * Math.sin(this.rotation) + b * Math.sin(t_x2) * Math.cos(this.rotation);
11395
+ let x_max, x_min;
11396
+ if (x_x1 > x_x2) {
11397
+ x_max = new Vertex(x_x1, y_x1);
11398
+ x_min = new Vertex(x_x2, y_x2);
11399
+ }
11400
+ else {
11401
+ x_max = new Vertex(x_x2, y_x2);
11402
+ x_min = new Vertex(x_x1, y_x1);
11403
+ }
11404
+ // Calculate t_y values
11405
+ const t_y1 = Math.atan2(b * Math.cos(this.rotation), a * Math.sin(this.rotation));
11406
+ const t_y2 = t_y1 + Math.PI;
11407
+ // Calculate y values at t_y
11408
+ const x_y1 = this.center.x + a * Math.cos(t_y1) * Math.cos(this.rotation) - b * Math.sin(t_y1) * Math.sin(this.rotation);
11409
+ const y_y1 = this.center.y + a * Math.cos(t_y1) * Math.sin(this.rotation) + b * Math.sin(t_y1) * Math.cos(this.rotation);
11410
+ const x_y2 = this.center.x + a * Math.cos(t_y2) * Math.cos(this.rotation) - b * Math.sin(t_y2) * Math.sin(this.rotation);
11411
+ const y_y2 = this.center.y + a * Math.cos(t_y2) * Math.sin(this.rotation) + b * Math.sin(t_y2) * Math.cos(this.rotation);
11412
+ let y_max, y_min;
11413
+ if (y_y1 > y_y2) {
11414
+ y_max = new Vertex(x_y1, y_y1);
11415
+ y_min = new Vertex(x_y2, y_y2);
11416
+ }
11417
+ else {
11418
+ y_max = new Vertex(x_y2, y_y2);
11419
+ y_min = new Vertex(x_y1, y_y1);
11420
+ }
11421
+ return [x_max, x_min, y_max, y_min];
11422
+ }
11423
+ //--- BEGIN --- Implement interface `IBounded`
11424
+ /**
11425
+ * Get the bounds of this ellipse.
11426
+ *
11427
+ * The bounds are approximated by the underlying segment buffer; the more segment there are,
11428
+ * the more accurate will be the returned bounds.
11429
+ *
11430
+ * @method getBounds
11431
+ * @instance
11432
+ * @memberof VEllipse
11433
+ * @return {Bounds} The bounds of this ellipse.
11434
+ **/
11435
+ getBounds() {
11436
+ // Thanks to Cuixiping
11437
+ // https://stackoverflow.com/questions/87734/how-do-you-calculate-the-axis-aligned-bounding-box-of-an-ellipse
11438
+ const r1 = this.radiusH();
11439
+ const r2 = this.radiusV();
11440
+ const ux = r1 * Math.cos(this.rotation);
11441
+ const uy = r1 * Math.sin(this.rotation);
11442
+ const vx = r2 * Math.cos(this.rotation + Math.PI / 2);
11443
+ const vy = r2 * Math.sin(this.rotation + Math.PI / 2);
11444
+ const bbox_halfwidth = Math.sqrt(ux * ux + vx * vx);
11445
+ const bbox_halfheight = Math.sqrt(uy * uy + vy * vy);
11446
+ return new Bounds({ x: this.center.x - bbox_halfwidth, y: this.center.y - bbox_halfheight }, { x: this.center.x + bbox_halfwidth, y: this.center.y + bbox_halfheight });
11447
+ }
11448
+ //--- BEGIN --- Implement interface `IBounded`
11449
+ /**
11450
+ * Move the ellipse by the given amount. This is equivalent by moving the `center` and `axis` points.
11451
+ *
11452
+ * @method move
11453
+ * @param {XYCoords} amount - The amount to move.
11454
+ * @instance
11455
+ * @memberof VEllipse
11456
+ * @return {VEllipse} this for chaining
11457
+ **/
11458
+ move(amount) {
11459
+ this.center.add(amount);
11460
+ this.axis.add(amount);
11461
+ return this;
11462
+ }
10539
11463
  /**
10540
11464
  * Scale this ellipse by the given factor from the center point. The factor will be applied to both radii.
10541
11465
  *
@@ -10593,7 +11517,7 @@ class VEllipse {
10593
11517
  * @param {number=1.0} length - [optional, default=1] The length of the returned vector.
10594
11518
  */
10595
11519
  normalAt(angle, length) {
10596
- const point = this.vertAt(angle);
11520
+ const point = this.vertAt(angle - this.rotation); // HERE IS THE CORRECT BEHAVIOR!
10597
11521
  const foci = this.getFoci();
10598
11522
  // Calculate the angle between [point,focusA] and [point,focusB]
10599
11523
  const angleA = new Line(point, foci[0]).angle();
@@ -10605,12 +11529,13 @@ class VEllipse {
10605
11529
  .addX(50)
10606
11530
  .clone()
10607
11531
  .rotate(Math.PI + centerAngle, point);
10608
- if (this.center.distance(endPointA) < this.center.distance(endPointB)) {
10609
- return new Vector(point, endPointB);
10610
- }
10611
- else {
10612
- return new Vector(point, endPointA);
11532
+ const resultVector = this.center.distance(endPointA) < this.center.distance(endPointB)
11533
+ ? new Vector(point, endPointB)
11534
+ : new Vector(point, endPointA);
11535
+ if (typeof length === "number") {
11536
+ resultVector.setLength(length);
10613
11537
  }
11538
+ return resultVector;
10614
11539
  }
10615
11540
  /**
10616
11541
  * Get the tangent vector at the given angle.
@@ -10629,9 +11554,7 @@ class VEllipse {
10629
11554
  */
10630
11555
  tangentAt(angle, length) {
10631
11556
  const normal = this.normalAt(angle, length);
10632
- // Rotate the normal by 90 degrees, then it is the tangent.
10633
- normal.b.rotate(Math.PI / 2, normal.a);
10634
- return normal;
11557
+ return normal.inv().perp();
10635
11558
  }
10636
11559
  /**
10637
11560
  * Get the perimeter of this ellipse.
@@ -10682,6 +11605,8 @@ class VEllipse {
10682
11605
  /**
10683
11606
  * Get equally distributed points on the outline of this ellipse.
10684
11607
  *
11608
+ * @method getEquidistantVertices
11609
+ * @instance
10685
11610
  * @param {number} pointCount - The number of points.
10686
11611
  * @returns {Array<Vertex>}
10687
11612
  */
@@ -10693,6 +11618,75 @@ class VEllipse {
10693
11618
  }
10694
11619
  return result;
10695
11620
  }
11621
+ //--- BEGIN --- Implement interface `Intersectable`
11622
+ /**
11623
+ * Get the line intersections as vectors with this ellipse.
11624
+ *
11625
+ * @method lineIntersections
11626
+ * @instance
11627
+ * @param {VertTuple<Vector> ray - The line/ray to intersect this ellipse with.
11628
+ * @param {boolean} inVectorBoundsOnly - (default=false) Set to true if only intersections within the vector bounds are of interest.
11629
+ * @returns
11630
+ */
11631
+ lineIntersections(ray, inVectorBoundsOnly = false) {
11632
+ // Question: what happens to extreme versions when ellipse is a line (width or height is zero)?
11633
+ // This would result in a Division_by_Zero exception!
11634
+ // Step A: create clones for operations (keep originals unchanged)
11635
+ const ellipseCopy = this.clone(); // VEllipse
11636
+ const rayCopy = ray.clone(); // Vector
11637
+ // Step B: move both so ellipse's center is located at (0,0)
11638
+ const moveAmount = ellipseCopy.center.clone().inv();
11639
+ ellipseCopy.move(moveAmount);
11640
+ rayCopy.add(moveAmount);
11641
+ // Step C: rotate eclipse backwards it's rotation, so that rotation is zero (0.0).
11642
+ // Rotate together with ray!
11643
+ const rotationAmount = -ellipseCopy.rotation;
11644
+ ellipseCopy.rotate(rotationAmount); // Rotation around (0,0) = center of translated ellipse
11645
+ rayCopy.a.rotate(rotationAmount, ellipseCopy.center);
11646
+ rayCopy.b.rotate(rotationAmount, ellipseCopy.center);
11647
+ // Step D: find x/y factors to use for scaling to transform the ellipse to a circle.
11648
+ // Scale together with vector ray.
11649
+ const radiusH = ellipseCopy.radiusH();
11650
+ const radiusV = ellipseCopy.radiusV();
11651
+ const scalingFactors = radiusH > radiusV ? { x: radiusV / radiusH, y: 1.0 } : { x: 1.0, y: radiusH / radiusV };
11652
+ // Step E: scale ellipse AND ray by calculated factors.
11653
+ ellipseCopy.axis.scaleXY(scalingFactors);
11654
+ rayCopy.a.scaleXY(scalingFactors);
11655
+ rayCopy.b.scaleXY(scalingFactors);
11656
+ // Intermediate result: now the ellipse is transformed to a circle and we can calculate intersections :)
11657
+ // Step F: calculate circle+line intersecions
11658
+ const tmpCircle = new Circle(new Vertex(), ellipseCopy.radiusH()); // radiusH() === radiusV()
11659
+ const intersections = tmpCircle.lineIntersections(rayCopy, inVectorBoundsOnly);
11660
+ // Step G: transform intersecions back to original configuration
11661
+ intersections.forEach(function (intersectionPoint) {
11662
+ // Reverse transformation from above.
11663
+ intersectionPoint.scaleXY({ x: 1 / scalingFactors.x, y: 1 / scalingFactors.y }, ellipseCopy.center);
11664
+ intersectionPoint.rotate(-rotationAmount, ellipseCopy.center);
11665
+ intersectionPoint.sub(moveAmount);
11666
+ });
11667
+ return intersections;
11668
+ }
11669
+ /**
11670
+ * Get all line intersections of this polygon and their tangents along the shape.
11671
+ *
11672
+ * This method returns all intersection tangents (as vectors) with this shape. The returned array of vectors is in no specific order.
11673
+ *
11674
+ * @param line
11675
+ * @param lineIntersectionTangents
11676
+ * @returns
11677
+ */
11678
+ lineIntersectionTangents(line, inVectorBoundsOnly = false) {
11679
+ // Find the intersections of all lines plus their tangents inside the circle bounds
11680
+ const interSectionPoints = this.lineIntersections(line, inVectorBoundsOnly);
11681
+ return interSectionPoints.map((vert) => {
11682
+ // Calculate angle
11683
+ const lineFromCenter = new Line(this.center, vert);
11684
+ const angle = lineFromCenter.angle();
11685
+ // Calculate tangent at angle
11686
+ return this.tangentAt(angle);
11687
+ });
11688
+ }
11689
+ //--- END --- Implement interface `Intersectable`
10696
11690
  /**
10697
11691
  * Convert this ellipse into cubic Bézier curves.
10698
11692
  *
@@ -10705,7 +11699,7 @@ class VEllipse {
10705
11699
  // Math by Luc Maisonobe
10706
11700
  // http://www.spaceroots.org/documents/ellipse/node22.html
10707
11701
  // Note that ellipses with radiusH=0 or radiusV=0 cannot be represented as Bézier curves.
10708
- // Return a single line here (as a Bézier curve)
11702
+ // Return a single line here (as a Bézier curve)?
10709
11703
  // if (Math.abs(this.radiusV()) < 0.00001) {
10710
11704
  // const radiusH = this.radiusH();
10711
11705
  // return [
@@ -10735,10 +11729,10 @@ class VEllipse {
10735
11729
  const radiusV = this.radiusV();
10736
11730
  const curves = [];
10737
11731
  const angles = VEllipse.utils.equidistantVertAngles(radiusH, radiusV, segmentCount);
10738
- let curAngle = angles[0];
11732
+ let curAngle = angles[0] + this.rotation;
10739
11733
  let startPoint = this.vertAt(curAngle);
10740
11734
  for (var i = 0; i < angles.length; i++) {
10741
- let nextAngle = angles[(i + 1) % angles.length];
11735
+ let nextAngle = angles[(i + 1) % angles.length] + this.rotation;
10742
11736
  let endPoint = this.vertAt(nextAngle);
10743
11737
  if (Math.abs(radiusV) < 0.0001 || Math.abs(radiusH) < 0.0001) {
10744
11738
  // Distorted ellipses can only be approximated by linear Bézier segments
@@ -10747,8 +11741,8 @@ class VEllipse {
10747
11741
  curves.push(curve);
10748
11742
  }
10749
11743
  else {
10750
- let startTangent = this.tangentAt(curAngle);
10751
- let endTangent = this.tangentAt(nextAngle);
11744
+ let startTangent = this.tangentAt(curAngle + this.rotation);
11745
+ let endTangent = this.tangentAt(nextAngle + this.rotation);
10752
11746
  // Find intersection (ignore that the result might be null in some extreme cases)
10753
11747
  let intersection = startTangent.intersection(endTangent);
10754
11748
  // What if intersection is undefined?
@@ -10840,7 +11834,14 @@ VEllipse.utils = {
10840
11834
  * @date 2021-02-26
10841
11835
  * @modified 2022-02-02 Added the `destroy` method.
10842
11836
  * @modified 2022-11-01 Tweaked the `endpointToCenterParameters` function to handle negative values, too, without errors.
10843
- * @version 1.1.1
11837
+ * @modified 2025-04-01 Adapting a the `toCubicBezier` calculation to match an underlying change in the vertAt and tangentAt calculation of ellipses (was required to hamonize both methods with circles).
11838
+ * @modified 2025-04-02 Adding `VEllipseSector.containsAngle` method.
11839
+ * @modified 2025-04-02 Adding `VEllipseSector.lineIntersections` and `VEllipseSector.lineIntersectionTangents` and implementing `Intersectable`.
11840
+ * @modified 2025-04-07 Adding value wrapping (0 to TWO_PI) to the `VEllipseSector.containsAngle` method.
11841
+ * @modified 2025-04-09 Adding the `VEllipseSector.move` method.
11842
+ * @modified 2025-04-19 Added the `VEllipseSector.getStartPoint` and `getEndPoint` methods.
11843
+ * @modified 2025-04-23 Added the `VEllipseSector.getBounds` method.
11844
+ * @version 1.2.0
10844
11845
  */
10845
11846
  /**
10846
11847
  * @classdesc A class for elliptic sectors.
@@ -10876,6 +11877,137 @@ class VEllipseSector {
10876
11877
  this.startAngle = geomutils.wrapMinMax(startAngle, 0, Math.PI * 2);
10877
11878
  this.endAngle = geomutils.wrapMinMax(endAngle, 0, Math.PI * 2);
10878
11879
  }
11880
+ /**
11881
+ * Move the ellipse sector by the given amount.
11882
+ *
11883
+ * @method move
11884
+ * @param {XYCoords} amount - The amount to move.
11885
+ * @instance
11886
+ * @memberof VEllipseSector
11887
+ * @return {VEllipseSector} this for chaining
11888
+ **/
11889
+ move(amount) {
11890
+ this.ellipse.move(amount);
11891
+ return this;
11892
+ }
11893
+ /**
11894
+ * Checks wether the given angle (must be inside 0 and PI*2) is contained inside this sector.
11895
+ *
11896
+ * @param {number} angle - The numeric angle to check.
11897
+ * @method containsAngle
11898
+ * @instance
11899
+ * @memberof VEllipseSectpr
11900
+ * @return {boolean} True if (and only if) this sector contains the given angle.
11901
+ */
11902
+ containsAngle(angle) {
11903
+ angle = geomutils.mapAngleTo2PI(angle); // wrapMinMax(angle, 0, Math.PI * 2);
11904
+ const sAngle = geomutils.mapAngleTo2PI(this.startAngle);
11905
+ const eAngle = geomutils.mapAngleTo2PI(this.endAngle);
11906
+ // TODO: cleanup
11907
+ // if (this.startAngle <= this.endAngle) {
11908
+ // return angle >= this.startAngle && angle < this.endAngle;
11909
+ // } else {
11910
+ // // startAngle > endAngle
11911
+ // return angle >= this.startAngle || angle < this.endAngle;
11912
+ // }
11913
+ if (sAngle <= eAngle) {
11914
+ return angle >= sAngle && angle < eAngle;
11915
+ }
11916
+ else {
11917
+ // startAngle > endAngle
11918
+ return angle >= sAngle || angle < eAngle;
11919
+ }
11920
+ }
11921
+ /**
11922
+ * Get the sectors starting point (on the underlying ellipse, located at the start angle).
11923
+ *
11924
+ * @method getStartPoint
11925
+ * @instance
11926
+ * @memberof VEllipseSector
11927
+ * @return {Vertex} The sector's stating point.
11928
+ */
11929
+ getStartPoint() {
11930
+ return this.ellipse.vertAt(this.startAngle);
11931
+ }
11932
+ /**
11933
+ * Get the sectors ending point (on the underlying ellipse, located at the end angle).
11934
+ *
11935
+ * @method getEndPoint
11936
+ * @instance
11937
+ * @memberof VEllipseSector
11938
+ * @return {Vertex} The sector's ending point.
11939
+ */
11940
+ getEndPoint() {
11941
+ return this.ellipse.vertAt(this.endAngle);
11942
+ }
11943
+ //--- BEGIN --- Implement interface `IBounded`
11944
+ /**
11945
+ * Get the bounds of this elliptic sector.
11946
+ *
11947
+ * The bounds are approximated by the underlying segment buffer; the more segment there are,
11948
+ * the more accurate will be the returned bounds.
11949
+ *
11950
+ * @method getBounds
11951
+ * @instance
11952
+ * @memberof VEllipse
11953
+ * @return {Bounds} The bounds of this elliptic sector.
11954
+ **/
11955
+ getBounds() {
11956
+ // Calculage angles from east, west, north and south box points and check if they are inside
11957
+ const extremes = this.ellipse.getExtremePoints();
11958
+ const candidates = extremes.filter(point => {
11959
+ const angle = new Line(this.ellipse.center, point).angle() - this.ellipse.rotation;
11960
+ return this.containsAngle(angle);
11961
+ });
11962
+ return Bounds.computeFromVertices([this.getStartPoint(), this.getEndPoint()].concat(candidates));
11963
+ }
11964
+ //--- BEGIN --- Implement interface `Intersectable`
11965
+ /**
11966
+ * Get the line intersections as vectors with this ellipse.
11967
+ *
11968
+ * @method lineIntersections
11969
+ * @instance
11970
+ * @memberof VEllipseSectpr
11971
+ * @param {VertTuple<Vector>} ray - The line/ray to intersect this ellipse with.
11972
+ * @param {boolean} inVectorBoundsOnly - (default=false) Set to true if only intersections within the vector bounds are of interest.
11973
+ * @returns
11974
+ */
11975
+ lineIntersections(ray, inVectorBoundsOnly = false) {
11976
+ // First get all line intersections from underlying ellipse.
11977
+ const ellipseIntersections = this.ellipse.lineIntersections(ray, inVectorBoundsOnly);
11978
+ // Drop all intersection points that are not contained in the circle sectors bounds.
11979
+ const tmpLine = new Line(this.ellipse.center, new Vertex());
11980
+ return ellipseIntersections.filter((intersectionPoint) => {
11981
+ tmpLine.b.set(intersectionPoint);
11982
+ const lineAngle = tmpLine.angle();
11983
+ return this.containsAngle(lineAngle - this.ellipse.rotation);
11984
+ });
11985
+ }
11986
+ /**
11987
+ * Get all line intersections of this polygon and their tangents along the shape.
11988
+ *
11989
+ * This method returns all intersection tangents (as vectors) with this shape. The returned array of vectors is in no specific order.
11990
+ *
11991
+ * @method lineIntersections
11992
+ * @memberof VEllipseSectpr
11993
+ * @param line
11994
+ * @param lineIntersectionTangents
11995
+ * @returns
11996
+ */
11997
+ lineIntersectionTangents(line, inVectorBoundsOnly = false) {
11998
+ // Find the intersections of all lines plus their tangents inside the circle bounds
11999
+ const interSectionPoints = this.lineIntersections(line, inVectorBoundsOnly);
12000
+ return interSectionPoints.map((vert) => {
12001
+ // Calculate angle
12002
+ const lineFromCenter = new Line(this.ellipse.center, vert);
12003
+ const angle = lineFromCenter.angle();
12004
+ // console.log("angle", (angle / Math.PI) * 180.0);
12005
+ // const angle = Math.random() * Math.PI * 2; // TODO
12006
+ // Calculate tangent at angle
12007
+ return this.ellipse.tangentAt(angle);
12008
+ });
12009
+ }
12010
+ //--- END --- Implement interface `Intersectable`
10879
12011
  /**
10880
12012
  * Convert this elliptic sector into cubic Bézier curves.
10881
12013
  *
@@ -10903,8 +12035,8 @@ class VEllipseSector {
10903
12035
  for (var i = 0; i + 1 < angles.length; i++) {
10904
12036
  let nextAngle = angles[(i + 1) % angles.length];
10905
12037
  let endPoint = this.ellipse.vertAt(nextAngle);
10906
- let startTangent = this.ellipse.tangentAt(curAngle);
10907
- let endTangent = this.ellipse.tangentAt(nextAngle);
12038
+ let startTangent = this.ellipse.tangentAt(curAngle + this.ellipse.rotation);
12039
+ let endTangent = this.ellipse.tangentAt(nextAngle + this.ellipse.rotation);
10908
12040
  // Distorted ellipses can only be approximated by linear Bézier segments
10909
12041
  if (Math.abs(radiusV) < 0.0001 || Math.abs(radiusH) < 0.0001) {
10910
12042
  let diff = startPoint.difference(endPoint);
@@ -11008,20 +12140,20 @@ VEllipseSector.ellipseSectorUtils = {
11008
12140
  equidistantVertAngles: (radiusH, radiusV, startAngle, endAngle, fullEllipsePointCount) => {
11009
12141
  var ellipseAngles = VEllipse.utils.equidistantVertAngles(radiusH, radiusV, fullEllipsePointCount);
11010
12142
  ellipseAngles = ellipseAngles.map((angle) => VEllipseSector.ellipseSectorUtils.normalizeAngle(angle));
11011
- var angleIsInRange = (angle) => {
12143
+ const angleIsInRange = (angle) => {
11012
12144
  if (startAngle < endAngle)
11013
12145
  return angle >= startAngle && angle <= endAngle;
11014
12146
  else
11015
12147
  return angle >= startAngle || (angle <= endAngle && angle >= 0);
11016
12148
  };
11017
12149
  // Drop all angles outside the sector
11018
- var ellipseAngles = ellipseAngles.filter(angleIsInRange);
12150
+ ellipseAngles = ellipseAngles.filter(angleIsInRange);
11019
12151
  // Now we need to sort the angles to the first one in the array is the closest to startAngle.
11020
12152
  // --> find the angle that is closest to the start angle
11021
- var startIndex = VEllipseSector.ellipseSectorUtils.findClosestToStartAngle(startAngle, endAngle, ellipseAngles);
12153
+ const startIndex = VEllipseSector.ellipseSectorUtils.findClosestToStartAngle(startAngle, endAngle, ellipseAngles);
11022
12154
  // Bring all angles into the correct order
11023
12155
  // Idea: use splice or slice here?
11024
- var angles = [];
12156
+ const angles = [];
11025
12157
  for (var i = 0; i < ellipseAngles.length; i++) {
11026
12158
  angles.push(ellipseAngles[(startIndex + i) % ellipseAngles.length]);
11027
12159
  }
@@ -12149,18 +13281,17 @@ class PlotBoilerplate {
12149
13281
  }
12150
13282
  else if (d instanceof Triangle) {
12151
13283
  draw.polyline([d.a, d.b, d.c], false, this.drawConfig.triangle.color, this.drawConfig.triangle.lineWidth);
12152
- if (!this.drawConfig.drawHandlePoints)
13284
+ if (!this.drawConfig.drawHandlePoints) {
12153
13285
  d.a.attr.renderTime = d.b.attr.renderTime = d.c.attr.renderTime = renderTime;
13286
+ }
12154
13287
  }
12155
13288
  else if (d instanceof VEllipse) {
12156
13289
  if (this.drawConfig.drawHandleLines) {
12157
13290
  draw.setCurrentId(`${d.uid}_e0`);
12158
13291
  draw.setCurrentClassName(`${d.className}-v-line`);
12159
- // draw.line( d.center.clone().add(0,d.axis.y-d.center.y), d.axis, '#c8c8c8' );
12160
13292
  draw.handleLine(d.center.clone().add(0, d.signedRadiusV()).rotate(d.rotation, d.center), d.axis); // , "#c8c8c8");
12161
13293
  draw.setCurrentId(`${d.uid}_e1`);
12162
13294
  draw.setCurrentClassName(`${d.className}-h-line`);
12163
- // draw.line( d.center.clone().add(d.axis.x-d.center.x,0), d.axis, '#c8c8c8' );
12164
13295
  draw.handleLine(d.center.clone().add(d.signedRadiusH(), 0).rotate(d.rotation, d.center), d.axis); // , "#c8c8c8");
12165
13296
  }
12166
13297
  draw.setCurrentId(d.uid);
@@ -12176,12 +13307,6 @@ class PlotBoilerplate {
12176
13307
  else if (d instanceof VEllipseSector) {
12177
13308
  draw.setCurrentId(d.uid);
12178
13309
  draw.setCurrentClassName(`${d.className}`);
12179
- /* draw.ellipse( d.center,
12180
- // Math.abs(d.axis.x-d.center.x), Math.abs(d.axis.y-d.center.y),
12181
- d.radiusH(), d.radiusV(),
12182
- this.drawConfig.ellipse.color,
12183
- this.drawConfig.ellipse.lineWidth,
12184
- d.rotation ); */
12185
13310
  const data = VEllipseSector.ellipseSectorUtils.describeSVGArc(d.ellipse.center.x, d.ellipse.center.y, d.ellipse.radiusH(), d.ellipse.radiusV(), d.startAngle, d.endAngle, d.ellipse.rotation, { moveToStart: true });
12186
13311
  draw.path(data, this.drawConfig.ellipseSector.color, this.drawConfig.ellipseSector.lineWidth);
12187
13312
  }