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.
- package/README.md +6 -0
- package/dist/index.esm.js +1390 -265
- package/dist/index.esm.js.map +1 -1
- package/dist/index.esm.min.js +1 -1
- package/dist/index.esm.min.js.map +1 -1
- package/dist/plotboilerplate.browser.min.js +1 -1
- package/dist/plotboilerplate.browser.min.js.map +1 -1
- package/dist/plotboilerplate.js +1264 -99
- package/dist/plotboilerplate.min.js +1 -1
- package/package.json +1 -1
- package/src/cjs/BezierPath.d.ts +41 -5
- package/src/cjs/BezierPath.js +52 -2
- package/src/cjs/BezierPath.js.map +1 -1
- package/src/cjs/Bounds.d.ts +67 -3
- package/src/cjs/Bounds.js +101 -3
- package/src/cjs/Bounds.js.map +1 -1
- package/src/cjs/Circle.d.ts +46 -4
- package/src/cjs/Circle.js +82 -3
- package/src/cjs/Circle.js.map +1 -1
- package/src/cjs/CircleSector.d.ts +51 -2
- package/src/cjs/CircleSector.js +112 -3
- package/src/cjs/CircleSector.js.map +1 -1
- package/src/cjs/CubicBezierCurve.d.ts +113 -7
- package/src/cjs/CubicBezierCurve.js +281 -33
- package/src/cjs/CubicBezierCurve.js.map +1 -1
- package/src/cjs/Line.d.ts +38 -3
- package/src/cjs/Line.js +64 -1
- package/src/cjs/Line.js.map +1 -1
- package/src/cjs/PlotBoilerplate.js +2 -9
- package/src/cjs/PlotBoilerplate.js.map +1 -1
- package/src/cjs/Polygon.d.ts +48 -4
- package/src/cjs/Polygon.js +91 -16
- package/src/cjs/Polygon.js.map +1 -1
- package/src/cjs/Triangle.d.ts +52 -3
- package/src/cjs/Triangle.js +80 -1
- package/src/cjs/Triangle.js.map +1 -1
- package/src/cjs/VEllipse.d.ts +62 -3
- package/src/cjs/VEllipse.js +181 -17
- package/src/cjs/VEllipse.js.map +1 -1
- package/src/cjs/VEllipseSector.d.ts +86 -2
- package/src/cjs/VEllipseSector.js +148 -4
- package/src/cjs/VEllipseSector.js.map +1 -1
- package/src/cjs/Vector.d.ts +12 -2
- package/src/cjs/Vector.js +15 -2
- package/src/cjs/Vector.js.map +1 -1
- package/src/cjs/VertTuple.d.ts +28 -4
- package/src/cjs/VertTuple.js +36 -3
- package/src/cjs/VertTuple.js.map +1 -1
- package/src/cjs/Vertex.d.ts +17 -2
- package/src/cjs/Vertex.js +18 -1
- package/src/cjs/Vertex.js.map +1 -1
- package/src/cjs/interfaces/core.d.ts +39 -0
- package/src/cjs/interfaces/core.js +2 -0
- package/src/cjs/interfaces/core.js.map +1 -1
- package/src/cjs/utils/algorithms/convexPolygonIncircle.d.ts +2 -1
- package/src/cjs/utils/algorithms/convexPolygonIncircle.js +14 -6
- package/src/cjs/utils/algorithms/convexPolygonIncircle.js.map +1 -1
- package/src/cjs/utils/createRandomizedPolygon.d.ts +14 -0
- package/src/cjs/utils/createRandomizedPolygon.js +32 -0
- package/src/cjs/utils/createRandomizedPolygon.js.map +1 -0
- package/src/cjs/utils/helpers/BezierPathInteractionHelper.d.ts +9 -7
- package/src/cjs/utils/helpers/BezierPathInteractionHelper.js +37 -23
- package/src/cjs/utils/helpers/BezierPathInteractionHelper.js.map +1 -1
- package/src/cjs/utils/helpers/CircleSectorHelper.d.ts +10 -1
- package/src/cjs/utils/helpers/CircleSectorHelper.js +19 -20
- package/src/cjs/utils/helpers/CircleSectorHelper.js.map +1 -1
- package/src/cjs/utils/helpers/VEllipseHelper.d.ts +30 -0
- package/src/cjs/utils/helpers/VEllipseHelper.js +86 -0
- package/src/cjs/utils/helpers/VEllipseHelper.js.map +1 -0
- package/src/cjs/utils/helpers/VEllipseSectorHelper.d.ts +42 -3
- package/src/cjs/utils/helpers/VEllipseSectorHelper.js +107 -25
- package/src/cjs/utils/helpers/VEllipseSectorHelper.js.map +1 -1
- package/src/createDemoDrawables.js +3 -1
- package/src/esm/BezierPath.d.ts +41 -5
- package/src/esm/BezierPath.js +50 -2
- package/src/esm/BezierPath.js.map +1 -1
- package/src/esm/Bounds.d.ts +67 -3
- package/src/esm/Bounds.js +101 -3
- package/src/esm/Bounds.js.map +1 -1
- package/src/esm/Circle.d.ts +46 -4
- package/src/esm/Circle.js +79 -3
- package/src/esm/Circle.js.map +1 -1
- package/src/esm/CircleSector.d.ts +51 -2
- package/src/esm/CircleSector.js +108 -3
- package/src/esm/CircleSector.js.map +1 -1
- package/src/esm/CubicBezierCurve.d.ts +113 -7
- package/src/esm/CubicBezierCurve.js +277 -33
- package/src/esm/CubicBezierCurve.js.map +1 -1
- package/src/esm/Line.d.ts +38 -3
- package/src/esm/Line.js +62 -1
- package/src/esm/Line.js.map +1 -1
- package/src/esm/PlotBoilerplate.js +2 -9
- package/src/esm/PlotBoilerplate.js.map +1 -1
- package/src/esm/Polygon.d.ts +48 -4
- package/src/esm/Polygon.js +89 -16
- package/src/esm/Polygon.js.map +1 -1
- package/src/esm/Triangle.d.ts +52 -3
- package/src/esm/Triangle.js +77 -1
- package/src/esm/Triangle.js.map +1 -1
- package/src/esm/VEllipse.d.ts +62 -3
- package/src/esm/VEllipse.js +178 -17
- package/src/esm/VEllipse.js.map +1 -1
- package/src/esm/VEllipseSector.d.ts +86 -2
- package/src/esm/VEllipseSector.js +146 -7
- package/src/esm/VEllipseSector.js.map +1 -1
- package/src/esm/Vector.d.ts +12 -2
- package/src/esm/Vector.js +15 -2
- package/src/esm/Vector.js.map +1 -1
- package/src/esm/VertTuple.d.ts +28 -4
- package/src/esm/VertTuple.js +36 -3
- package/src/esm/VertTuple.js.map +1 -1
- package/src/esm/Vertex.d.ts +17 -2
- package/src/esm/Vertex.js +18 -1
- package/src/esm/Vertex.js.map +1 -1
- package/src/esm/interfaces/core.d.ts +39 -0
- package/src/esm/interfaces/core.js +2 -0
- package/src/esm/interfaces/core.js.map +1 -1
- package/src/esm/utils/algorithms/convexPolygonIncircle.d.ts +2 -1
- package/src/esm/utils/algorithms/convexPolygonIncircle.js +15 -7
- package/src/esm/utils/algorithms/convexPolygonIncircle.js.map +1 -1
- package/src/esm/utils/algorithms/createRandomizedPolygon.d.ts +14 -0
- package/src/esm/utils/algorithms/createRandomizedPolygon.js +28 -0
- package/src/esm/utils/algorithms/createRandomizedPolygon.js.map +1 -0
- package/src/esm/utils/createRandomizedPolygon.d.ts +14 -0
- package/src/esm/utils/createRandomizedPolygon.js +28 -0
- package/src/esm/utils/createRandomizedPolygon.js.map +1 -0
- package/src/esm/utils/helpers/BezierPathInteractionHelper.d.ts +9 -7
- package/src/esm/utils/helpers/BezierPathInteractionHelper.js +35 -22
- package/src/esm/utils/helpers/BezierPathInteractionHelper.js.map +1 -1
- package/src/esm/utils/helpers/CircleSectorHelper.d.ts +10 -1
- package/src/esm/utils/helpers/CircleSectorHelper.js +19 -20
- package/src/esm/utils/helpers/CircleSectorHelper.js.map +1 -1
- package/src/esm/utils/helpers/VEllipseHelper.d.ts +30 -0
- package/src/esm/utils/helpers/VEllipseHelper.js +81 -0
- package/src/esm/utils/helpers/VEllipseHelper.js.map +1 -0
- package/src/esm/utils/helpers/VEllipseSectorHelper.d.ts +42 -3
- package/src/esm/utils/helpers/VEllipseSectorHelper.js +106 -24
- package/src/esm/utils/helpers/VEllipseSectorHelper.js.map +1 -1
- package/src/ts/BezierPath.ts +58 -5
- package/src/ts/Bounds.ts +110 -6
- package/src/ts/Circle.ts +87 -5
- package/src/ts/CircleSector.ts +115 -5
- package/src/ts/CubicBezierCurve.ts +332 -42
- package/src/ts/Line.ts +66 -3
- package/src/ts/PlotBoilerplate.ts +3 -9
- package/src/ts/Polygon.ts +103 -22
- package/src/ts/Triangle.ts +85 -3
- package/src/ts/VEllipse.ts +214 -19
- package/src/ts/VEllipseSector.ts +154 -8
- package/src/ts/Vector.ts +16 -2
- package/src/ts/VertTuple.ts +39 -4
- package/src/ts/Vertex.ts +20 -2
- package/src/ts/interfaces/core.ts +42 -0
- package/src/ts/utils/algorithms/convexPolygonIncircle.ts +82 -78
- package/src/ts/utils/createRandomizedPolygon.ts +31 -0
- package/src/ts/utils/helpers/BezierPathInteractionHelper.ts +45 -29
- package/src/ts/utils/helpers/CircleSectorHelper.ts +115 -125
- package/src/ts/utils/helpers/VEllipseHelper.ts +113 -0
- 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
|
-
* @
|
|
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 {
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
2029
|
+
* Reverse this vertex tuple: a becomes b, and b becomes a.
|
|
2030
|
+
* This operation is in-place.
|
|
2022
2031
|
*
|
|
2023
|
-
* @method
|
|
2024
|
-
* @
|
|
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 {
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
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
|
-
* @
|
|
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<
|
|
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
|
-
* @
|
|
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 {
|
|
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
|
-
* @
|
|
4038
|
+
* @method getClosestT
|
|
4039
|
+
* @instance
|
|
4040
|
+
* @memberof CubicBezierCurve
|
|
4041
|
+
* @return {Bounds} The bounds of this ellipse.
|
|
3571
4042
|
**/
|
|
3572
4043
|
getBounds() {
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
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
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
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
|
-
//
|
|
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
|
-
* @
|
|
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 {
|
|
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
|
-
|
|
5234
|
-
|
|
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
|
|
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
|
-
* @
|
|
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
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
|
|
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
|
-
|
|
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
|
-
* @
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|