plotboilerplate 1.23.3 → 1.25.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 +19 -1
- package/dist/index.esm.js +1684 -394
- 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 +12450 -11120
- package/dist/plotboilerplate.min.js +1 -1
- package/package.json +9 -5
- 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.d.ts +37 -6
- package/src/cjs/PlotBoilerplate.js +224 -125
- package/src/cjs/PlotBoilerplate.js.map +1 -1
- package/src/cjs/Polygon.d.ts +68 -5
- package/src/cjs/Polygon.js +143 -22
- 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 +31 -4
- package/src/cjs/Vertex.js +35 -1
- package/src/cjs/Vertex.js.map +1 -1
- package/src/cjs/interfaces/core.d.ts +67 -0
- package/src/cjs/interfaces/core.js +4 -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/datastructures/Color.d.ts +147 -1
- package/src/cjs/utils/datastructures/Color.js +385 -41
- package/src/cjs/utils/datastructures/Color.js.map +1 -1
- package/src/cjs/utils/datastructures/ColorGradient.d.ts +79 -0
- package/src/cjs/utils/datastructures/ColorGradient.js +171 -0
- package/src/cjs/utils/datastructures/ColorGradient.js.map +1 -0
- package/src/cjs/utils/dom/components/ColorGradientPicker.d.ts +245 -0
- package/src/cjs/utils/dom/components/ColorGradientPicker.js +667 -0
- package/src/cjs/utils/dom/components/ColorGradientPicker.js.map +1 -0
- package/src/cjs/utils/dom/components/ColorGradientPickerJSX.d.ts +129 -0
- package/src/cjs/utils/dom/components/ColorGradientPickerJSX.js +520 -0
- package/src/cjs/utils/dom/components/ColorGradientPickerJSX.js.map +1 -0
- package/src/cjs/utils/dom/components/ColorGradientSelector.d.ts +117 -0
- package/src/cjs/utils/dom/components/ColorGradientSelector.js +325 -0
- package/src/cjs/utils/dom/components/ColorGradientSelector.js.map +1 -0
- package/src/cjs/utils/dom/components/Test.d.ts +7 -0
- package/src/cjs/utils/dom/components/Test.js +30 -0
- package/src/cjs/utils/dom/components/Test.js.map +1 -0
- package/src/cjs/utils/dom/guiSizeToggler.js +1 -1
- package/src/cjs/utils/dom/guiSizeToggler.js.map +1 -1
- package/src/cjs/utils/helpers/BezierPathInteractionHelper.d.ts +16 -8
- package/src/cjs/utils/helpers/BezierPathInteractionHelper.js +49 -28
- package/src/cjs/utils/helpers/BezierPathInteractionHelper.js.map +1 -1
- package/src/cjs/utils/helpers/CircleHelper.d.ts +10 -1
- package/src/cjs/utils/helpers/CircleHelper.js +13 -2
- package/src/cjs/utils/helpers/CircleHelper.js.map +1 -1
- package/src/cjs/utils/helpers/CircleSectorHelper.d.ts +11 -2
- package/src/cjs/utils/helpers/CircleSectorHelper.js +20 -20
- package/src/cjs/utils/helpers/CircleSectorHelper.js.map +1 -1
- package/src/cjs/utils/helpers/TriangleHelper.d.ts +57 -0
- package/src/cjs/utils/helpers/TriangleHelper.js +83 -0
- package/src/cjs/utils/helpers/TriangleHelper.js.map +1 -0
- package/src/cjs/utils/helpers/VEllipseHelper.d.ts +32 -0
- package/src/cjs/utils/helpers/VEllipseHelper.js +88 -0
- package/src/cjs/utils/helpers/VEllipseHelper.js.map +1 -0
- package/src/cjs/utils/helpers/VEllipseSectorHelper.d.ts +44 -4
- package/src/cjs/utils/helpers/VEllipseSectorHelper.js +108 -25
- package/src/cjs/utils/helpers/VEllipseSectorHelper.js.map +1 -1
- package/src/cjs/utils/parsers/LinearColorGradientParser.d.ts +78 -0
- package/src/cjs/utils/parsers/LinearColorGradientParser.js +224 -0
- package/src/cjs/utils/parsers/LinearColorGradientParser.js.map +1 -0
- package/src/createDemoDrawables.js +4 -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.d.ts +37 -6
- package/src/esm/PlotBoilerplate.js +224 -125
- package/src/esm/PlotBoilerplate.js.map +1 -1
- package/src/esm/Polygon.d.ts +68 -5
- package/src/esm/Polygon.js +138 -22
- 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 +31 -4
- package/src/esm/Vertex.js +35 -1
- package/src/esm/Vertex.js.map +1 -1
- package/src/esm/interfaces/core.d.ts +67 -0
- package/src/esm/interfaces/core.js +4 -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/datastructures/Color.d.ts +147 -1
- package/src/esm/utils/datastructures/Color.js +386 -42
- package/src/esm/utils/datastructures/Color.js.map +1 -1
- package/src/esm/utils/datastructures/ColorGradient.d.ts +79 -0
- package/src/esm/utils/datastructures/ColorGradient.js +166 -0
- package/src/esm/utils/datastructures/ColorGradient.js.map +1 -0
- package/src/esm/utils/dom/components/ColorGradientPicker.d.ts +245 -0
- package/src/esm/utils/dom/components/ColorGradientPicker.js +732 -0
- package/src/esm/utils/dom/components/ColorGradientPicker.js.map +1 -0
- package/src/esm/utils/dom/components/ColorGradientPickerJSX.d.ts +129 -0
- package/src/esm/utils/dom/components/ColorGradientPickerJSX.js +582 -0
- package/src/esm/utils/dom/components/ColorGradientPickerJSX.js.map +1 -0
- package/src/esm/utils/dom/components/ColorGradientSelector.d.ts +117 -0
- package/src/esm/utils/dom/components/ColorGradientSelector.js +332 -0
- package/src/esm/utils/dom/components/ColorGradientSelector.js.map +1 -0
- package/src/esm/utils/dom/components/Test.d.ts +7 -0
- package/src/esm/utils/dom/components/Test.js +26 -0
- package/src/esm/utils/dom/components/Test.js.map +1 -0
- package/src/esm/utils/dom/guiSizeToggler.js +1 -1
- package/src/esm/utils/dom/guiSizeToggler.js.map +1 -1
- package/src/esm/utils/helpers/BezierPathInteractionHelper.d.ts +16 -8
- package/src/esm/utils/helpers/BezierPathInteractionHelper.js +47 -27
- package/src/esm/utils/helpers/BezierPathInteractionHelper.js.map +1 -1
- package/src/esm/utils/helpers/CircleHelper.d.ts +10 -1
- package/src/esm/utils/helpers/CircleHelper.js +13 -2
- package/src/esm/utils/helpers/CircleHelper.js.map +1 -1
- package/src/esm/utils/helpers/CircleSectorHelper.d.ts +11 -2
- package/src/esm/utils/helpers/CircleSectorHelper.js +20 -20
- package/src/esm/utils/helpers/CircleSectorHelper.js.map +1 -1
- package/src/esm/utils/helpers/TriangleHelper.d.ts +57 -0
- package/src/esm/utils/helpers/TriangleHelper.js +78 -0
- package/src/esm/utils/helpers/TriangleHelper.js.map +1 -0
- package/src/esm/utils/helpers/VEllipseHelper.d.ts +32 -0
- package/src/esm/utils/helpers/VEllipseHelper.js +83 -0
- package/src/esm/utils/helpers/VEllipseHelper.js.map +1 -0
- package/src/esm/utils/helpers/VEllipseSectorHelper.d.ts +44 -4
- package/src/esm/utils/helpers/VEllipseSectorHelper.js +107 -24
- package/src/esm/utils/helpers/VEllipseSectorHelper.js.map +1 -1
- package/src/esm/utils/parsers/LinearColorGradientParser.d.ts +78 -0
- package/src/esm/utils/parsers/LinearColorGradientParser.js +217 -0
- package/src/esm/utils/parsers/LinearColorGradientParser.js.map +1 -0
- package/src/external.d.ts +9 -0
- 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 +236 -114
- package/src/ts/Polygon.ts +164 -29
- 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 +40 -4
- package/src/ts/interfaces/core.ts +73 -0
- package/src/ts/utils/algorithms/convexPolygonIncircle.ts +82 -78
- package/src/ts/utils/createRandomizedPolygon.ts +31 -0
- package/src/ts/utils/datastructures/Color.ts +423 -44
- package/src/ts/utils/datastructures/ColorGradient.ts +196 -0
- package/src/ts/utils/dom/components/ColorGradientPicker.tsx +877 -0
- package/src/ts/utils/dom/components/ColorGradientSelector.tsx +445 -0
- package/src/ts/utils/dom/components/Test.tsx +44 -0
- package/src/ts/utils/dom/components/_ColorGradientPicker_raw.ts_ +392 -0
- package/src/ts/utils/dom/guiSizeToggler.ts +1 -1
- package/src/ts/utils/helpers/BezierPathInteractionHelper.ts +61 -35
- package/src/ts/utils/helpers/CircleHelper.ts +17 -3
- package/src/ts/utils/helpers/CircleSectorHelper.ts +118 -126
- package/src/ts/utils/helpers/TriangleHelper.ts +102 -0
- package/src/ts/utils/helpers/VEllipseHelper.ts +115 -0
- package/src/ts/utils/helpers/VEllipseSectorHelper.ts +144 -26
- package/src/ts/utils/parsers/LinearColorGradientParser.ts +297 -0
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;
|
|
608
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)));
|
|
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.intersection);
|
|
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.intersection);
|
|
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,10 @@ 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
|
+
* @modified 2025-05-07 Class `Vertex` is now implementing interface `IBounded` (to meet convention).
|
|
1297
|
+
* @version 2.11.0
|
|
1307
1298
|
*
|
|
1308
1299
|
* @file Vertex
|
|
1309
1300
|
* @public
|
|
@@ -1494,6 +1485,21 @@ class Vertex {
|
|
|
1494
1485
|
}
|
|
1495
1486
|
return this;
|
|
1496
1487
|
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Move this point by the given amount.
|
|
1490
|
+
*
|
|
1491
|
+
* This method just calls `add(amount).
|
|
1492
|
+
*
|
|
1493
|
+
*
|
|
1494
|
+
* @method move
|
|
1495
|
+
* @param {Vertex} amount - The amount to move this vertex.
|
|
1496
|
+
* @return {Vertex} this - For chaining.
|
|
1497
|
+
* @instance
|
|
1498
|
+
* @memberof Vertex
|
|
1499
|
+
*/
|
|
1500
|
+
move(amount) {
|
|
1501
|
+
return this.add(amount);
|
|
1502
|
+
}
|
|
1497
1503
|
/**
|
|
1498
1504
|
* Add the passed amounts to the x- and y- components of this vertex.
|
|
1499
1505
|
*
|
|
@@ -1833,6 +1839,21 @@ class Vertex {
|
|
|
1833
1839
|
this.y = Math.abs(this.y);
|
|
1834
1840
|
return this;
|
|
1835
1841
|
}
|
|
1842
|
+
//--- BEGIN --- Implement interface `IBounded`
|
|
1843
|
+
/**
|
|
1844
|
+
* Get the bounding box (bounds) of this Vertex.
|
|
1845
|
+
* This is just by convention – any vertex is just a position without any useful with or height (0).
|
|
1846
|
+
*
|
|
1847
|
+
* @method getBounds
|
|
1848
|
+
* @instance
|
|
1849
|
+
* @memberof Vertex
|
|
1850
|
+
* @return {Bounds} The rectangular bounds of this Vertex (width and height are zero).
|
|
1851
|
+
**/
|
|
1852
|
+
getBounds() {
|
|
1853
|
+
// return Bounds.computeFromVertices([this.a, this.b, this.c]);
|
|
1854
|
+
return Bounds.computeFromVertices([this]);
|
|
1855
|
+
}
|
|
1856
|
+
//--- END --- Implement interface `IBounded`
|
|
1836
1857
|
/**
|
|
1837
1858
|
* Get a string representation of this vertex.
|
|
1838
1859
|
*
|
|
@@ -1943,7 +1964,10 @@ Vertex.utils = {
|
|
|
1943
1964
|
* @modified 2024-09-10 Chaging the first param of `pointDistance` from `Vertex` to less strict type `XYCoords`. This should not break anything.
|
|
1944
1965
|
* @modified 2024-09-10 Adding the optional `epsilon` param to the `hasPoint` method.
|
|
1945
1966
|
* @modified 2024-12-02 Added the `epsilon` param to the `colinear` method. Default is 1.0e-6.
|
|
1946
|
-
* @
|
|
1967
|
+
* @modified 2025-03-31 Added the `VertTuple.revert` method.
|
|
1968
|
+
* @modified 2025-04-15 Changed param of `VertTuple.moveTo` method from `Vertex` to `XYCoords`.
|
|
1969
|
+
* @modified 2025-04-15 Added method `VertTuple.move` method.
|
|
1970
|
+
* @version 1.4.0
|
|
1947
1971
|
*/
|
|
1948
1972
|
/**
|
|
1949
1973
|
* @classdesc An abstract base classes for vertex tuple constructs, like Lines or Vectors.
|
|
@@ -2008,15 +2032,31 @@ class VertTuple {
|
|
|
2008
2032
|
*
|
|
2009
2033
|
* @method add
|
|
2010
2034
|
* @param {XYCoords} amount The amount (x,y) to add.
|
|
2011
|
-
* @return {Line} this
|
|
2012
2035
|
* @instance
|
|
2013
2036
|
* @memberof VertTuple
|
|
2037
|
+
* @return {VertTuple<T>} this
|
|
2014
2038
|
**/
|
|
2015
2039
|
add(amount) {
|
|
2016
2040
|
this.a.add(amount);
|
|
2017
2041
|
this.b.add(amount);
|
|
2018
2042
|
return this;
|
|
2019
2043
|
}
|
|
2044
|
+
/**
|
|
2045
|
+
* Reverse this vertex tuple: a becomes b, and b becomes a.
|
|
2046
|
+
* This operation is in-place.
|
|
2047
|
+
*
|
|
2048
|
+
* @method add
|
|
2049
|
+
* @param {XYCoords} amount The amount (x,y) to add.
|
|
2050
|
+
* @instance
|
|
2051
|
+
* @memberof VertTuple
|
|
2052
|
+
* @return {VertTuple<T>} this
|
|
2053
|
+
*/
|
|
2054
|
+
revert() {
|
|
2055
|
+
const tmp = this.a;
|
|
2056
|
+
this.a = this.b;
|
|
2057
|
+
this.b = tmp;
|
|
2058
|
+
return this;
|
|
2059
|
+
}
|
|
2020
2060
|
/**
|
|
2021
2061
|
* Normalize this line (set to length 1).
|
|
2022
2062
|
*
|
|
@@ -2046,7 +2086,7 @@ class VertTuple {
|
|
|
2046
2086
|
* Move this line to a new location.
|
|
2047
2087
|
*
|
|
2048
2088
|
* @method moveTo
|
|
2049
|
-
* @param {
|
|
2089
|
+
* @param {XYCoords} newA - The new desired location of 'a'. Vertex 'b' will be moved, too.
|
|
2050
2090
|
* @return {VertTuple} this
|
|
2051
2091
|
* @instance
|
|
2052
2092
|
* @memberof VertTuple
|
|
@@ -2057,6 +2097,20 @@ class VertTuple {
|
|
|
2057
2097
|
this.b.add(diff);
|
|
2058
2098
|
return this;
|
|
2059
2099
|
}
|
|
2100
|
+
/**
|
|
2101
|
+
* Move this line by the given amount
|
|
2102
|
+
*
|
|
2103
|
+
* @method move
|
|
2104
|
+
* @param {XYCoords} amount - The amount to move both point of this tuple.
|
|
2105
|
+
* @return {VertTuple} this
|
|
2106
|
+
* @instance
|
|
2107
|
+
* @memberof VertTuple
|
|
2108
|
+
**/
|
|
2109
|
+
move(amount) {
|
|
2110
|
+
this.a.add(amount);
|
|
2111
|
+
this.b.add(amount);
|
|
2112
|
+
return this;
|
|
2113
|
+
}
|
|
2060
2114
|
/**
|
|
2061
2115
|
* Get the angle between this and the passed line (in radians).
|
|
2062
2116
|
*
|
|
@@ -2221,18 +2275,193 @@ class VertTuple {
|
|
|
2221
2275
|
* After calling this function the object might not hold valid data any more and
|
|
2222
2276
|
* should not be used.
|
|
2223
2277
|
*/
|
|
2224
|
-
destroy() {
|
|
2225
|
-
this.a.destroy();
|
|
2226
|
-
this.b.destroy();
|
|
2227
|
-
this.isDestroyed = true;
|
|
2278
|
+
destroy() {
|
|
2279
|
+
this.a.destroy();
|
|
2280
|
+
this.b.destroy();
|
|
2281
|
+
this.isDestroyed = true;
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
/**
|
|
2285
|
+
* @private
|
|
2286
|
+
**/
|
|
2287
|
+
VertTuple.vtutils = {
|
|
2288
|
+
dist2: (v, w) => {
|
|
2289
|
+
return (v.x - w.x) * (v.x - w.x) + (v.y - w.y) * (v.y - w.y);
|
|
2290
|
+
}
|
|
2291
|
+
};
|
|
2292
|
+
|
|
2293
|
+
/**
|
|
2294
|
+
* @author Ikaros Kappler
|
|
2295
|
+
* @date 2019-01-30
|
|
2296
|
+
* @modified 2019-02-23 Added the toSVGString function, overriding Line.toSVGString.
|
|
2297
|
+
* @modified 2019-03-20 Added JSDoc tags.
|
|
2298
|
+
* @modified 2019-04-19 Added the clone function (overriding Line.clone()).
|
|
2299
|
+
* @modified 2019-09-02 Added the Vector.perp() function.
|
|
2300
|
+
* @modified 2019-09-02 Added the Vector.inverse() function.
|
|
2301
|
+
* @modified 2019-12-04 Added the Vector.inv() function.
|
|
2302
|
+
* @modified 2020-03-23 Ported to Typescript from JS.
|
|
2303
|
+
* @modified 2021-01-20 Added UID.
|
|
2304
|
+
* @modified 2022-02-02 Added the `destroy` method.
|
|
2305
|
+
* @modified 2022-02-02 Cleared the `Vector.toSVGString` function (deprecated). Use `drawutilssvg` instead.
|
|
2306
|
+
* @modified 2022-10-25 Added the `getOrthogonal` method.
|
|
2307
|
+
* @modified 2025-04-14 Added the `Vector.rotate(number)` method.
|
|
2308
|
+
* @version 1.6.0
|
|
2309
|
+
*
|
|
2310
|
+
* @file Vector
|
|
2311
|
+
* @public
|
|
2312
|
+
**/
|
|
2313
|
+
/**
|
|
2314
|
+
* @classdesc A vector (Vertex,Vertex) is a line with a visible direction.<br>
|
|
2315
|
+
* <br>
|
|
2316
|
+
* Vectors are drawn with an arrow at their end point.<br>
|
|
2317
|
+
* <b>The Vector class extends the Line class.</b>
|
|
2318
|
+
*
|
|
2319
|
+
* @requires VertTuple
|
|
2320
|
+
* @requires Vertex
|
|
2321
|
+
**/
|
|
2322
|
+
class Vector extends VertTuple {
|
|
2323
|
+
/**
|
|
2324
|
+
* The constructor.
|
|
2325
|
+
*
|
|
2326
|
+
* @constructor
|
|
2327
|
+
* @name Vector
|
|
2328
|
+
* @extends Line
|
|
2329
|
+
* @param {Vertex} vertA - The start vertex of the vector.
|
|
2330
|
+
* @param {Vertex} vertB - The end vertex of the vector.
|
|
2331
|
+
**/
|
|
2332
|
+
constructor(vertA, vertB) {
|
|
2333
|
+
super(vertA, vertB, (a, b) => new Vector(a, b));
|
|
2334
|
+
/**
|
|
2335
|
+
* Required to generate proper CSS classes and other class related IDs.
|
|
2336
|
+
**/
|
|
2337
|
+
this.className = "Vector";
|
|
2338
|
+
}
|
|
2339
|
+
/**
|
|
2340
|
+
* Get the perpendicular of this vector which is located at a.
|
|
2341
|
+
*
|
|
2342
|
+
* @return {Vector} A new vector being the perpendicular of this vector sitting on a.
|
|
2343
|
+
**/
|
|
2344
|
+
perp() {
|
|
2345
|
+
var v = this.clone();
|
|
2346
|
+
v.sub(this.a);
|
|
2347
|
+
v = new Vector(new Vertex(), new Vertex(-v.b.y, v.b.x));
|
|
2348
|
+
v.a.add(this.a);
|
|
2349
|
+
v.b.add(this.a);
|
|
2350
|
+
return v;
|
|
2351
|
+
}
|
|
2352
|
+
/**
|
|
2353
|
+
* The inverse of a vector is a vector with the same magnitude but oppose direction.
|
|
2354
|
+
*
|
|
2355
|
+
* Please not that the origin of this vector changes here: a->b becomes b->a.
|
|
2356
|
+
*
|
|
2357
|
+
* @return {Vector}
|
|
2358
|
+
**/
|
|
2359
|
+
inverse() {
|
|
2360
|
+
var tmp = this.a;
|
|
2361
|
+
this.a = this.b;
|
|
2362
|
+
this.b = tmp;
|
|
2363
|
+
return this;
|
|
2364
|
+
}
|
|
2365
|
+
/**
|
|
2366
|
+
* This function computes the inverse of the vector, which means 'a' stays untouched.
|
|
2367
|
+
*
|
|
2368
|
+
* @return {Vector} this for chaining.
|
|
2369
|
+
**/
|
|
2370
|
+
inv() {
|
|
2371
|
+
this.b.x = this.a.x - (this.b.x - this.a.x);
|
|
2372
|
+
this.b.y = this.a.y - (this.b.y - this.a.y);
|
|
2373
|
+
return this;
|
|
2374
|
+
}
|
|
2375
|
+
/**
|
|
2376
|
+
* Get the intersection if this vector and the specified vector.
|
|
2377
|
+
*
|
|
2378
|
+
* @method intersection
|
|
2379
|
+
* @param {Vector} line The second vector.
|
|
2380
|
+
* @return {Vertex} The intersection (may lie outside the end-points).
|
|
2381
|
+
* @instance
|
|
2382
|
+
* @memberof Line
|
|
2383
|
+
**/
|
|
2384
|
+
intersection(line) {
|
|
2385
|
+
var denominator = this.denominator(line);
|
|
2386
|
+
if (denominator == 0)
|
|
2387
|
+
return null;
|
|
2388
|
+
var a = this.a.y - line.a.y;
|
|
2389
|
+
var b = this.a.x - line.a.x;
|
|
2390
|
+
var numerator1 = (line.b.x - line.a.x) * a - (line.b.y - line.a.y) * b;
|
|
2391
|
+
var numerator2 = (this.b.x - this.a.x) * a - (this.b.y - this.a.y) * b;
|
|
2392
|
+
a = numerator1 / denominator; // NaN if parallel lines
|
|
2393
|
+
b = numerator2 / denominator;
|
|
2394
|
+
// TODO:
|
|
2395
|
+
// FOR A VECTOR THE LINE-INTERSECTION MUST BE ON BOTH VECTORS
|
|
2396
|
+
// if we cast these lines infinitely in both directions, they intersect here:
|
|
2397
|
+
return new Vertex(this.a.x + a * (this.b.x - this.a.x), this.a.y + a * (this.b.y - this.a.y));
|
|
2398
|
+
}
|
|
2399
|
+
/**
|
|
2400
|
+
* Get the orthogonal "vector" of this vector (rotated by 90° clockwise).
|
|
2401
|
+
*
|
|
2402
|
+
* @name getOrthogonal
|
|
2403
|
+
* @method getOrthogonal
|
|
2404
|
+
* @return {Vector} A new vector with the same length that stands on this vector's point a.
|
|
2405
|
+
* @instance
|
|
2406
|
+
* @memberof Vector
|
|
2407
|
+
**/
|
|
2408
|
+
getOrthogonal() {
|
|
2409
|
+
// Orthogonal of vector (0,0)->(x,y) is (0,0)->(-y,x)
|
|
2410
|
+
const linePoint = this.a.clone();
|
|
2411
|
+
const startPoint = this.b.clone().sub(this.a);
|
|
2412
|
+
const tmp = startPoint.x;
|
|
2413
|
+
startPoint.x = -startPoint.y;
|
|
2414
|
+
startPoint.y = tmp;
|
|
2415
|
+
return new Vector(linePoint, startPoint.add(this.a));
|
|
2416
|
+
}
|
|
2417
|
+
/**
|
|
2418
|
+
* Rotate this vector by the given angle around the first point `a`.
|
|
2419
|
+
*
|
|
2420
|
+
* @name rotate
|
|
2421
|
+
* @method rotate
|
|
2422
|
+
* @return {Vector} this - for chaining.
|
|
2423
|
+
* @instance
|
|
2424
|
+
* @memberof Vector
|
|
2425
|
+
*/
|
|
2426
|
+
rotate(angle) {
|
|
2427
|
+
this.b.rotate(angle, this.a);
|
|
2428
|
+
return this;
|
|
2228
2429
|
}
|
|
2229
2430
|
}
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2431
|
+
Vector.utils = {
|
|
2432
|
+
/**
|
|
2433
|
+
* Generate a four-point arrow head, starting at the vector end minus the
|
|
2434
|
+
* arrow head length.
|
|
2435
|
+
*
|
|
2436
|
+
* The first vertex in the returned array is guaranteed to be the located
|
|
2437
|
+
* at the vector line end minus the arrow head length.
|
|
2438
|
+
*
|
|
2439
|
+
*
|
|
2440
|
+
* Due to performance all params are required.
|
|
2441
|
+
*
|
|
2442
|
+
* The params scaleX and scaleY are required for the case that the scaling is not uniform (x and y
|
|
2443
|
+
* scaling different). Arrow heads should not look distored on non-uniform scaling.
|
|
2444
|
+
*
|
|
2445
|
+
* If unsure use 1.0 for scaleX and scaleY (=no distortion).
|
|
2446
|
+
* For headlen use 8, it's a good arrow head size.
|
|
2447
|
+
*
|
|
2448
|
+
* Example:
|
|
2449
|
+
* buildArrowHead( new Vertex(0,0), new Vertex(50,100), 8, 1.0, 1.0 )
|
|
2450
|
+
*
|
|
2451
|
+
* @param {XYCoords} zA - The start vertex of the vector to calculate the arrow head for.
|
|
2452
|
+
* @param {XYCoords} zB - The end vertex of the vector.
|
|
2453
|
+
* @param {number} headlen - The length of the arrow head (along the vector direction. A good value is 12).
|
|
2454
|
+
* @param {number} scaleX - The horizontal scaling during draw.
|
|
2455
|
+
* @param {number} scaleY - the vertical scaling during draw.
|
|
2456
|
+
**/
|
|
2457
|
+
buildArrowHead: (zA, zB, headlen, scaleX, scaleY) => {
|
|
2458
|
+
const angle = Math.atan2((zB.y - zA.y) * scaleY, (zB.x - zA.x) * scaleX);
|
|
2459
|
+
const vertices = [];
|
|
2460
|
+
vertices.push(new Vertex(zB.x * scaleX - headlen * Math.cos(angle), zB.y * scaleY - headlen * Math.sin(angle)));
|
|
2461
|
+
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)));
|
|
2462
|
+
vertices.push(new Vertex(zB.x * scaleX, zB.y * scaleY));
|
|
2463
|
+
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)));
|
|
2464
|
+
return vertices;
|
|
2236
2465
|
}
|
|
2237
2466
|
};
|
|
2238
2467
|
|
|
@@ -2254,7 +2483,9 @@ VertTuple.vtutils = {
|
|
|
2254
2483
|
* @modified 2022-10-09 Changed the actual return value of the `intersection` function to null (was undefined before).
|
|
2255
2484
|
* @modified 2022-10-17 Adding these methods from the `PathSegment` interface: getStartPoint, getEndPoint, revert.
|
|
2256
2485
|
* @modified 2023-09-25 Changed param type of `intersection()` from Line to VertTuple.
|
|
2257
|
-
* @
|
|
2486
|
+
* @modified 2025-04-15 Class `Line` now implements interface `Intersectable`.
|
|
2487
|
+
* @modified 2025-04-16 Class `Line` now implements interface `IBounded`.
|
|
2488
|
+
* @version 2.4.0
|
|
2258
2489
|
*
|
|
2259
2490
|
* @file Line
|
|
2260
2491
|
* @public
|
|
@@ -2313,6 +2544,19 @@ class Line extends VertTuple {
|
|
|
2313
2544
|
// if we cast these lines infinitely in both directions, they intersect here:
|
|
2314
2545
|
return new Vertex(x, y);
|
|
2315
2546
|
}
|
|
2547
|
+
//--- BEGIN --- Implement interface `IBounded`
|
|
2548
|
+
/**
|
|
2549
|
+
* Get the bounding box (bounds) of this Line.
|
|
2550
|
+
*
|
|
2551
|
+
* @method getBounds
|
|
2552
|
+
* @instance
|
|
2553
|
+
* @memberof Line
|
|
2554
|
+
* @return {Bounds} The rectangular bounds of this Line.
|
|
2555
|
+
**/
|
|
2556
|
+
getBounds() {
|
|
2557
|
+
return Bounds.computeFromVertices([this.a, this.b]);
|
|
2558
|
+
}
|
|
2559
|
+
//--- END --- Implement interface `IBounded`
|
|
2316
2560
|
//--- Implement PathSegment ---
|
|
2317
2561
|
/**
|
|
2318
2562
|
* Get the start point of this path segment.
|
|
@@ -2367,6 +2611,50 @@ class Line extends VertTuple {
|
|
|
2367
2611
|
this.b = tmp;
|
|
2368
2612
|
return this;
|
|
2369
2613
|
}
|
|
2614
|
+
//--- END Implement PathSegment ---
|
|
2615
|
+
//--- BEGIN --- Implement interface `Intersectable`
|
|
2616
|
+
/**
|
|
2617
|
+
* Get all line intersections with this polygon.
|
|
2618
|
+
*
|
|
2619
|
+
* This method returns all intersections (as vertices) with this shape. The returned array of vertices is in no specific order.
|
|
2620
|
+
*
|
|
2621
|
+
* See demo `47-closest-vector-projection-on-polygon` for how it works.
|
|
2622
|
+
*
|
|
2623
|
+
* @param {VertTuple} line - The line to find intersections with.
|
|
2624
|
+
* @param {boolean} inVectorBoundsOnly - If set to true only intersecion points on the passed vector are returned (located strictly between start and end vertex).
|
|
2625
|
+
* @returns {Array<Vertex>} - An array of all intersections within the polygon bounds.
|
|
2626
|
+
*/
|
|
2627
|
+
lineIntersections(line, inVectorBoundsOnly = false) {
|
|
2628
|
+
// Find the intersections of all lines inside the edge bounds
|
|
2629
|
+
const intersection = this.intersection(line);
|
|
2630
|
+
if (!intersection) {
|
|
2631
|
+
return []; // Both lines parallel
|
|
2632
|
+
}
|
|
2633
|
+
if (this.hasPoint(intersection, true) && (!inVectorBoundsOnly || line.hasPoint(intersection, inVectorBoundsOnly))) {
|
|
2634
|
+
return [intersection];
|
|
2635
|
+
}
|
|
2636
|
+
else {
|
|
2637
|
+
return [];
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
/**
|
|
2641
|
+
* Get all line intersections of this polygon and their tangents along the shape.
|
|
2642
|
+
*
|
|
2643
|
+
* This method returns all intersection tangents (as vectors) with this shape. The returned array of vectors is in no specific order.
|
|
2644
|
+
*
|
|
2645
|
+
* @param line
|
|
2646
|
+
* @param inVectorBoundsOnly
|
|
2647
|
+
* @returns
|
|
2648
|
+
*/
|
|
2649
|
+
lineIntersectionTangents(line, inVectorBoundsOnly = false) {
|
|
2650
|
+
// Find the intersection tangents of all lines inside the edge bounds
|
|
2651
|
+
const intersections = this.lineIntersections(line, inVectorBoundsOnly);
|
|
2652
|
+
if (intersections.length === 0) {
|
|
2653
|
+
return [];
|
|
2654
|
+
}
|
|
2655
|
+
const intrsctn = intersections[0];
|
|
2656
|
+
return [new Vector(this.a.clone(), this.b.clone()).moveTo(intrsctn)];
|
|
2657
|
+
}
|
|
2370
2658
|
}
|
|
2371
2659
|
|
|
2372
2660
|
/**
|
|
@@ -2403,7 +2691,13 @@ class Line extends VertTuple {
|
|
|
2403
2691
|
* @modified 2024-10-30 Added the `getEdges` method.
|
|
2404
2692
|
* @modified 2024-12-02 Added the `elimitateColinearEdges` method.
|
|
2405
2693
|
* @modified 2025-02-12 Added the `containsVerts` method to test multiple vertices for containment.
|
|
2406
|
-
* @
|
|
2694
|
+
* @modified 2025-03-28 Added the `Polygon.utils.locateLineIntersecion` static helper method.
|
|
2695
|
+
* @modified 2025-03-28 Added the `Polygon.lineIntersectionTangents` method.
|
|
2696
|
+
* @modified 2025-04-09 Added the `Polygon.getCentroid` method.
|
|
2697
|
+
* @modified 2025-05-16 Class `Polygon` now implements `IBounded`.
|
|
2698
|
+
* @modified 2025-05-20 Tweaking `Polygon.getInnerAngleAt` and `Polygo.isAngleAcute` to handle indices out of array bounds as well.
|
|
2699
|
+
* @modified 2025-06-07 Adding `Polygon.closestLineIntersectionIndex` to determine line intersections plus detected edge index.
|
|
2700
|
+
* @version 1.16.0
|
|
2407
2701
|
*
|
|
2408
2702
|
* @file Polygon
|
|
2409
2703
|
* @public
|
|
@@ -2510,7 +2804,7 @@ class Polygon {
|
|
|
2510
2804
|
* @returns {boolean} `true` is angle is acute, `false` is obtuse.
|
|
2511
2805
|
*/
|
|
2512
2806
|
getInnerAngleAt(vertIndex) {
|
|
2513
|
-
const p2 = this.vertices[vertIndex];
|
|
2807
|
+
const p2 = this.vertices[vertIndex % this.vertices.length];
|
|
2514
2808
|
const p1 = this.vertices[(vertIndex + this.vertices.length - 1) % this.vertices.length].clone();
|
|
2515
2809
|
const p3 = this.vertices[(vertIndex + 1) % this.vertices.length].clone();
|
|
2516
2810
|
// See
|
|
@@ -2546,7 +2840,7 @@ class Polygon {
|
|
|
2546
2840
|
*/
|
|
2547
2841
|
isAngleAcute(vertIndex) {
|
|
2548
2842
|
const A = this.vertices[(vertIndex + this.vertices.length - 1) % this.vertices.length].clone();
|
|
2549
|
-
const B = this.vertices[vertIndex];
|
|
2843
|
+
const B = this.vertices[vertIndex % this.vertices.length];
|
|
2550
2844
|
const C = this.vertices[(vertIndex + 1) % this.vertices.length].clone();
|
|
2551
2845
|
// Find local winding number for triangle A B C
|
|
2552
2846
|
const windingNumber = Triangle.utils.determinant(A, B, C);
|
|
@@ -2612,8 +2906,9 @@ class Polygon {
|
|
|
2612
2906
|
let xi = this.vertices[i].x, yi = this.vertices[i].y;
|
|
2613
2907
|
let xj = this.vertices[j].x, yj = this.vertices[j].y;
|
|
2614
2908
|
var intersect = yi > vert.y != yj > vert.y && vert.x < ((xj - xi) * (vert.y - yi)) / (yj - yi) + xi;
|
|
2615
|
-
if (intersect)
|
|
2909
|
+
if (intersect) {
|
|
2616
2910
|
inside = !inside;
|
|
2911
|
+
}
|
|
2617
2912
|
}
|
|
2618
2913
|
return inside;
|
|
2619
2914
|
}
|
|
@@ -2775,9 +3070,44 @@ class Polygon {
|
|
|
2775
3070
|
center.y /= this.vertices.length;
|
|
2776
3071
|
return center;
|
|
2777
3072
|
}
|
|
3073
|
+
/**
|
|
3074
|
+
* Get centroid.
|
|
3075
|
+
* Centroids define the barycenter of any non self-intersecting convex polygon.
|
|
3076
|
+
*
|
|
3077
|
+
* If the polygon is self intersecting or non konvex then the barycenter is not well defined.
|
|
3078
|
+
*
|
|
3079
|
+
* https://mathworld.wolfram.com/PolygonCentroid.html
|
|
3080
|
+
*
|
|
3081
|
+
* @method getCentroid
|
|
3082
|
+
* @instance
|
|
3083
|
+
* @memberof Polygon
|
|
3084
|
+
* @returns {Vertex|null}
|
|
3085
|
+
*/
|
|
3086
|
+
getCentroid() {
|
|
3087
|
+
if (this.vertices.length === 0) {
|
|
3088
|
+
return null;
|
|
3089
|
+
}
|
|
3090
|
+
const center = new Vertex(0.0, 0.0);
|
|
3091
|
+
const n = this.vertices.length;
|
|
3092
|
+
for (var i = 0; i < n; i++) {
|
|
3093
|
+
// center.add(this.vertices[i]);
|
|
3094
|
+
const cur = this.vertices[i];
|
|
3095
|
+
const next = this.vertices[(i + 1) % n];
|
|
3096
|
+
var factor = cur.x * next.y - next.x * cur.y;
|
|
3097
|
+
center.x += (cur.x + next.x) * factor;
|
|
3098
|
+
center.y += (cur.y + next.y) * factor;
|
|
3099
|
+
}
|
|
3100
|
+
const area = this.area();
|
|
3101
|
+
center.x *= 1 / (6 * area);
|
|
3102
|
+
center.y *= 1 / (6 * area);
|
|
3103
|
+
return center;
|
|
3104
|
+
}
|
|
3105
|
+
//--- BEGIN --- Implement interface `Intersectable`
|
|
2778
3106
|
/**
|
|
2779
3107
|
* Get all line intersections with this polygon.
|
|
2780
3108
|
*
|
|
3109
|
+
* This method returns all intersections (as vertices) with this shape. The returned array of vertices is in no specific order.
|
|
3110
|
+
*
|
|
2781
3111
|
* See demo `47-closest-vector-projection-on-polygon` for how it works.
|
|
2782
3112
|
*
|
|
2783
3113
|
* @param {VertTuple} line - The line to find intersections with.
|
|
@@ -2786,20 +3116,45 @@ class Polygon {
|
|
|
2786
3116
|
*/
|
|
2787
3117
|
lineIntersections(line, inVectorBoundsOnly = false) {
|
|
2788
3118
|
// Find the intersections of all lines inside the edge bounds
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
3119
|
+
return Polygon.utils
|
|
3120
|
+
.locateLineIntersecion(line, this.vertices, this.isOpen, inVectorBoundsOnly)
|
|
3121
|
+
.map(intersectionTuple => intersectionTuple.intersection);
|
|
3122
|
+
}
|
|
3123
|
+
/**
|
|
3124
|
+
* Get all line intersections of this polygon and their tangents along the shape.
|
|
3125
|
+
*
|
|
3126
|
+
* This method returns all intersection tangents (as vectors) with this shape. The returned array of vectors is in no specific order.
|
|
3127
|
+
*
|
|
3128
|
+
* @param line
|
|
3129
|
+
* @param inVectorBoundsOnly
|
|
3130
|
+
* @returns
|
|
3131
|
+
*/
|
|
3132
|
+
lineIntersectionTangents(line, inVectorBoundsOnly = false) {
|
|
3133
|
+
// Find the intersection tangents of all lines inside the edge bounds
|
|
3134
|
+
return Polygon.utils.locateLineIntersecion(line, this.vertices, this.isOpen, inVectorBoundsOnly).map(intersectionTuple => {
|
|
3135
|
+
const polyLine = this.getEdgeAt(intersectionTuple.edgeIndex);
|
|
3136
|
+
return new Vector(polyLine.a.clone(), polyLine.b.clone()).moveTo(intersectionTuple.intersection);
|
|
3137
|
+
});
|
|
3138
|
+
}
|
|
3139
|
+
//--- END --- Implement interface `Intersectable`
|
|
3140
|
+
/**
|
|
3141
|
+
* Get all line intersections of this polygon and their tangents along the shape.
|
|
3142
|
+
*
|
|
3143
|
+
* This method returns all intersection tangents (as vectors) with this shape. The returned array of vectors is in no specific order.
|
|
3144
|
+
*
|
|
3145
|
+
* @param line
|
|
3146
|
+
* @param inVectorBoundsOnly
|
|
3147
|
+
* @returns
|
|
3148
|
+
*/
|
|
3149
|
+
lineIntersectionTangentsIndices(line, inVectorBoundsOnly = false) {
|
|
3150
|
+
// Find the intersection tangents of all lines inside the edge bounds
|
|
3151
|
+
return Polygon.utils.locateLineIntersecion(line, this.vertices, this.isOpen, inVectorBoundsOnly).map(intersectionTuple => {
|
|
3152
|
+
const polyLine = this.getEdgeAt(intersectionTuple.edgeIndex);
|
|
3153
|
+
return {
|
|
3154
|
+
intersection: new Vector(polyLine.a.clone(), polyLine.b.clone()).moveTo(intersectionTuple.intersection),
|
|
3155
|
+
edgeIndex: intersectionTuple.edgeIndex
|
|
3156
|
+
};
|
|
3157
|
+
});
|
|
2803
3158
|
}
|
|
2804
3159
|
/**
|
|
2805
3160
|
* Get the closest line-polygon-intersection point (closest the line point A).
|
|
@@ -2808,9 +3163,27 @@ class Polygon {
|
|
|
2808
3163
|
*
|
|
2809
3164
|
* @param {VertTuple} line - The line to find intersections with.
|
|
2810
3165
|
* @param {boolean} inVectorBoundsOnly - If set to true only intersecion points on the passed vector are considered (located strictly between start and end vertex).
|
|
2811
|
-
* @returns {
|
|
3166
|
+
* @returns {Vertex | null} - The intersection point within the polygon bounds.
|
|
2812
3167
|
*/
|
|
2813
3168
|
closestLineIntersection(line, inVectorBoundsOnly = false) {
|
|
3169
|
+
var closestInterSectionIndex = this.closestLineIntersectionIndex(line, inVectorBoundsOnly);
|
|
3170
|
+
if (closestInterSectionIndex) {
|
|
3171
|
+
return closestInterSectionIndex.intersection;
|
|
3172
|
+
}
|
|
3173
|
+
else {
|
|
3174
|
+
return null;
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
/**
|
|
3178
|
+
* Get the closest line-polygon-intersection point (closest the line point A) plus the edge index..
|
|
3179
|
+
*
|
|
3180
|
+
* See demo `63-measure-angles-on-polygon` for how it works.
|
|
3181
|
+
*
|
|
3182
|
+
* @param {VertTuple} line - The line to find intersections with.
|
|
3183
|
+
* @param {boolean} inVectorBoundsOnly - If set to true only intersecion points on the passed vector are considered (located strictly between start and end vertex).
|
|
3184
|
+
* @returns {PolygonIntersectionTuple| null} - A pair containing the intersection point and the affected polygon edge index.
|
|
3185
|
+
*/
|
|
3186
|
+
closestLineIntersectionIndex(line, inVectorBoundsOnly = false) {
|
|
2814
3187
|
const allIntersections = this.lineIntersections(line, inVectorBoundsOnly);
|
|
2815
3188
|
if (allIntersections.length <= 0) {
|
|
2816
3189
|
// Empty polygon -> no intersections
|
|
@@ -2818,17 +3191,20 @@ class Polygon {
|
|
|
2818
3191
|
}
|
|
2819
3192
|
// Find the closest intersection
|
|
2820
3193
|
let closestIntersection = new Vertex(Number.MAX_VALUE, Number.MAX_VALUE);
|
|
3194
|
+
let closestInterSectionIndex = -1;
|
|
2821
3195
|
let curDist = Number.MAX_VALUE;
|
|
2822
|
-
for (var i
|
|
3196
|
+
for (var i = 0; i < allIntersections.length; i++) {
|
|
2823
3197
|
const curVert = allIntersections[i];
|
|
2824
3198
|
const dist = curVert.distance(line.a);
|
|
2825
3199
|
if (dist < curDist) {
|
|
2826
3200
|
// && line.hasPoint(curVert)) {
|
|
2827
3201
|
curDist = dist;
|
|
2828
3202
|
closestIntersection = curVert;
|
|
3203
|
+
closestInterSectionIndex = i;
|
|
2829
3204
|
}
|
|
2830
3205
|
}
|
|
2831
|
-
return closestIntersection;
|
|
3206
|
+
// return [closestIntersection, closestInterSectionIndex];
|
|
3207
|
+
return { edgeIndex: closestInterSectionIndex, intersection: closestIntersection };
|
|
2832
3208
|
}
|
|
2833
3209
|
/**
|
|
2834
3210
|
* Construct a new polygon from this polygon with more vertices on each edge. The
|
|
@@ -2901,6 +3277,7 @@ class Polygon {
|
|
|
2901
3277
|
}
|
|
2902
3278
|
return result;
|
|
2903
3279
|
}
|
|
3280
|
+
//--- BEGIN --- Implement interface `IBounded`
|
|
2904
3281
|
/**
|
|
2905
3282
|
* Get the bounding box (bounds) of this polygon.
|
|
2906
3283
|
*
|
|
@@ -2912,6 +3289,7 @@ class Polygon {
|
|
|
2912
3289
|
getBounds() {
|
|
2913
3290
|
return Bounds.computeFromVertices(this.vertices);
|
|
2914
3291
|
}
|
|
3292
|
+
//--- END --- Implement interface `IBounded`
|
|
2915
3293
|
/**
|
|
2916
3294
|
* Create a deep copy of this polygon.
|
|
2917
3295
|
*
|
|
@@ -2966,7 +3344,6 @@ class Polygon {
|
|
|
2966
3344
|
// -> delete all vertices in between
|
|
2967
3345
|
if (j - i > 2) {
|
|
2968
3346
|
// Means: there have been 'colinear vertices' in between
|
|
2969
|
-
// console.log("Splice", "i", i, "j", j, i + 1, j - i - 1);
|
|
2970
3347
|
verts.splice(i + 1, j - i - 2);
|
|
2971
3348
|
}
|
|
2972
3349
|
i++;
|
|
@@ -3163,6 +3540,32 @@ Polygon.utils = {
|
|
|
3163
3540
|
sum += (vertices[j].x - vertices[i].x) * (vertices[i].y + vertices[j].y);
|
|
3164
3541
|
}
|
|
3165
3542
|
return sum;
|
|
3543
|
+
},
|
|
3544
|
+
/**
|
|
3545
|
+
* Find intersections of a line with a polygon (vertices).
|
|
3546
|
+
*
|
|
3547
|
+
* @param {VertTuple<any>} line - The line to find intersections with.
|
|
3548
|
+
* @param {Array<Vertex>} vertices - The polygon's vertices.
|
|
3549
|
+
* @param {boolean} isOpen - True if the polygon is open, false otherwise.
|
|
3550
|
+
* @param {boolean} inVectorBoundsOnly - If only intersections in strict vector bounds should be returned.
|
|
3551
|
+
* @returns
|
|
3552
|
+
*/
|
|
3553
|
+
locateLineIntersecion(line, vertices, isOpen, inVectorBoundsOnly) {
|
|
3554
|
+
// Find the intersections of all lines inside the edge bounds
|
|
3555
|
+
const intersectionPoints = [];
|
|
3556
|
+
var n = isOpen ? vertices.length - 1 : vertices.length;
|
|
3557
|
+
for (var i = 0; i < n; i++) {
|
|
3558
|
+
const polyLine = new Line(vertices[i % n], vertices[(i + 1) % n]);
|
|
3559
|
+
const intersection = polyLine.intersection(line);
|
|
3560
|
+
// true => only inside bounds
|
|
3561
|
+
// ignore last edge if open
|
|
3562
|
+
if (intersection !== null &&
|
|
3563
|
+
polyLine.hasPoint(intersection, true) &&
|
|
3564
|
+
(!inVectorBoundsOnly || line.hasPoint(intersection, inVectorBoundsOnly))) {
|
|
3565
|
+
intersectionPoints.push({ edgeIndex: i, intersection: intersection });
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
return intersectionPoints;
|
|
3166
3569
|
}
|
|
3167
3570
|
};
|
|
3168
3571
|
|
|
@@ -3177,7 +3580,11 @@ Polygon.utils = {
|
|
|
3177
3580
|
* @modified 2022-10-09 Added the `fromDimension` function.
|
|
3178
3581
|
* @modified 2022-11-28 Added the `clone` method.
|
|
3179
3582
|
* @modified 2023-09-29 Added the `randomPoint` method.
|
|
3180
|
-
* @
|
|
3583
|
+
* @modified 2025-03-23 Added the `getMinDimension` and `getMaxDimension` methods.
|
|
3584
|
+
* @modified 2025-04-18 Change parameter type in `Bounds.computeFromVertices` from `Vertex` to more general `XYCoords`.
|
|
3585
|
+
* @modified 2025-04-19 Added methods to `Bounds` class: `getNorthPoint`, `getSouthPoint`, `getEastPoint` and `getWestPoint`.
|
|
3586
|
+
* @modified 2025-04-26 Added static method `Bounds.computeFromBoundsSet` to calculate containing bounds for a set of bounding boxes.
|
|
3587
|
+
* @version 1.8.0
|
|
3181
3588
|
**/
|
|
3182
3589
|
/**
|
|
3183
3590
|
* @classdesc A bounds class with min and max values. Implementing IBounds.
|
|
@@ -3201,6 +3608,54 @@ class Bounds {
|
|
|
3201
3608
|
this.width = max.x - min.x;
|
|
3202
3609
|
this.height = max.y - min.y;
|
|
3203
3610
|
}
|
|
3611
|
+
/**
|
|
3612
|
+
* Get the center point of the north bound.
|
|
3613
|
+
*
|
|
3614
|
+
* @method getNorthPoint
|
|
3615
|
+
* @instance
|
|
3616
|
+
* @memberof Bounds
|
|
3617
|
+
* @return {Vertex} The "northmost" centered point of this bounding box.
|
|
3618
|
+
*/
|
|
3619
|
+
getNorthPoint() {
|
|
3620
|
+
return new Vertex(this.min.x + this.width / 2.0, this.min.y);
|
|
3621
|
+
}
|
|
3622
|
+
;
|
|
3623
|
+
/**
|
|
3624
|
+
* Get the center point of the south bound.
|
|
3625
|
+
*
|
|
3626
|
+
* @method getNorthPoint
|
|
3627
|
+
* @instance
|
|
3628
|
+
* @memberof Bounds
|
|
3629
|
+
* @return {Vertex} The "southhmost" centered point of this bounding box.
|
|
3630
|
+
*/
|
|
3631
|
+
getSouthPoint() {
|
|
3632
|
+
return new Vertex(this.min.x + this.width / 2.0, this.max.y);
|
|
3633
|
+
}
|
|
3634
|
+
;
|
|
3635
|
+
/**
|
|
3636
|
+
* Get the center point of the west bound.
|
|
3637
|
+
*
|
|
3638
|
+
* @method getWestPoint
|
|
3639
|
+
* @instance
|
|
3640
|
+
* @memberof Bounds
|
|
3641
|
+
* @return {Vertex} The "westhmost" centered point of this bounding box.
|
|
3642
|
+
*/
|
|
3643
|
+
getWestPoint() {
|
|
3644
|
+
return new Vertex(this.min.x, this.min.y + this.height / 2.0);
|
|
3645
|
+
}
|
|
3646
|
+
;
|
|
3647
|
+
/**
|
|
3648
|
+
* Get the center point of the east bound.
|
|
3649
|
+
*
|
|
3650
|
+
* @method getEastPoint
|
|
3651
|
+
* @instance
|
|
3652
|
+
* @memberof Bounds
|
|
3653
|
+
* @return {Vertex} The "easthmost" centered point of this bounding box.
|
|
3654
|
+
*/
|
|
3655
|
+
getEastPoint() {
|
|
3656
|
+
return new Vertex(this.max.x, this.min.y + this.height / 2.0);
|
|
3657
|
+
}
|
|
3658
|
+
;
|
|
3204
3659
|
/**
|
|
3205
3660
|
* Convert this rectangular bounding box to a polygon with four vertices.
|
|
3206
3661
|
*
|
|
@@ -3223,6 +3678,22 @@ class Bounds {
|
|
|
3223
3678
|
getCenter() {
|
|
3224
3679
|
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
3680
|
}
|
|
3681
|
+
/**
|
|
3682
|
+
* Get the minimum of `width` and `height`.
|
|
3683
|
+
*
|
|
3684
|
+
* @returns {number} The value of Math.min( this.width, this.height )
|
|
3685
|
+
*/
|
|
3686
|
+
getMinDimension() {
|
|
3687
|
+
return Math.min(this.width, this.height);
|
|
3688
|
+
}
|
|
3689
|
+
/**
|
|
3690
|
+
* Get the minimum of `width` and `height`.
|
|
3691
|
+
*
|
|
3692
|
+
* @returns {number} The value of Math.min( this.width, this.height )
|
|
3693
|
+
*/
|
|
3694
|
+
getMaxDimension() {
|
|
3695
|
+
return Math.max(this.width, this.height);
|
|
3696
|
+
}
|
|
3226
3697
|
/**
|
|
3227
3698
|
* Generate a random point inside this bounds object. Safe areas at the border to avoid
|
|
3228
3699
|
* included.
|
|
@@ -3273,12 +3744,13 @@ class Bounds {
|
|
|
3273
3744
|
* @static
|
|
3274
3745
|
* @method computeFromVertices
|
|
3275
3746
|
* @memberof Bounds
|
|
3276
|
-
* @param {Array<
|
|
3747
|
+
* @param {Array<XYCoords>} vertices - The set of vertices you want to get the bounding box for.
|
|
3277
3748
|
* @return The minimal Bounds for the given vertices.
|
|
3278
3749
|
**/
|
|
3279
3750
|
static computeFromVertices(vertices) {
|
|
3280
|
-
if (vertices.length == 0)
|
|
3751
|
+
if (vertices.length == 0) {
|
|
3281
3752
|
return new Bounds(new Vertex(0, 0), new Vertex(0, 0));
|
|
3753
|
+
}
|
|
3282
3754
|
let xMin = vertices[0].x;
|
|
3283
3755
|
let xMax = vertices[0].x;
|
|
3284
3756
|
let yMin = vertices[0].y;
|
|
@@ -3293,6 +3765,35 @@ class Bounds {
|
|
|
3293
3765
|
}
|
|
3294
3766
|
return new Bounds(new Vertex(xMin, yMin), new Vertex(xMax, yMax));
|
|
3295
3767
|
}
|
|
3768
|
+
/**
|
|
3769
|
+
* Compute the minimal bounding box for a given set of existing bounding boxes.
|
|
3770
|
+
*
|
|
3771
|
+
* An empty vertex array will return an empty bounding box located at (0,0).
|
|
3772
|
+
*
|
|
3773
|
+
* @static
|
|
3774
|
+
* @method computeFromBoundsSet
|
|
3775
|
+
* @memberof Bounds
|
|
3776
|
+
* @param {Array<IBounds>} boundingBoxes - The set of existing bounding boxes to get the containing bounding box for.
|
|
3777
|
+
* @return The minimal Bounds for the given bounds instances.
|
|
3778
|
+
**/
|
|
3779
|
+
static computeFromBoundsSet(boundingBoxes) {
|
|
3780
|
+
if (boundingBoxes.length == 0) {
|
|
3781
|
+
return new Bounds(new Vertex(0, 0), new Vertex(0, 0));
|
|
3782
|
+
}
|
|
3783
|
+
let xMin = boundingBoxes[0].min.x;
|
|
3784
|
+
let xMax = boundingBoxes[0].max.x;
|
|
3785
|
+
let yMin = boundingBoxes[0].min.y;
|
|
3786
|
+
let yMax = boundingBoxes[0].min.y;
|
|
3787
|
+
let bounds;
|
|
3788
|
+
for (var i in boundingBoxes) {
|
|
3789
|
+
bounds = boundingBoxes[i];
|
|
3790
|
+
xMin = Math.min(xMin, bounds.min.x);
|
|
3791
|
+
xMax = Math.max(xMax, bounds.max.x);
|
|
3792
|
+
yMin = Math.min(yMin, bounds.min.y);
|
|
3793
|
+
yMax = Math.max(yMax, bounds.min.y);
|
|
3794
|
+
}
|
|
3795
|
+
return new Bounds(new Vertex(xMin, yMin), new Vertex(xMax, yMax));
|
|
3796
|
+
}
|
|
3296
3797
|
/**
|
|
3297
3798
|
* Create a new `Bounds` instance just from `width` and `height`, located at (0,0) or the optionally given origin.
|
|
3298
3799
|
*
|
|
@@ -3331,7 +3832,17 @@ class Bounds {
|
|
|
3331
3832
|
* @modified 2022-10-17 The `CubicBezierCurve` class now implements the new `PathSegment` interface.
|
|
3332
3833
|
* @modified 2023-09-30 Added the function `CubicbezierCurve.getSubCurve(number,number)` – similar to `getSubCurveAt(...)` but with absolute position parameters.
|
|
3333
3834
|
* @modified 2023-10-07 Added the `trimEnd`, `trimEndAt`, `trimStart`, `trimStartAt` methods.
|
|
3334
|
-
* @
|
|
3835
|
+
* @modified 2025-04-09 Added the `CubicBezierCurve.move` method to match the convention – which just calls `translate`.
|
|
3836
|
+
* @modified 2025-04-09 Modified the `CubicBezierCurve.translate` method: chaning parameter `Vertex` to more generalized `XYCoords`.
|
|
3837
|
+
* @modified 2025-04-13 Changed visibility of `CubicBezierCurve.utils` from 'private' to 'public'.
|
|
3838
|
+
* @modified 2025-04-13 Added helper function `CubicBezierCurve.utils.bezierCoeffs`.
|
|
3839
|
+
* @modified 2025-04-13 Added helper functopn `CubicBezierCurve.utils.sgn(number)` for division safe sign calculation.
|
|
3840
|
+
* @modified 2025-03-13 Class `CubicBezierCurve` is now implementing interface `Intersectable`.
|
|
3841
|
+
* @modified 2025-04-18 Added evaluation method for cubic Bézier curves `CubicBezierCurve.utils.evaluateT`.
|
|
3842
|
+
* @modified 2025-04-18 Refactored method `CubicBezierCurve.getPointAt` to use `evaluateT`.
|
|
3843
|
+
* @modified 2025-04-18 Fixed the `CubicBezierCurve.getBounds` method: now returning the real bounding box. Before it was an approximated one.
|
|
3844
|
+
* @modified 2025-ß4-18 Added helper methods for bounding box calculation `CubucBezierCurve.util.cubicPolyMinMax` and `cubicPoly`.
|
|
3845
|
+
* @version 2.9.0
|
|
3335
3846
|
*
|
|
3336
3847
|
* @file CubicBezierCurve
|
|
3337
3848
|
* @public
|
|
@@ -3420,7 +3931,7 @@ class CubicBezierCurve {
|
|
|
3420
3931
|
* Translate the whole curve by the given {x,y} amount: moves all four points.
|
|
3421
3932
|
*
|
|
3422
3933
|
* @method translate
|
|
3423
|
-
* @param {
|
|
3934
|
+
* @param {XYCoords} amount - The amount to translate this curve by.
|
|
3424
3935
|
* @instance
|
|
3425
3936
|
* @memberof CubicBezierCurve
|
|
3426
3937
|
* @return {CubicBezierCurve} this (for chaining).
|
|
@@ -3432,6 +3943,18 @@ class CubicBezierCurve {
|
|
|
3432
3943
|
this.endPoint.add(amount);
|
|
3433
3944
|
return this;
|
|
3434
3945
|
}
|
|
3946
|
+
/**
|
|
3947
|
+
* Translate the whole curve by the given {x,y} amount: moves all four points.
|
|
3948
|
+
*
|
|
3949
|
+
* @method translate
|
|
3950
|
+
* @param {XYCoords} amount - The amount to translate this curve by.
|
|
3951
|
+
* @instance
|
|
3952
|
+
* @memberof CubicBezierCurve
|
|
3953
|
+
* @return {CubicBezierCurve} this (for chaining).
|
|
3954
|
+
**/
|
|
3955
|
+
move(amount) {
|
|
3956
|
+
return this.translate(amount);
|
|
3957
|
+
}
|
|
3435
3958
|
/**
|
|
3436
3959
|
* Reverse this curve, means swapping start- and end-point and swapping
|
|
3437
3960
|
* start-control- and end-control-point.
|
|
@@ -3512,6 +4035,9 @@ class CubicBezierCurve {
|
|
|
3512
4035
|
*
|
|
3513
4036
|
* This function uses a recursive approach by cutting the curve into several linear segments.
|
|
3514
4037
|
*
|
|
4038
|
+
* @method getClosestT
|
|
4039
|
+
* @instance
|
|
4040
|
+
* @memberof CubicBezierCurve
|
|
3515
4041
|
* @param {Vertex} p - The point to find the closest position ('t' on the curve).
|
|
3516
4042
|
* @return {number}
|
|
3517
4043
|
**/
|
|
@@ -3561,27 +4087,35 @@ class CubicBezierCurve {
|
|
|
3561
4087
|
tNext: tStart + tDiff * (Math.min(stepCount, minIndex + 1) / stepCount)
|
|
3562
4088
|
};
|
|
3563
4089
|
}
|
|
4090
|
+
//--- BEGIN --- Implement interface `IBounded`
|
|
3564
4091
|
/**
|
|
3565
4092
|
* Get the bounds of this bezier curve.
|
|
3566
4093
|
*
|
|
3567
4094
|
* The bounds are approximated by the underlying segment buffer; the more segment there are,
|
|
3568
4095
|
* the more accurate will be the returned bounds.
|
|
3569
4096
|
*
|
|
3570
|
-
* @
|
|
4097
|
+
* @method getClosestT
|
|
4098
|
+
* @instance
|
|
4099
|
+
* @memberof CubicBezierCurve
|
|
4100
|
+
* @return {Bounds} The bounds of this ellipse.
|
|
3571
4101
|
**/
|
|
3572
4102
|
getBounds() {
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
4103
|
+
// Thanks to Richard "RM" for the Bézier bounds calculatin
|
|
4104
|
+
// https://jsfiddle.net/SalixAlba/QQnvm/4/
|
|
4105
|
+
const xMinMax = CubicBezierCurve.utils.cubicPolyMinMax(this.startPoint.x, this.startControlPoint.x, this.endControlPoint.x, this.endPoint.x);
|
|
4106
|
+
const xl = xMinMax.min;
|
|
4107
|
+
const xh = xMinMax.max;
|
|
4108
|
+
const yMinMax = CubicBezierCurve.utils.cubicPolyMinMax(this.startPoint.y, this.startControlPoint.y, this.endControlPoint.y, this.endPoint.y);
|
|
4109
|
+
const yl = yMinMax.min;
|
|
4110
|
+
const yh = yMinMax.max;
|
|
4111
|
+
return Bounds.computeFromVertices([
|
|
4112
|
+
{ x: xl, y: yl },
|
|
4113
|
+
{ x: xl, y: yh },
|
|
4114
|
+
{ x: xh, y: yh },
|
|
4115
|
+
{ x: xh, y: yl }
|
|
4116
|
+
]);
|
|
4117
|
+
}
|
|
4118
|
+
//--- END --- Implement interface `IBounded`
|
|
3585
4119
|
/**
|
|
3586
4120
|
* Get the start point of the curve.<br>
|
|
3587
4121
|
* <br>
|
|
@@ -3668,14 +4202,19 @@ class CubicBezierCurve {
|
|
|
3668
4202
|
**/
|
|
3669
4203
|
getPointAt(t) {
|
|
3670
4204
|
// Perform some powerful math magic
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
4205
|
+
// TODO: cleanup
|
|
4206
|
+
// const x: number =
|
|
4207
|
+
// this.startPoint.x * Math.pow(1.0 - t, 3) +
|
|
4208
|
+
// this.startControlPoint.x * 3 * t * Math.pow(1.0 - t, 2) +
|
|
4209
|
+
// this.endControlPoint.x * 3 * Math.pow(t, 2) * (1.0 - t) +
|
|
4210
|
+
// this.endPoint.x * Math.pow(t, 3);
|
|
4211
|
+
// const y: number =
|
|
4212
|
+
// this.startPoint.y * Math.pow(1.0 - t, 3) +
|
|
4213
|
+
// this.startControlPoint.y * 3 * t * Math.pow(1.0 - t, 2) +
|
|
4214
|
+
// this.endControlPoint.y * 3 * Math.pow(t, 2) * (1.0 - t) +
|
|
4215
|
+
// this.endPoint.y * Math.pow(t, 3);
|
|
4216
|
+
const x = CubicBezierCurve.utils.evaluateT(this.startPoint.x, this.startControlPoint.x, this.endControlPoint.x, this.endPoint.x, t);
|
|
4217
|
+
const y = CubicBezierCurve.utils.evaluateT(this.startPoint.y, this.startControlPoint.y, this.endControlPoint.y, this.endPoint.y, t);
|
|
3679
4218
|
return new Vertex(x, y);
|
|
3680
4219
|
}
|
|
3681
4220
|
/**
|
|
@@ -3816,17 +4355,7 @@ class CubicBezierCurve {
|
|
|
3816
4355
|
* @return {CubicBezierCurve} The sub curve as a new curve.
|
|
3817
4356
|
**/
|
|
3818
4357
|
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);
|
|
4358
|
+
// This 'splits' the curve at the given point at t.
|
|
3830
4359
|
const subCurbePoints = CubicBezierCurve.utils.getSubCurvePointsAt(this, tStart, tEnd);
|
|
3831
4360
|
return new CubicBezierCurve(subCurbePoints[0], subCurbePoints[1], subCurbePoints[2], subCurbePoints[3]);
|
|
3832
4361
|
}
|
|
@@ -3918,6 +4447,69 @@ class CubicBezierCurve {
|
|
|
3918
4447
|
return this.endControlPoint;
|
|
3919
4448
|
}
|
|
3920
4449
|
//---END PathSegment-------------------------
|
|
4450
|
+
//--- BEGIN --- Implement interface `Intersectable`
|
|
4451
|
+
/**
|
|
4452
|
+
* Get all line intersections with this shape.
|
|
4453
|
+
*
|
|
4454
|
+
* This method returns all intersections (as vertices) with this shape. The returned array of vertices is in no specific order.
|
|
4455
|
+
*
|
|
4456
|
+
* @param {VertTuple} line - The line to find intersections with.
|
|
4457
|
+
* @param {boolean} inVectorBoundsOnly - If set to true only intersecion points on the passed vector are returned (located strictly between start and end vertex).
|
|
4458
|
+
* @returns {Array<Vertex>} - An array of all intersections with the shape outline.
|
|
4459
|
+
*/
|
|
4460
|
+
lineIntersections(line, inVectorBoundsOnly = false) {
|
|
4461
|
+
const intersectionTs = this.lineIntersectionTs(line);
|
|
4462
|
+
const intersectionPoints = intersectionTs.map((t) => {
|
|
4463
|
+
return this.getPointAt(t);
|
|
4464
|
+
});
|
|
4465
|
+
if (inVectorBoundsOnly) {
|
|
4466
|
+
// const maxDist = line.length();
|
|
4467
|
+
return intersectionPoints.filter((vert) => line.hasPoint(vert, true));
|
|
4468
|
+
}
|
|
4469
|
+
else {
|
|
4470
|
+
return intersectionPoints;
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
/**
|
|
4474
|
+
* Get all line intersections of this polygon and their tangents along the shape.
|
|
4475
|
+
*
|
|
4476
|
+
* This method returns all intersection tangents (as vectors) with this shape. The returned array of vectors is in no specific order.
|
|
4477
|
+
*
|
|
4478
|
+
* @param line
|
|
4479
|
+
* @param lineIntersectionTangents
|
|
4480
|
+
* @returns
|
|
4481
|
+
*/
|
|
4482
|
+
lineIntersectionTangents(line, inVectorBoundsOnly = false) {
|
|
4483
|
+
const intersectionTs = this.lineIntersectionTs(line);
|
|
4484
|
+
const intersectionTangents = intersectionTs.map((t) => {
|
|
4485
|
+
const startPoint = this.getPointAt(t);
|
|
4486
|
+
const endPoint = this.getTangentAt(t);
|
|
4487
|
+
return new Vector(startPoint, endPoint.add(startPoint));
|
|
4488
|
+
});
|
|
4489
|
+
if (inVectorBoundsOnly) {
|
|
4490
|
+
return intersectionTangents.filter((vec) => line.hasPoint(vec.a, true));
|
|
4491
|
+
}
|
|
4492
|
+
else {
|
|
4493
|
+
return intersectionTangents;
|
|
4494
|
+
}
|
|
4495
|
+
}
|
|
4496
|
+
//--- END --- Implement interface `Intersectable`
|
|
4497
|
+
lineIntersectionTs(line) {
|
|
4498
|
+
var A = line.b.y - line.a.y; // A=y2-y1
|
|
4499
|
+
var B = line.a.x - line.b.x; // B=x1-x2
|
|
4500
|
+
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)
|
|
4501
|
+
// var bx = bezierCoeffs(px[0], px[1], px[2], px[3]);
|
|
4502
|
+
// var by = bezierCoeffs(py[0], py[1], py[2], py[3]);
|
|
4503
|
+
var bx = CubicBezierCurve.utils.bezierCoeffs(this.startPoint.x, this.startControlPoint.x, this.endControlPoint.x, this.endPoint.x);
|
|
4504
|
+
var by = CubicBezierCurve.utils.bezierCoeffs(this.startPoint.y, this.startControlPoint.y, this.endControlPoint.y, this.endPoint.y);
|
|
4505
|
+
const poly = Array(4);
|
|
4506
|
+
poly[0] = A * bx[0] + B * by[0]; /*t^3*/
|
|
4507
|
+
poly[1] = A * bx[1] + B * by[1]; /*t^2*/
|
|
4508
|
+
poly[2] = A * bx[2] + B * by[2]; /*t*/
|
|
4509
|
+
poly[3] = A * bx[3] + B * by[3] + C; /*1*/
|
|
4510
|
+
var roots = CubicBezierCurve.utils.cubicRoots(poly);
|
|
4511
|
+
return roots.filter((root) => root != -1);
|
|
4512
|
+
}
|
|
3921
4513
|
/**
|
|
3922
4514
|
* Check if this and the specified curve are equal.<br>
|
|
3923
4515
|
* <br>
|
|
@@ -4085,6 +4677,71 @@ CubicBezierCurve.END_POINT = 3;
|
|
|
4085
4677
|
* Helper utils.
|
|
4086
4678
|
*/
|
|
4087
4679
|
CubicBezierCurve.utils = {
|
|
4680
|
+
evaluateT: (p0, p1, p2, p3, t) => {
|
|
4681
|
+
return p0 * Math.pow(1.0 - t, 3) +
|
|
4682
|
+
p1 * 3 * t * Math.pow(1.0 - t, 2) +
|
|
4683
|
+
p2 * 3 * Math.pow(t, 2) * (1.0 - t) +
|
|
4684
|
+
p3 * Math.pow(t, 3);
|
|
4685
|
+
},
|
|
4686
|
+
cubicPolyMinMax: (p0, p1, p2, p3) => {
|
|
4687
|
+
// var polyX = CubicBezierCurve.utils.cubicPoly2(
|
|
4688
|
+
// p0, // P[0].X, // bezierCurve.startPoint.x,
|
|
4689
|
+
// p1, // P[1].X, // bezierCurve.startControlPoint.x,
|
|
4690
|
+
// p2, // P[2].X, // bezierCurve.endControlPoint.x,
|
|
4691
|
+
// p3 // P[3].X // bezierCurve.endPoint.x
|
|
4692
|
+
// );
|
|
4693
|
+
// var a = polyX.a;
|
|
4694
|
+
// var b = polyX.b;
|
|
4695
|
+
// var c = polyX.c;
|
|
4696
|
+
// var disc = polyX.b * polyX.b - 4 * polyX.a * polyX.c;
|
|
4697
|
+
var polyX = CubicBezierCurve.utils.cubicPoly(p0, // P[0].X, // bezierCurve.startPoint.x,
|
|
4698
|
+
p1, // P[1].X, // bezierCurve.startControlPoint.x,
|
|
4699
|
+
p2, // P[2].X, // bezierCurve.endControlPoint.x,
|
|
4700
|
+
p3 // P[3].X // bezierCurve.endPoint.x
|
|
4701
|
+
);
|
|
4702
|
+
var a = polyX[0]; // .a;
|
|
4703
|
+
var b = polyX[1]; // .b;
|
|
4704
|
+
polyX[2]; // .c;
|
|
4705
|
+
//alert("a "+a+" "+b+" "+c);
|
|
4706
|
+
// var disc = b * b - 4 * a * c;
|
|
4707
|
+
var disc = polyX[1] * polyX[1] - 4 * polyX[0] * polyX[2];
|
|
4708
|
+
// var polyX = CubicBezierCurve.utils.bezierCoeffs(p3,p2,p1,p0);
|
|
4709
|
+
// var a = polyX[0]; //polyX.a;
|
|
4710
|
+
// var b = polyX[1]; // .b;
|
|
4711
|
+
// var c = polyX[2]; //.c;
|
|
4712
|
+
// var disc = polyX[1] * polyX[1] - 4 * polyX[0] * polyX[2];
|
|
4713
|
+
// var xl = Math.min(bCurve.endPoint.x, bCurve.startPoint.x); // P[0].X;
|
|
4714
|
+
// var xh = Math.max(bCurve.endPoint.x, bCurve.startPoint.x); // P[0].X;
|
|
4715
|
+
var xl = Math.min(p3, p0); // P[0].X;
|
|
4716
|
+
var xh = Math.max(p3, p0); // P[0].X;
|
|
4717
|
+
// if (P[3].X < xl) xl = P[3].X;
|
|
4718
|
+
// if (P[3].X > xh) xh = P[3].X;
|
|
4719
|
+
if (disc >= 0) {
|
|
4720
|
+
var t1 = (-b + Math.sqrt(disc)) / (2 * a);
|
|
4721
|
+
// alert("t1 " + t1);
|
|
4722
|
+
if (t1 > 0 && t1 < 1) {
|
|
4723
|
+
// var x1 = evalBez(PX, t1);
|
|
4724
|
+
// var x1 = bCurve.getPointAt(t1).x;
|
|
4725
|
+
var x1 = CubicBezierCurve.utils.evaluateT(p0, p1, p2, p3, t1); // bCurve.getPointAt(t1).x;
|
|
4726
|
+
if (x1 < xl)
|
|
4727
|
+
xl = x1;
|
|
4728
|
+
if (x1 > xh)
|
|
4729
|
+
xh = x1;
|
|
4730
|
+
}
|
|
4731
|
+
var t2 = (-b - Math.sqrt(disc)) / (2 * a);
|
|
4732
|
+
// alert("t2 " + t2);
|
|
4733
|
+
if (t2 > 0 && t2 < 1) {
|
|
4734
|
+
// var x2 = evalBez(PX, t2);
|
|
4735
|
+
// var x2 = bCurve.getPointAt(t2).x;
|
|
4736
|
+
var x2 = CubicBezierCurve.utils.evaluateT(p0, p1, p2, p3, t2); //
|
|
4737
|
+
if (x2 < xl)
|
|
4738
|
+
xl = x2;
|
|
4739
|
+
if (x2 > xh)
|
|
4740
|
+
xh = x2;
|
|
4741
|
+
}
|
|
4742
|
+
}
|
|
4743
|
+
return { min: xl, max: xh };
|
|
4744
|
+
},
|
|
4088
4745
|
/**
|
|
4089
4746
|
* Get the points of a sub curve at the given start end end offsets (values between 0.0 and 1.0).
|
|
4090
4747
|
*
|
|
@@ -4108,6 +4765,94 @@ CubicBezierCurve.utils = {
|
|
|
4108
4765
|
startVec.scale(0.33333333 * (tEnd - tStart));
|
|
4109
4766
|
endVec.scale(0.33333333 * (tEnd - tStart));
|
|
4110
4767
|
return [startVec.a, endVec.a, startVec.b, endVec.b];
|
|
4768
|
+
},
|
|
4769
|
+
/**
|
|
4770
|
+
* Compute the cubic roots for the given cubic polynomial coefficients.
|
|
4771
|
+
*
|
|
4772
|
+
* Based on
|
|
4773
|
+
* http://mysite.verizon.net/res148h4j/javascript/script_exact_cubic.html#the%20source%20code
|
|
4774
|
+
* Inspired by
|
|
4775
|
+
* https://www.particleincell.com/2013/cubic-line-intersection/
|
|
4776
|
+
* Thanks to Stephan Schmitt and Particle-In-Cell!
|
|
4777
|
+
*
|
|
4778
|
+
* @param poly
|
|
4779
|
+
* @returns
|
|
4780
|
+
*/
|
|
4781
|
+
cubicRoots: (poly) => {
|
|
4782
|
+
const a = poly[0];
|
|
4783
|
+
const b = poly[1];
|
|
4784
|
+
const c = poly[2];
|
|
4785
|
+
const d = poly[3];
|
|
4786
|
+
const A = b / a;
|
|
4787
|
+
const B = c / a;
|
|
4788
|
+
const C = d / a;
|
|
4789
|
+
var S, T, Im;
|
|
4790
|
+
const Q = (3 * B - Math.pow(A, 2)) / 9;
|
|
4791
|
+
const R = (9 * A * B - 27 * C - 2 * Math.pow(A, 3)) / 54;
|
|
4792
|
+
const D = Math.pow(Q, 3) + Math.pow(R, 2); // polynomial discriminant
|
|
4793
|
+
const ts = []; // Array();
|
|
4794
|
+
if (D >= 0) {
|
|
4795
|
+
// complex or duplicate roots
|
|
4796
|
+
S = CubicBezierCurve.utils.sgn(R + Math.sqrt(D)) * Math.pow(Math.abs(R + Math.sqrt(D)), 1 / 3);
|
|
4797
|
+
T = CubicBezierCurve.utils.sgn(R - Math.sqrt(D)) * Math.pow(Math.abs(R - Math.sqrt(D)), 1 / 3);
|
|
4798
|
+
ts[0] = -A / 3 + (S + T); // real root
|
|
4799
|
+
ts[1] = -A / 3 - (S + T) / 2; // real part of complex root
|
|
4800
|
+
ts[2] = -A / 3 - (S + T) / 2; // real part of complex root
|
|
4801
|
+
Im = Math.abs((Math.sqrt(3) * (S - T)) / 2); // complex part of root pair
|
|
4802
|
+
// Mark complex roots to be discarded
|
|
4803
|
+
if (Im != 0) {
|
|
4804
|
+
ts[1] = -1;
|
|
4805
|
+
ts[2] = -1;
|
|
4806
|
+
}
|
|
4807
|
+
} // distinct real roots
|
|
4808
|
+
else {
|
|
4809
|
+
const th = Math.acos(R / Math.sqrt(-Math.pow(Q, 3)));
|
|
4810
|
+
ts[0] = 2 * Math.sqrt(-Q) * Math.cos(th / 3) - A / 3;
|
|
4811
|
+
ts[1] = 2 * Math.sqrt(-Q) * Math.cos((th + 2 * Math.PI) / 3) - A / 3;
|
|
4812
|
+
ts[2] = 2 * Math.sqrt(-Q) * Math.cos((th + 4 * Math.PI) / 3) - A / 3;
|
|
4813
|
+
Im = 0.0;
|
|
4814
|
+
}
|
|
4815
|
+
// Discard all t's out of spec and sort the rest
|
|
4816
|
+
return ts.filter((t) => t >= 0 && t <= 1.0).sort();
|
|
4817
|
+
},
|
|
4818
|
+
/**
|
|
4819
|
+
* Compute the Bézier coefficients from the given Bézier point coordinates.
|
|
4820
|
+
*
|
|
4821
|
+
* @param {number} p0 - The start point coordinate.
|
|
4822
|
+
* @param {number} p1 - The start control point coordinate.
|
|
4823
|
+
* @param {number} p2 - The end control point coordinate.
|
|
4824
|
+
* @param {number} p3 - The end point coordinate.
|
|
4825
|
+
* @returns {[number,number,number,number]}
|
|
4826
|
+
*/
|
|
4827
|
+
bezierCoeffs: (p0, p1, p2, p3) => {
|
|
4828
|
+
const coeffs = [NaN, NaN, NaN, NaN]; //Array(4);
|
|
4829
|
+
coeffs[0] = -p0 + 3 * p1 + -3 * p2 + p3;
|
|
4830
|
+
coeffs[1] = 3 * p0 - 6 * p1 + 3 * p2;
|
|
4831
|
+
coeffs[2] = -3 * p0 + 3 * p1;
|
|
4832
|
+
coeffs[3] = p0;
|
|
4833
|
+
return coeffs;
|
|
4834
|
+
},
|
|
4835
|
+
/**
|
|
4836
|
+
* Calculate the cubic polynomial coefficients used to find the bounding box.
|
|
4837
|
+
*
|
|
4838
|
+
* @param {number} p0 - The start point coordinate.
|
|
4839
|
+
* @param {number} p1 - The start control point coordinate.
|
|
4840
|
+
* @param {number} p2 - The end control point coordinate.
|
|
4841
|
+
* @param {number} p3 - The end point coordinate.
|
|
4842
|
+
* @returns {[number,number,number]}
|
|
4843
|
+
*/
|
|
4844
|
+
cubicPoly: (p0, p1, p2, p3) => {
|
|
4845
|
+
return [
|
|
4846
|
+
3 * p3 - 9 * p2 + 9 * p1 - 3 * p0,
|
|
4847
|
+
6 * p0 - 12 * p1 + 6 * p2,
|
|
4848
|
+
3 * p1 - 3 * p0
|
|
4849
|
+
];
|
|
4850
|
+
},
|
|
4851
|
+
/**
|
|
4852
|
+
* sign of number, but is division safe: no zero returned :)
|
|
4853
|
+
*/
|
|
4854
|
+
sgn(x) {
|
|
4855
|
+
return x < 0.0 ? -1 : 1;
|
|
4111
4856
|
}
|
|
4112
4857
|
};
|
|
4113
4858
|
|
|
@@ -4138,7 +4883,10 @@ CubicBezierCurve.utils = {
|
|
|
4138
4883
|
* @modified 2022-02-02 Cleared the `toSVGString` function (deprecated). Use `drawutilssvg` instead.
|
|
4139
4884
|
* @modified 2023-10-06 Adding the `BezierPath.toPathPoints()` method.
|
|
4140
4885
|
* @modified 2023-10-07 Adding the `BezierPath.fromCurve(CubicBezierCurve)` static function.
|
|
4141
|
-
* @
|
|
4886
|
+
* @modified 2025-04-09 Added the `BezierPath.move` method to match the convention – which just calls `translate`.
|
|
4887
|
+
* @modified 2025-04-09 Modified the `BezierPath.translate` method: chaning parameter `Vertex` to more generalized `XYCoords`.
|
|
4888
|
+
* @modified 2025-04-14 Class `BezierPath` is now implementing interface `Intersectable`.
|
|
4889
|
+
* @version 2.7.0
|
|
4142
4890
|
*
|
|
4143
4891
|
* @file BezierPath
|
|
4144
4892
|
* @public
|
|
@@ -4343,7 +5091,7 @@ class BezierPath {
|
|
|
4343
5091
|
* Move the whole bezier path by the given (x,y)-amount.
|
|
4344
5092
|
*
|
|
4345
5093
|
* @method translate
|
|
4346
|
-
* @param {
|
|
5094
|
+
* @param {XYCoords} amount - The amount to be added (amount.x and amount.y)
|
|
4347
5095
|
* to each vertex of the curve.
|
|
4348
5096
|
* @instance
|
|
4349
5097
|
* @memberof BezierPath
|
|
@@ -4362,6 +5110,19 @@ class BezierPath {
|
|
|
4362
5110
|
this.updateArcLengths();
|
|
4363
5111
|
return this;
|
|
4364
5112
|
}
|
|
5113
|
+
/**
|
|
5114
|
+
* Move the whole bezier path by the given (x,y)-amount.
|
|
5115
|
+
*
|
|
5116
|
+
* @method move
|
|
5117
|
+
* @param {XYCoords} amount - The amount to be added (amount.x and amount.y)
|
|
5118
|
+
* to each vertex of the curve.
|
|
5119
|
+
* @instance
|
|
5120
|
+
* @memberof BezierPath
|
|
5121
|
+
* @return {BezierPath} this for chaining
|
|
5122
|
+
**/
|
|
5123
|
+
move(amount) {
|
|
5124
|
+
return this.translate(amount);
|
|
5125
|
+
}
|
|
4365
5126
|
/**
|
|
4366
5127
|
* Scale the whole bezier path by the given uniform factor.
|
|
4367
5128
|
*
|
|
@@ -4572,6 +5333,36 @@ class BezierPath {
|
|
|
4572
5333
|
var relativeU = u - uResult.uPart;
|
|
4573
5334
|
return bCurve.getPerpendicular(relativeU);
|
|
4574
5335
|
}
|
|
5336
|
+
//--- BEGIN --- Implement interface `Intersectable`
|
|
5337
|
+
/**
|
|
5338
|
+
* Get all line intersections with this shape.
|
|
5339
|
+
*
|
|
5340
|
+
* This method returns all intersections (as vertices) with this shape. The returned array of vertices is in no specific order.
|
|
5341
|
+
*
|
|
5342
|
+
* @param {VertTuple} line - The line to find intersections with.
|
|
5343
|
+
* @param {boolean} inVectorBoundsOnly - If set to true only intersecion points on the passed vector are returned (located strictly between start and end vertex).
|
|
5344
|
+
* @returns {Array<Vertex>} - An array of all intersections with the shape's outline.
|
|
5345
|
+
*/
|
|
5346
|
+
lineIntersections(line, inVectorBoundsOnly = false) {
|
|
5347
|
+
return this.bezierCurves.reduce((accu, curCurve) => {
|
|
5348
|
+
return accu.concat(curCurve.lineIntersections(line, inVectorBoundsOnly));
|
|
5349
|
+
}, []);
|
|
5350
|
+
}
|
|
5351
|
+
/**
|
|
5352
|
+
* Get all line intersections of this polygon and their tangents along the shape.
|
|
5353
|
+
*
|
|
5354
|
+
* This method returns all intersection tangents (as vectors) with this shape. The returned array of vectors is in no specific order.
|
|
5355
|
+
*
|
|
5356
|
+
* @param line
|
|
5357
|
+
* @param lineIntersectionTangents
|
|
5358
|
+
* @returns
|
|
5359
|
+
*/
|
|
5360
|
+
lineIntersectionTangents(line, inVectorBoundsOnly = false) {
|
|
5361
|
+
return this.bezierCurves.reduce((accu, curCurve) => {
|
|
5362
|
+
return accu.concat(curCurve.lineIntersectionTangents(line, inVectorBoundsOnly));
|
|
5363
|
+
}, []);
|
|
5364
|
+
}
|
|
5365
|
+
//--- END --- Implement interface `Intersectable`
|
|
4575
5366
|
/**
|
|
4576
5367
|
* This is a helper function to locate the curve index for a given
|
|
4577
5368
|
* absolute path position u.
|
|
@@ -4785,6 +5576,7 @@ class BezierPath {
|
|
|
4785
5576
|
}
|
|
4786
5577
|
neighbourCurve.updateArcLengths();
|
|
4787
5578
|
}
|
|
5579
|
+
//--- BEGIN --- Implement interface `IBounded`
|
|
4788
5580
|
/**
|
|
4789
5581
|
* Get the bounds of this Bézier path.
|
|
4790
5582
|
*
|
|
@@ -4806,6 +5598,7 @@ class BezierPath {
|
|
|
4806
5598
|
}
|
|
4807
5599
|
return new Bounds(min, max);
|
|
4808
5600
|
}
|
|
5601
|
+
//--- END --- Implement interface `IBounded`
|
|
4809
5602
|
/**
|
|
4810
5603
|
* Get n 'equally' distributed vertices along this Bézier path.
|
|
4811
5604
|
*
|
|
@@ -5189,6 +5982,10 @@ BezierPath.END_POINT = 3;
|
|
|
5189
5982
|
* @modified 2024-03-08 Added the `containsAngle` method.
|
|
5190
5983
|
* @modified 2024-03-09 Added the `circleSectorIntersection` method to find coherent sector intersections..
|
|
5191
5984
|
* @modified 2024-03-09 Added the `angleAt` method to determine any angle at some ratio.
|
|
5985
|
+
* @modified 2025-04-02 Adding the `CircleSector.lineIntersections` and `CircleSector.lineIntersectionTangents` and implementing `Intersectable`.
|
|
5986
|
+
* @modified 2025-04-09 Adding the `CircleSector.move()` method.
|
|
5987
|
+
* @modified 2025-04-19 Tweaking the `CircleSector.containsAngle` method: all values (input angle, start- and end- angle) are wrapped into [0,2*PI) now.
|
|
5988
|
+
* @modified 2025-04-19 Class `CircleSector` implements interface `Bounded` now (method `getBounds` added).
|
|
5192
5989
|
* @version 1.2.0
|
|
5193
5990
|
**/
|
|
5194
5991
|
/**
|
|
@@ -5220,6 +6017,49 @@ class CircleSector {
|
|
|
5220
6017
|
this.startAngle = startAngle;
|
|
5221
6018
|
this.endAngle = endAngle;
|
|
5222
6019
|
}
|
|
6020
|
+
//--- BEGIN --- Implement interface `IBounded`
|
|
6021
|
+
/**
|
|
6022
|
+
* Get the bounds of this ellipse.
|
|
6023
|
+
*
|
|
6024
|
+
* The bounds are approximated by the underlying segment buffer; the more segment there are,
|
|
6025
|
+
* the more accurate will be the returned bounds.
|
|
6026
|
+
*
|
|
6027
|
+
* @method getBounds
|
|
6028
|
+
* @instance
|
|
6029
|
+
* @memberof VEllipse
|
|
6030
|
+
* @return {Bounds} The bounds of this curve.
|
|
6031
|
+
**/
|
|
6032
|
+
getBounds() {
|
|
6033
|
+
const _self = this;
|
|
6034
|
+
const circleBounds = this.circle.getBounds();
|
|
6035
|
+
// Calculage angles from east, west, north and south box points and check if they are inside
|
|
6036
|
+
const candidates = [
|
|
6037
|
+
circleBounds.getNorthPoint(),
|
|
6038
|
+
circleBounds.getSouthPoint(),
|
|
6039
|
+
circleBounds.getWestPoint(),
|
|
6040
|
+
circleBounds.getEastPoint()
|
|
6041
|
+
].filter((point) => {
|
|
6042
|
+
// Check for each candidate points if they are contained in this sector. Drop if not.
|
|
6043
|
+
const angle = new Line(_self.circle.center, point).angle();
|
|
6044
|
+
return _self.containsAngle(angle);
|
|
6045
|
+
});
|
|
6046
|
+
// Compute bounds and inlcude start end end point (they are definitely part of the bounds)
|
|
6047
|
+
return Bounds.computeFromVertices(candidates.concat([this.getStartPoint(), this.getEndPoint()]));
|
|
6048
|
+
}
|
|
6049
|
+
//--- BEGIN --- Implement interface `IBounded`
|
|
6050
|
+
/**
|
|
6051
|
+
* Move the circle sector by the given amount.
|
|
6052
|
+
*
|
|
6053
|
+
* @method move
|
|
6054
|
+
* @param {XYCoords} amount - The amount to move.
|
|
6055
|
+
* @instance
|
|
6056
|
+
* @memberof CircleSector
|
|
6057
|
+
* @return {CircleSector} this for chaining
|
|
6058
|
+
**/
|
|
6059
|
+
move(amount) {
|
|
6060
|
+
this.circle.move(amount);
|
|
6061
|
+
return this;
|
|
6062
|
+
}
|
|
5223
6063
|
/**
|
|
5224
6064
|
* Checks wether the given angle (must be inside 0 and PI*2) is contained inside this sector.
|
|
5225
6065
|
*
|
|
@@ -5230,12 +6070,22 @@ class CircleSector {
|
|
|
5230
6070
|
* @return {boolean} True if (and only if) this sector contains the given angle.
|
|
5231
6071
|
*/
|
|
5232
6072
|
containsAngle(angle) {
|
|
5233
|
-
|
|
5234
|
-
|
|
6073
|
+
var wrappedAngle = geomutils.mapAngleTo2PI(angle);
|
|
6074
|
+
var wrappedStart = geomutils.mapAngleTo2PI(this.startAngle);
|
|
6075
|
+
var wrappedEnd = geomutils.mapAngleTo2PI(this.endAngle);
|
|
6076
|
+
// TODO: cleanup
|
|
6077
|
+
// if (this.startAngle <= this.endAngle) {
|
|
6078
|
+
// return angle >= this.startAngle && angle < this.endAngle;
|
|
6079
|
+
// } else {
|
|
6080
|
+
// // startAngle > endAngle
|
|
6081
|
+
// return angle >= this.startAngle || angle < this.endAngle;
|
|
6082
|
+
// }
|
|
6083
|
+
if (wrappedStart <= wrappedEnd) {
|
|
6084
|
+
return wrappedAngle >= wrappedStart && wrappedAngle < wrappedEnd;
|
|
5235
6085
|
}
|
|
5236
6086
|
else {
|
|
5237
6087
|
// startAngle > endAngle
|
|
5238
|
-
return
|
|
6088
|
+
return wrappedAngle >= wrappedStart || wrappedAngle < wrappedEnd;
|
|
5239
6089
|
}
|
|
5240
6090
|
}
|
|
5241
6091
|
/**
|
|
@@ -5329,6 +6179,50 @@ class CircleSector {
|
|
|
5329
6179
|
}
|
|
5330
6180
|
return resultSector;
|
|
5331
6181
|
}
|
|
6182
|
+
//--- BEGIN --- Implement interface `Intersectable`
|
|
6183
|
+
/**
|
|
6184
|
+
* Get the line intersections as vectors with this ellipse.
|
|
6185
|
+
*
|
|
6186
|
+
* @method lineIntersections
|
|
6187
|
+
* @instance
|
|
6188
|
+
* @param {VertTuple<Vector> ray - The line/ray to intersect this ellipse with.
|
|
6189
|
+
* @param {boolean} inVectorBoundsOnly - (default=false) Set to true if only intersections within the vector bounds are of interest.
|
|
6190
|
+
* @returns
|
|
6191
|
+
*/
|
|
6192
|
+
lineIntersections(ray, inVectorBoundsOnly = false) {
|
|
6193
|
+
// First get all line intersections from underlying ellipse.
|
|
6194
|
+
const ellipseIntersections = this.circle.lineIntersections(ray, inVectorBoundsOnly);
|
|
6195
|
+
// Drop all intersection points that are not contained in the circle sectors bounds.
|
|
6196
|
+
const tmpLine = new Line(this.circle.center, new Vertex());
|
|
6197
|
+
return ellipseIntersections.filter((intersectionPoint) => {
|
|
6198
|
+
tmpLine.b.set(intersectionPoint);
|
|
6199
|
+
const lineAngle = tmpLine.angle();
|
|
6200
|
+
return this.containsAngle(geomutils.wrapMinMax(lineAngle, 0, Math.PI * 2));
|
|
6201
|
+
});
|
|
6202
|
+
}
|
|
6203
|
+
/**
|
|
6204
|
+
* Get all line intersections of this polygon and their tangents along the shape.
|
|
6205
|
+
*
|
|
6206
|
+
* This method returns all intersection tangents (as vectors) with this shape. The returned array of vectors is in no specific order.
|
|
6207
|
+
*
|
|
6208
|
+
* @param line
|
|
6209
|
+
* @param lineIntersectionTangents
|
|
6210
|
+
* @returns
|
|
6211
|
+
*/
|
|
6212
|
+
lineIntersectionTangents(line, inVectorBoundsOnly = false) {
|
|
6213
|
+
// Find the intersections of all lines plus their tangents inside the circle bounds
|
|
6214
|
+
const interSectionPoints = this.lineIntersections(line, inVectorBoundsOnly);
|
|
6215
|
+
return interSectionPoints.map((vert) => {
|
|
6216
|
+
// Calculate angle
|
|
6217
|
+
const lineFromCenter = new Line(this.circle.center, vert);
|
|
6218
|
+
const angle = lineFromCenter.angle();
|
|
6219
|
+
// console.log("angle", (angle / Math.PI) * 180.0);
|
|
6220
|
+
// const angle = Math.random() * Math.PI * 2; // TODO
|
|
6221
|
+
// Calculate tangent at angle
|
|
6222
|
+
return this.circle.tangentAt(angle);
|
|
6223
|
+
});
|
|
6224
|
+
}
|
|
6225
|
+
//--- END --- Implement interface `Intersectable`
|
|
5332
6226
|
/**
|
|
5333
6227
|
* This function should invalidate any installed listeners and invalidate this object.
|
|
5334
6228
|
* After calling this function the object might not hold valid data any more and
|
|
@@ -10445,7 +11339,11 @@ class AlloyFinger {
|
|
|
10445
11339
|
* @modified 2021-03-19 Added the `VEllipse.rotate` function.
|
|
10446
11340
|
* @modified 2022-02-02 Added the `destroy` method.
|
|
10447
11341
|
* @modified 2022-02-02 Cleared the `VEllipse.toSVGString` function (deprecated). Use `drawutilssvg` instead.
|
|
10448
|
-
* @
|
|
11342
|
+
* @modified 2025-03-31 ATTENTION: modified the winding direction of the `tangentAt` method to match with the Circle method. This is a breaking change!
|
|
11343
|
+
* @modified 2025-03-31 Adding the `VEllipse.move(amount: XYCoords)` method.
|
|
11344
|
+
* @modified 2025-04-19 Adding the `VEllipse.getBounds()` method.
|
|
11345
|
+
* @modified 2025-04-24 Adding the `VEllipse.getExtremePoints()` method for calculating minima and maxima.
|
|
11346
|
+
* @version 1.4.0
|
|
10449
11347
|
*
|
|
10450
11348
|
* @file VEllipse
|
|
10451
11349
|
* @fileoverview Ellipses with a center and an x- and a y-axis (stored as a vertex).
|
|
@@ -10531,11 +11429,96 @@ class VEllipse {
|
|
|
10531
11429
|
* @return {number} The signed vertical radius of this ellipse.
|
|
10532
11430
|
*/
|
|
10533
11431
|
signedRadiusV() {
|
|
10534
|
-
// return Math.abs(this.axis.y - this.center.y);
|
|
10535
11432
|
// 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
11433
|
return new Vertex(this.axis).rotate(-this.rotation, this.center).y - this.center.y;
|
|
10538
11434
|
}
|
|
11435
|
+
/**
|
|
11436
|
+
* Get the the minima and maxima (points) of this (rotated) ellipse.
|
|
11437
|
+
*
|
|
11438
|
+
* @method getExtremePoints
|
|
11439
|
+
* @instance
|
|
11440
|
+
* @memberof VEllipse
|
|
11441
|
+
* @return {[Vertex, Vertex, Vertex, Vertex]} Get the the minima and maxima (points) of this (rotated) ellipse.
|
|
11442
|
+
*/
|
|
11443
|
+
getExtremePoints() {
|
|
11444
|
+
const a = this.radiusH();
|
|
11445
|
+
const b = this.radiusV();
|
|
11446
|
+
// Calculate t_x values
|
|
11447
|
+
const t_x1 = Math.atan2(-b * Math.sin(this.rotation), a * Math.cos(this.rotation));
|
|
11448
|
+
const t_x2 = t_x1 + Math.PI;
|
|
11449
|
+
// Calculate x values at t_x
|
|
11450
|
+
const x_x1 = this.center.x + a * Math.cos(t_x1) * Math.cos(this.rotation) - b * Math.sin(t_x1) * Math.sin(this.rotation);
|
|
11451
|
+
const y_x1 = this.center.y + a * Math.cos(t_x1) * Math.sin(this.rotation) + b * Math.sin(t_x1) * Math.cos(this.rotation);
|
|
11452
|
+
const x_x2 = this.center.x + a * Math.cos(t_x2) * Math.cos(this.rotation) - b * Math.sin(t_x2) * Math.sin(this.rotation);
|
|
11453
|
+
const y_x2 = this.center.y + a * Math.cos(t_x2) * Math.sin(this.rotation) + b * Math.sin(t_x2) * Math.cos(this.rotation);
|
|
11454
|
+
let x_max, x_min;
|
|
11455
|
+
if (x_x1 > x_x2) {
|
|
11456
|
+
x_max = new Vertex(x_x1, y_x1);
|
|
11457
|
+
x_min = new Vertex(x_x2, y_x2);
|
|
11458
|
+
}
|
|
11459
|
+
else {
|
|
11460
|
+
x_max = new Vertex(x_x2, y_x2);
|
|
11461
|
+
x_min = new Vertex(x_x1, y_x1);
|
|
11462
|
+
}
|
|
11463
|
+
// Calculate t_y values
|
|
11464
|
+
const t_y1 = Math.atan2(b * Math.cos(this.rotation), a * Math.sin(this.rotation));
|
|
11465
|
+
const t_y2 = t_y1 + Math.PI;
|
|
11466
|
+
// Calculate y values at t_y
|
|
11467
|
+
const x_y1 = this.center.x + a * Math.cos(t_y1) * Math.cos(this.rotation) - b * Math.sin(t_y1) * Math.sin(this.rotation);
|
|
11468
|
+
const y_y1 = this.center.y + a * Math.cos(t_y1) * Math.sin(this.rotation) + b * Math.sin(t_y1) * Math.cos(this.rotation);
|
|
11469
|
+
const x_y2 = this.center.x + a * Math.cos(t_y2) * Math.cos(this.rotation) - b * Math.sin(t_y2) * Math.sin(this.rotation);
|
|
11470
|
+
const y_y2 = this.center.y + a * Math.cos(t_y2) * Math.sin(this.rotation) + b * Math.sin(t_y2) * Math.cos(this.rotation);
|
|
11471
|
+
let y_max, y_min;
|
|
11472
|
+
if (y_y1 > y_y2) {
|
|
11473
|
+
y_max = new Vertex(x_y1, y_y1);
|
|
11474
|
+
y_min = new Vertex(x_y2, y_y2);
|
|
11475
|
+
}
|
|
11476
|
+
else {
|
|
11477
|
+
y_max = new Vertex(x_y2, y_y2);
|
|
11478
|
+
y_min = new Vertex(x_y1, y_y1);
|
|
11479
|
+
}
|
|
11480
|
+
return [x_max, x_min, y_max, y_min];
|
|
11481
|
+
}
|
|
11482
|
+
//--- BEGIN --- Implement interface `IBounded`
|
|
11483
|
+
/**
|
|
11484
|
+
* Get the bounds of this ellipse.
|
|
11485
|
+
*
|
|
11486
|
+
* The bounds are approximated by the underlying segment buffer; the more segment there are,
|
|
11487
|
+
* the more accurate will be the returned bounds.
|
|
11488
|
+
*
|
|
11489
|
+
* @method getBounds
|
|
11490
|
+
* @instance
|
|
11491
|
+
* @memberof VEllipse
|
|
11492
|
+
* @return {Bounds} The bounds of this ellipse.
|
|
11493
|
+
**/
|
|
11494
|
+
getBounds() {
|
|
11495
|
+
// Thanks to Cuixiping
|
|
11496
|
+
// https://stackoverflow.com/questions/87734/how-do-you-calculate-the-axis-aligned-bounding-box-of-an-ellipse
|
|
11497
|
+
const r1 = this.radiusH();
|
|
11498
|
+
const r2 = this.radiusV();
|
|
11499
|
+
const ux = r1 * Math.cos(this.rotation);
|
|
11500
|
+
const uy = r1 * Math.sin(this.rotation);
|
|
11501
|
+
const vx = r2 * Math.cos(this.rotation + Math.PI / 2);
|
|
11502
|
+
const vy = r2 * Math.sin(this.rotation + Math.PI / 2);
|
|
11503
|
+
const bbox_halfwidth = Math.sqrt(ux * ux + vx * vx);
|
|
11504
|
+
const bbox_halfheight = Math.sqrt(uy * uy + vy * vy);
|
|
11505
|
+
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 });
|
|
11506
|
+
}
|
|
11507
|
+
//--- BEGIN --- Implement interface `IBounded`
|
|
11508
|
+
/**
|
|
11509
|
+
* Move the ellipse by the given amount. This is equivalent by moving the `center` and `axis` points.
|
|
11510
|
+
*
|
|
11511
|
+
* @method move
|
|
11512
|
+
* @param {XYCoords} amount - The amount to move.
|
|
11513
|
+
* @instance
|
|
11514
|
+
* @memberof VEllipse
|
|
11515
|
+
* @return {VEllipse} this for chaining
|
|
11516
|
+
**/
|
|
11517
|
+
move(amount) {
|
|
11518
|
+
this.center.add(amount);
|
|
11519
|
+
this.axis.add(amount);
|
|
11520
|
+
return this;
|
|
11521
|
+
}
|
|
10539
11522
|
/**
|
|
10540
11523
|
* Scale this ellipse by the given factor from the center point. The factor will be applied to both radii.
|
|
10541
11524
|
*
|
|
@@ -10593,7 +11576,7 @@ class VEllipse {
|
|
|
10593
11576
|
* @param {number=1.0} length - [optional, default=1] The length of the returned vector.
|
|
10594
11577
|
*/
|
|
10595
11578
|
normalAt(angle, length) {
|
|
10596
|
-
const point = this.vertAt(angle);
|
|
11579
|
+
const point = this.vertAt(angle - this.rotation); // HERE IS THE CORRECT BEHAVIOR!
|
|
10597
11580
|
const foci = this.getFoci();
|
|
10598
11581
|
// Calculate the angle between [point,focusA] and [point,focusB]
|
|
10599
11582
|
const angleA = new Line(point, foci[0]).angle();
|
|
@@ -10605,12 +11588,13 @@ class VEllipse {
|
|
|
10605
11588
|
.addX(50)
|
|
10606
11589
|
.clone()
|
|
10607
11590
|
.rotate(Math.PI + centerAngle, point);
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
|
|
11591
|
+
const resultVector = this.center.distance(endPointA) < this.center.distance(endPointB)
|
|
11592
|
+
? new Vector(point, endPointB)
|
|
11593
|
+
: new Vector(point, endPointA);
|
|
11594
|
+
if (typeof length === "number") {
|
|
11595
|
+
resultVector.setLength(length);
|
|
10613
11596
|
}
|
|
11597
|
+
return resultVector;
|
|
10614
11598
|
}
|
|
10615
11599
|
/**
|
|
10616
11600
|
* Get the tangent vector at the given angle.
|
|
@@ -10629,9 +11613,7 @@ class VEllipse {
|
|
|
10629
11613
|
*/
|
|
10630
11614
|
tangentAt(angle, length) {
|
|
10631
11615
|
const normal = this.normalAt(angle, length);
|
|
10632
|
-
|
|
10633
|
-
normal.b.rotate(Math.PI / 2, normal.a);
|
|
10634
|
-
return normal;
|
|
11616
|
+
return normal.inv().perp();
|
|
10635
11617
|
}
|
|
10636
11618
|
/**
|
|
10637
11619
|
* Get the perimeter of this ellipse.
|
|
@@ -10682,6 +11664,8 @@ class VEllipse {
|
|
|
10682
11664
|
/**
|
|
10683
11665
|
* Get equally distributed points on the outline of this ellipse.
|
|
10684
11666
|
*
|
|
11667
|
+
* @method getEquidistantVertices
|
|
11668
|
+
* @instance
|
|
10685
11669
|
* @param {number} pointCount - The number of points.
|
|
10686
11670
|
* @returns {Array<Vertex>}
|
|
10687
11671
|
*/
|
|
@@ -10693,6 +11677,75 @@ class VEllipse {
|
|
|
10693
11677
|
}
|
|
10694
11678
|
return result;
|
|
10695
11679
|
}
|
|
11680
|
+
//--- BEGIN --- Implement interface `Intersectable`
|
|
11681
|
+
/**
|
|
11682
|
+
* Get the line intersections as vectors with this ellipse.
|
|
11683
|
+
*
|
|
11684
|
+
* @method lineIntersections
|
|
11685
|
+
* @instance
|
|
11686
|
+
* @param {VertTuple<Vector> ray - The line/ray to intersect this ellipse with.
|
|
11687
|
+
* @param {boolean} inVectorBoundsOnly - (default=false) Set to true if only intersections within the vector bounds are of interest.
|
|
11688
|
+
* @returns
|
|
11689
|
+
*/
|
|
11690
|
+
lineIntersections(ray, inVectorBoundsOnly = false) {
|
|
11691
|
+
// Question: what happens to extreme versions when ellipse is a line (width or height is zero)?
|
|
11692
|
+
// This would result in a Division_by_Zero exception!
|
|
11693
|
+
// Step A: create clones for operations (keep originals unchanged)
|
|
11694
|
+
const ellipseCopy = this.clone(); // VEllipse
|
|
11695
|
+
const rayCopy = ray.clone(); // Vector
|
|
11696
|
+
// Step B: move both so ellipse's center is located at (0,0)
|
|
11697
|
+
const moveAmount = ellipseCopy.center.clone().inv();
|
|
11698
|
+
ellipseCopy.move(moveAmount);
|
|
11699
|
+
rayCopy.add(moveAmount);
|
|
11700
|
+
// Step C: rotate eclipse backwards it's rotation, so that rotation is zero (0.0).
|
|
11701
|
+
// Rotate together with ray!
|
|
11702
|
+
const rotationAmount = -ellipseCopy.rotation;
|
|
11703
|
+
ellipseCopy.rotate(rotationAmount); // Rotation around (0,0) = center of translated ellipse
|
|
11704
|
+
rayCopy.a.rotate(rotationAmount, ellipseCopy.center);
|
|
11705
|
+
rayCopy.b.rotate(rotationAmount, ellipseCopy.center);
|
|
11706
|
+
// Step D: find x/y factors to use for scaling to transform the ellipse to a circle.
|
|
11707
|
+
// Scale together with vector ray.
|
|
11708
|
+
const radiusH = ellipseCopy.radiusH();
|
|
11709
|
+
const radiusV = ellipseCopy.radiusV();
|
|
11710
|
+
const scalingFactors = radiusH > radiusV ? { x: radiusV / radiusH, y: 1.0 } : { x: 1.0, y: radiusH / radiusV };
|
|
11711
|
+
// Step E: scale ellipse AND ray by calculated factors.
|
|
11712
|
+
ellipseCopy.axis.scaleXY(scalingFactors);
|
|
11713
|
+
rayCopy.a.scaleXY(scalingFactors);
|
|
11714
|
+
rayCopy.b.scaleXY(scalingFactors);
|
|
11715
|
+
// Intermediate result: now the ellipse is transformed to a circle and we can calculate intersections :)
|
|
11716
|
+
// Step F: calculate circle+line intersecions
|
|
11717
|
+
const tmpCircle = new Circle(new Vertex(), ellipseCopy.radiusH()); // radiusH() === radiusV()
|
|
11718
|
+
const intersections = tmpCircle.lineIntersections(rayCopy, inVectorBoundsOnly);
|
|
11719
|
+
// Step G: transform intersecions back to original configuration
|
|
11720
|
+
intersections.forEach(function (intersectionPoint) {
|
|
11721
|
+
// Reverse transformation from above.
|
|
11722
|
+
intersectionPoint.scaleXY({ x: 1 / scalingFactors.x, y: 1 / scalingFactors.y }, ellipseCopy.center);
|
|
11723
|
+
intersectionPoint.rotate(-rotationAmount, ellipseCopy.center);
|
|
11724
|
+
intersectionPoint.sub(moveAmount);
|
|
11725
|
+
});
|
|
11726
|
+
return intersections;
|
|
11727
|
+
}
|
|
11728
|
+
/**
|
|
11729
|
+
* Get all line intersections of this polygon and their tangents along the shape.
|
|
11730
|
+
*
|
|
11731
|
+
* This method returns all intersection tangents (as vectors) with this shape. The returned array of vectors is in no specific order.
|
|
11732
|
+
*
|
|
11733
|
+
* @param line
|
|
11734
|
+
* @param lineIntersectionTangents
|
|
11735
|
+
* @returns
|
|
11736
|
+
*/
|
|
11737
|
+
lineIntersectionTangents(line, inVectorBoundsOnly = false) {
|
|
11738
|
+
// Find the intersections of all lines plus their tangents inside the circle bounds
|
|
11739
|
+
const interSectionPoints = this.lineIntersections(line, inVectorBoundsOnly);
|
|
11740
|
+
return interSectionPoints.map((vert) => {
|
|
11741
|
+
// Calculate angle
|
|
11742
|
+
const lineFromCenter = new Line(this.center, vert);
|
|
11743
|
+
const angle = lineFromCenter.angle();
|
|
11744
|
+
// Calculate tangent at angle
|
|
11745
|
+
return this.tangentAt(angle);
|
|
11746
|
+
});
|
|
11747
|
+
}
|
|
11748
|
+
//--- END --- Implement interface `Intersectable`
|
|
10696
11749
|
/**
|
|
10697
11750
|
* Convert this ellipse into cubic Bézier curves.
|
|
10698
11751
|
*
|
|
@@ -10705,7 +11758,7 @@ class VEllipse {
|
|
|
10705
11758
|
// Math by Luc Maisonobe
|
|
10706
11759
|
// http://www.spaceroots.org/documents/ellipse/node22.html
|
|
10707
11760
|
// 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)
|
|
11761
|
+
// Return a single line here (as a Bézier curve)?
|
|
10709
11762
|
// if (Math.abs(this.radiusV()) < 0.00001) {
|
|
10710
11763
|
// const radiusH = this.radiusH();
|
|
10711
11764
|
// return [
|
|
@@ -10735,10 +11788,10 @@ class VEllipse {
|
|
|
10735
11788
|
const radiusV = this.radiusV();
|
|
10736
11789
|
const curves = [];
|
|
10737
11790
|
const angles = VEllipse.utils.equidistantVertAngles(radiusH, radiusV, segmentCount);
|
|
10738
|
-
let curAngle = angles[0];
|
|
11791
|
+
let curAngle = angles[0] + this.rotation;
|
|
10739
11792
|
let startPoint = this.vertAt(curAngle);
|
|
10740
11793
|
for (var i = 0; i < angles.length; i++) {
|
|
10741
|
-
let nextAngle = angles[(i + 1) % angles.length];
|
|
11794
|
+
let nextAngle = angles[(i + 1) % angles.length] + this.rotation;
|
|
10742
11795
|
let endPoint = this.vertAt(nextAngle);
|
|
10743
11796
|
if (Math.abs(radiusV) < 0.0001 || Math.abs(radiusH) < 0.0001) {
|
|
10744
11797
|
// Distorted ellipses can only be approximated by linear Bézier segments
|
|
@@ -10747,8 +11800,8 @@ class VEllipse {
|
|
|
10747
11800
|
curves.push(curve);
|
|
10748
11801
|
}
|
|
10749
11802
|
else {
|
|
10750
|
-
let startTangent = this.tangentAt(curAngle);
|
|
10751
|
-
let endTangent = this.tangentAt(nextAngle);
|
|
11803
|
+
let startTangent = this.tangentAt(curAngle + this.rotation);
|
|
11804
|
+
let endTangent = this.tangentAt(nextAngle + this.rotation);
|
|
10752
11805
|
// Find intersection (ignore that the result might be null in some extreme cases)
|
|
10753
11806
|
let intersection = startTangent.intersection(endTangent);
|
|
10754
11807
|
// What if intersection is undefined?
|
|
@@ -10840,7 +11893,14 @@ VEllipse.utils = {
|
|
|
10840
11893
|
* @date 2021-02-26
|
|
10841
11894
|
* @modified 2022-02-02 Added the `destroy` method.
|
|
10842
11895
|
* @modified 2022-11-01 Tweaked the `endpointToCenterParameters` function to handle negative values, too, without errors.
|
|
10843
|
-
* @
|
|
11896
|
+
* @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).
|
|
11897
|
+
* @modified 2025-04-02 Adding `VEllipseSector.containsAngle` method.
|
|
11898
|
+
* @modified 2025-04-02 Adding `VEllipseSector.lineIntersections` and `VEllipseSector.lineIntersectionTangents` and implementing `Intersectable`.
|
|
11899
|
+
* @modified 2025-04-07 Adding value wrapping (0 to TWO_PI) to the `VEllipseSector.containsAngle` method.
|
|
11900
|
+
* @modified 2025-04-09 Adding the `VEllipseSector.move` method.
|
|
11901
|
+
* @modified 2025-04-19 Added the `VEllipseSector.getStartPoint` and `getEndPoint` methods.
|
|
11902
|
+
* @modified 2025-04-23 Added the `VEllipseSector.getBounds` method.
|
|
11903
|
+
* @version 1.2.0
|
|
10844
11904
|
*/
|
|
10845
11905
|
/**
|
|
10846
11906
|
* @classdesc A class for elliptic sectors.
|
|
@@ -10876,6 +11936,137 @@ class VEllipseSector {
|
|
|
10876
11936
|
this.startAngle = geomutils.wrapMinMax(startAngle, 0, Math.PI * 2);
|
|
10877
11937
|
this.endAngle = geomutils.wrapMinMax(endAngle, 0, Math.PI * 2);
|
|
10878
11938
|
}
|
|
11939
|
+
/**
|
|
11940
|
+
* Move the ellipse sector by the given amount.
|
|
11941
|
+
*
|
|
11942
|
+
* @method move
|
|
11943
|
+
* @param {XYCoords} amount - The amount to move.
|
|
11944
|
+
* @instance
|
|
11945
|
+
* @memberof VEllipseSector
|
|
11946
|
+
* @return {VEllipseSector} this for chaining
|
|
11947
|
+
**/
|
|
11948
|
+
move(amount) {
|
|
11949
|
+
this.ellipse.move(amount);
|
|
11950
|
+
return this;
|
|
11951
|
+
}
|
|
11952
|
+
/**
|
|
11953
|
+
* Checks wether the given angle (must be inside 0 and PI*2) is contained inside this sector.
|
|
11954
|
+
*
|
|
11955
|
+
* @param {number} angle - The numeric angle to check.
|
|
11956
|
+
* @method containsAngle
|
|
11957
|
+
* @instance
|
|
11958
|
+
* @memberof VEllipseSectpr
|
|
11959
|
+
* @return {boolean} True if (and only if) this sector contains the given angle.
|
|
11960
|
+
*/
|
|
11961
|
+
containsAngle(angle) {
|
|
11962
|
+
angle = geomutils.mapAngleTo2PI(angle); // wrapMinMax(angle, 0, Math.PI * 2);
|
|
11963
|
+
const sAngle = geomutils.mapAngleTo2PI(this.startAngle);
|
|
11964
|
+
const eAngle = geomutils.mapAngleTo2PI(this.endAngle);
|
|
11965
|
+
// TODO: cleanup
|
|
11966
|
+
// if (this.startAngle <= this.endAngle) {
|
|
11967
|
+
// return angle >= this.startAngle && angle < this.endAngle;
|
|
11968
|
+
// } else {
|
|
11969
|
+
// // startAngle > endAngle
|
|
11970
|
+
// return angle >= this.startAngle || angle < this.endAngle;
|
|
11971
|
+
// }
|
|
11972
|
+
if (sAngle <= eAngle) {
|
|
11973
|
+
return angle >= sAngle && angle < eAngle;
|
|
11974
|
+
}
|
|
11975
|
+
else {
|
|
11976
|
+
// startAngle > endAngle
|
|
11977
|
+
return angle >= sAngle || angle < eAngle;
|
|
11978
|
+
}
|
|
11979
|
+
}
|
|
11980
|
+
/**
|
|
11981
|
+
* Get the sectors starting point (on the underlying ellipse, located at the start angle).
|
|
11982
|
+
*
|
|
11983
|
+
* @method getStartPoint
|
|
11984
|
+
* @instance
|
|
11985
|
+
* @memberof VEllipseSector
|
|
11986
|
+
* @return {Vertex} The sector's stating point.
|
|
11987
|
+
*/
|
|
11988
|
+
getStartPoint() {
|
|
11989
|
+
return this.ellipse.vertAt(this.startAngle);
|
|
11990
|
+
}
|
|
11991
|
+
/**
|
|
11992
|
+
* Get the sectors ending point (on the underlying ellipse, located at the end angle).
|
|
11993
|
+
*
|
|
11994
|
+
* @method getEndPoint
|
|
11995
|
+
* @instance
|
|
11996
|
+
* @memberof VEllipseSector
|
|
11997
|
+
* @return {Vertex} The sector's ending point.
|
|
11998
|
+
*/
|
|
11999
|
+
getEndPoint() {
|
|
12000
|
+
return this.ellipse.vertAt(this.endAngle);
|
|
12001
|
+
}
|
|
12002
|
+
//--- BEGIN --- Implement interface `IBounded`
|
|
12003
|
+
/**
|
|
12004
|
+
* Get the bounds of this elliptic sector.
|
|
12005
|
+
*
|
|
12006
|
+
* The bounds are approximated by the underlying segment buffer; the more segment there are,
|
|
12007
|
+
* the more accurate will be the returned bounds.
|
|
12008
|
+
*
|
|
12009
|
+
* @method getBounds
|
|
12010
|
+
* @instance
|
|
12011
|
+
* @memberof VEllipse
|
|
12012
|
+
* @return {Bounds} The bounds of this elliptic sector.
|
|
12013
|
+
**/
|
|
12014
|
+
getBounds() {
|
|
12015
|
+
// Calculage angles from east, west, north and south box points and check if they are inside
|
|
12016
|
+
const extremes = this.ellipse.getExtremePoints();
|
|
12017
|
+
const candidates = extremes.filter(point => {
|
|
12018
|
+
const angle = new Line(this.ellipse.center, point).angle() - this.ellipse.rotation;
|
|
12019
|
+
return this.containsAngle(angle);
|
|
12020
|
+
});
|
|
12021
|
+
return Bounds.computeFromVertices([this.getStartPoint(), this.getEndPoint()].concat(candidates));
|
|
12022
|
+
}
|
|
12023
|
+
//--- BEGIN --- Implement interface `Intersectable`
|
|
12024
|
+
/**
|
|
12025
|
+
* Get the line intersections as vectors with this ellipse.
|
|
12026
|
+
*
|
|
12027
|
+
* @method lineIntersections
|
|
12028
|
+
* @instance
|
|
12029
|
+
* @memberof VEllipseSectpr
|
|
12030
|
+
* @param {VertTuple<Vector>} ray - The line/ray to intersect this ellipse with.
|
|
12031
|
+
* @param {boolean} inVectorBoundsOnly - (default=false) Set to true if only intersections within the vector bounds are of interest.
|
|
12032
|
+
* @returns
|
|
12033
|
+
*/
|
|
12034
|
+
lineIntersections(ray, inVectorBoundsOnly = false) {
|
|
12035
|
+
// First get all line intersections from underlying ellipse.
|
|
12036
|
+
const ellipseIntersections = this.ellipse.lineIntersections(ray, inVectorBoundsOnly);
|
|
12037
|
+
// Drop all intersection points that are not contained in the circle sectors bounds.
|
|
12038
|
+
const tmpLine = new Line(this.ellipse.center, new Vertex());
|
|
12039
|
+
return ellipseIntersections.filter((intersectionPoint) => {
|
|
12040
|
+
tmpLine.b.set(intersectionPoint);
|
|
12041
|
+
const lineAngle = tmpLine.angle();
|
|
12042
|
+
return this.containsAngle(lineAngle - this.ellipse.rotation);
|
|
12043
|
+
});
|
|
12044
|
+
}
|
|
12045
|
+
/**
|
|
12046
|
+
* Get all line intersections of this polygon and their tangents along the shape.
|
|
12047
|
+
*
|
|
12048
|
+
* This method returns all intersection tangents (as vectors) with this shape. The returned array of vectors is in no specific order.
|
|
12049
|
+
*
|
|
12050
|
+
* @method lineIntersections
|
|
12051
|
+
* @memberof VEllipseSectpr
|
|
12052
|
+
* @param line
|
|
12053
|
+
* @param lineIntersectionTangents
|
|
12054
|
+
* @returns
|
|
12055
|
+
*/
|
|
12056
|
+
lineIntersectionTangents(line, inVectorBoundsOnly = false) {
|
|
12057
|
+
// Find the intersections of all lines plus their tangents inside the circle bounds
|
|
12058
|
+
const interSectionPoints = this.lineIntersections(line, inVectorBoundsOnly);
|
|
12059
|
+
return interSectionPoints.map((vert) => {
|
|
12060
|
+
// Calculate angle
|
|
12061
|
+
const lineFromCenter = new Line(this.ellipse.center, vert);
|
|
12062
|
+
const angle = lineFromCenter.angle();
|
|
12063
|
+
// console.log("angle", (angle / Math.PI) * 180.0);
|
|
12064
|
+
// const angle = Math.random() * Math.PI * 2; // TODO
|
|
12065
|
+
// Calculate tangent at angle
|
|
12066
|
+
return this.ellipse.tangentAt(angle);
|
|
12067
|
+
});
|
|
12068
|
+
}
|
|
12069
|
+
//--- END --- Implement interface `Intersectable`
|
|
10879
12070
|
/**
|
|
10880
12071
|
* Convert this elliptic sector into cubic Bézier curves.
|
|
10881
12072
|
*
|
|
@@ -10903,8 +12094,8 @@ class VEllipseSector {
|
|
|
10903
12094
|
for (var i = 0; i + 1 < angles.length; i++) {
|
|
10904
12095
|
let nextAngle = angles[(i + 1) % angles.length];
|
|
10905
12096
|
let endPoint = this.ellipse.vertAt(nextAngle);
|
|
10906
|
-
let startTangent = this.ellipse.tangentAt(curAngle);
|
|
10907
|
-
let endTangent = this.ellipse.tangentAt(nextAngle);
|
|
12097
|
+
let startTangent = this.ellipse.tangentAt(curAngle + this.ellipse.rotation);
|
|
12098
|
+
let endTangent = this.ellipse.tangentAt(nextAngle + this.ellipse.rotation);
|
|
10908
12099
|
// Distorted ellipses can only be approximated by linear Bézier segments
|
|
10909
12100
|
if (Math.abs(radiusV) < 0.0001 || Math.abs(radiusH) < 0.0001) {
|
|
10910
12101
|
let diff = startPoint.difference(endPoint);
|
|
@@ -11008,20 +12199,20 @@ VEllipseSector.ellipseSectorUtils = {
|
|
|
11008
12199
|
equidistantVertAngles: (radiusH, radiusV, startAngle, endAngle, fullEllipsePointCount) => {
|
|
11009
12200
|
var ellipseAngles = VEllipse.utils.equidistantVertAngles(radiusH, radiusV, fullEllipsePointCount);
|
|
11010
12201
|
ellipseAngles = ellipseAngles.map((angle) => VEllipseSector.ellipseSectorUtils.normalizeAngle(angle));
|
|
11011
|
-
|
|
12202
|
+
const angleIsInRange = (angle) => {
|
|
11012
12203
|
if (startAngle < endAngle)
|
|
11013
12204
|
return angle >= startAngle && angle <= endAngle;
|
|
11014
12205
|
else
|
|
11015
12206
|
return angle >= startAngle || (angle <= endAngle && angle >= 0);
|
|
11016
12207
|
};
|
|
11017
12208
|
// Drop all angles outside the sector
|
|
11018
|
-
|
|
12209
|
+
ellipseAngles = ellipseAngles.filter(angleIsInRange);
|
|
11019
12210
|
// Now we need to sort the angles to the first one in the array is the closest to startAngle.
|
|
11020
12211
|
// --> find the angle that is closest to the start angle
|
|
11021
|
-
|
|
12212
|
+
const startIndex = VEllipseSector.ellipseSectorUtils.findClosestToStartAngle(startAngle, endAngle, ellipseAngles);
|
|
11022
12213
|
// Bring all angles into the correct order
|
|
11023
12214
|
// Idea: use splice or slice here?
|
|
11024
|
-
|
|
12215
|
+
const angles = [];
|
|
11025
12216
|
for (var i = 0; i < ellipseAngles.length; i++) {
|
|
11026
12217
|
angles.push(ellipseAngles[(startIndex + i) % ellipseAngles.length]);
|
|
11027
12218
|
}
|
|
@@ -11193,8 +12384,13 @@ VEllipseSector.ellipseSectorUtils = {
|
|
|
11193
12384
|
* @modified 2024-07-08 Adding `PlotBoilerplate.getGUI()` to retrieve the GUI instance.
|
|
11194
12385
|
* @modified 2024-08-25 Extending main class `PlotBoilerplate` optional param `isBackdropFiltersEnabled`.
|
|
11195
12386
|
* @modified 2024-12-02 Adding the `triggerRedraw` to the `removeAll` method.
|
|
12387
|
+
* @modified 2025-05-07 Changing the return type of `removeVertex` from `void` to `boolean`.
|
|
12388
|
+
* @modified 2025-05-07 Handling content changes now with `contentChangeListeners`.
|
|
12389
|
+
* @modified 2025-05-07 Added `PlogBoilerplate.addContentChangeListener` and `.removeContentChangeListener`.
|
|
12390
|
+
* @modified 2025-05-07 Moving full vectors now by default when vector point a is moved.
|
|
12391
|
+
* @modified 2025-05-20 Applying `lineWith` parameter in the draw routine for vectors (had been missing).
|
|
11196
12392
|
*
|
|
11197
|
-
* @version 1.
|
|
12393
|
+
* @version 1.21.1
|
|
11198
12394
|
*
|
|
11199
12395
|
* @file PlotBoilerplate
|
|
11200
12396
|
* @fileoverview The main class.
|
|
@@ -11297,6 +12493,10 @@ class PlotBoilerplate {
|
|
|
11297
12493
|
*/
|
|
11298
12494
|
constructor(config, drawConfig) {
|
|
11299
12495
|
var _b, _c;
|
|
12496
|
+
/**
|
|
12497
|
+
* A list of content change listeners.
|
|
12498
|
+
*/
|
|
12499
|
+
this.contentChangeListeners = [];
|
|
11300
12500
|
/**
|
|
11301
12501
|
* A discrete timestamp to identify single render cycles.
|
|
11302
12502
|
* Note that using system time milliseconds is not a safe way to identify render frames, as on modern powerful machines
|
|
@@ -11684,6 +12884,52 @@ class PlotBoilerplate {
|
|
|
11684
12884
|
PlotBoilerplate.utils.setCSSscale(this.canvas, (_d = this.config.cssScaleX) !== null && _d !== void 0 ? _d : 1.0, (_e = this.config.cssScaleY) !== null && _e !== void 0 ? _e : 1.0);
|
|
11685
12885
|
}
|
|
11686
12886
|
}
|
|
12887
|
+
/**
|
|
12888
|
+
* Adds a new content change listener to this instance. Adding duplicates is not possible.
|
|
12889
|
+
*
|
|
12890
|
+
* @param {PBContentChangeListener} listener - The listenre to add.
|
|
12891
|
+
* @method addContentChangeListener
|
|
12892
|
+
* @instance
|
|
12893
|
+
* @memberof PlotBoilerplate
|
|
12894
|
+
* @returns {void}
|
|
12895
|
+
*/
|
|
12896
|
+
addContentChangeListener(listener) {
|
|
12897
|
+
for (var i in this.contentChangeListeners) {
|
|
12898
|
+
if (this.contentChangeListeners[i] === listener) {
|
|
12899
|
+
return;
|
|
12900
|
+
}
|
|
12901
|
+
}
|
|
12902
|
+
this.contentChangeListeners.push(listener);
|
|
12903
|
+
}
|
|
12904
|
+
/**
|
|
12905
|
+
* Removes an existing content change listener from this instance.
|
|
12906
|
+
*
|
|
12907
|
+
* @param {PBContentChangeListener} listener - The listenre to add.
|
|
12908
|
+
* @method removeContentChangeListener
|
|
12909
|
+
* @instance
|
|
12910
|
+
* @memberof PlotBoilerplate
|
|
12911
|
+
* @returns {void}
|
|
12912
|
+
*/
|
|
12913
|
+
removeContentChangeListener(listener) {
|
|
12914
|
+
for (var i = 0; i < this.contentChangeListeners.length; i++) {
|
|
12915
|
+
if (this.contentChangeListeners[i] === listener) {
|
|
12916
|
+
this.contentChangeListeners.splice(i, 1);
|
|
12917
|
+
return;
|
|
12918
|
+
}
|
|
12919
|
+
}
|
|
12920
|
+
}
|
|
12921
|
+
_fireContentChanged(addedDrawables, removedDrawables) {
|
|
12922
|
+
for (var i in this.contentChangeListeners) {
|
|
12923
|
+
const listener = this.contentChangeListeners[i];
|
|
12924
|
+
if (listener && typeof listener === "function") {
|
|
12925
|
+
listener({
|
|
12926
|
+
type: addedDrawables.length > 0 ? "DRAWABLES_ADDED" : "DRAWABLES_REMOVED",
|
|
12927
|
+
addedDrawables: addedDrawables,
|
|
12928
|
+
removedDrawables: removedDrawables
|
|
12929
|
+
});
|
|
12930
|
+
}
|
|
12931
|
+
}
|
|
12932
|
+
}
|
|
11687
12933
|
/**
|
|
11688
12934
|
* Add a drawable object.<br>
|
|
11689
12935
|
* <br>
|
|
@@ -11708,100 +12954,121 @@ class PlotBoilerplate {
|
|
|
11708
12954
|
* @memberof PlotBoilerplate
|
|
11709
12955
|
* @return {void}
|
|
11710
12956
|
**/
|
|
11711
|
-
add(drawable, redraw) {
|
|
12957
|
+
add(drawable, redraw, doNotFireEvent) {
|
|
11712
12958
|
if (Array.isArray(drawable)) {
|
|
11713
12959
|
const arr = drawable;
|
|
11714
12960
|
for (var i = 0; i < arr.length; i++) {
|
|
11715
|
-
this.add(arr[i], false);
|
|
12961
|
+
this.add(arr[i], false, doNotFireEvent);
|
|
11716
12962
|
}
|
|
12963
|
+
// !doNotFireEvent && this._fireContentChanged(arr, []);
|
|
11717
12964
|
}
|
|
11718
|
-
else
|
|
11719
|
-
|
|
11720
|
-
|
|
11721
|
-
|
|
11722
|
-
|
|
11723
|
-
// Add some lines
|
|
11724
|
-
this.drawables.push(drawable);
|
|
11725
|
-
this.vertices.push(drawable.a);
|
|
11726
|
-
this.vertices.push(drawable.b);
|
|
11727
|
-
}
|
|
11728
|
-
else if (drawable instanceof Vector) {
|
|
11729
|
-
this.drawables.push(drawable);
|
|
11730
|
-
this.vertices.push(drawable.a);
|
|
11731
|
-
this.vertices.push(drawable.b);
|
|
11732
|
-
}
|
|
11733
|
-
else if (drawable instanceof VEllipse) {
|
|
11734
|
-
this.vertices.push(drawable.center);
|
|
11735
|
-
this.vertices.push(drawable.axis);
|
|
11736
|
-
this.drawables.push(drawable);
|
|
11737
|
-
drawable.center.listeners.addDragListener((event) => {
|
|
11738
|
-
drawable.axis.add(event.params.dragAmount);
|
|
11739
|
-
});
|
|
11740
|
-
}
|
|
11741
|
-
else if (drawable instanceof VEllipseSector) {
|
|
11742
|
-
this.vertices.push(drawable.ellipse.center);
|
|
11743
|
-
this.vertices.push(drawable.ellipse.axis);
|
|
11744
|
-
this.drawables.push(drawable);
|
|
11745
|
-
drawable.ellipse.center.listeners.addDragListener((event) => {
|
|
11746
|
-
drawable.ellipse.axis.add(event.params.dragAmount);
|
|
11747
|
-
});
|
|
11748
|
-
}
|
|
11749
|
-
else if (drawable instanceof Circle) {
|
|
11750
|
-
this.vertices.push(drawable.center);
|
|
11751
|
-
this.drawables.push(drawable);
|
|
11752
|
-
}
|
|
11753
|
-
else if (drawable instanceof CircleSector) {
|
|
11754
|
-
this.vertices.push(drawable.circle.center);
|
|
11755
|
-
this.drawables.push(drawable);
|
|
11756
|
-
}
|
|
11757
|
-
else if (drawable instanceof Polygon) {
|
|
11758
|
-
this.drawables.push(drawable);
|
|
11759
|
-
for (var i = 0; i < drawable.vertices.length; i++) {
|
|
11760
|
-
this.vertices.push(drawable.vertices[i]);
|
|
12965
|
+
else {
|
|
12966
|
+
const addedDrawables = [drawable];
|
|
12967
|
+
if (drawable instanceof Vertex) {
|
|
12968
|
+
this.drawables.push(drawable);
|
|
12969
|
+
this.vertices.push(drawable);
|
|
11761
12970
|
}
|
|
11762
|
-
|
|
11763
|
-
|
|
11764
|
-
|
|
11765
|
-
|
|
11766
|
-
|
|
11767
|
-
|
|
11768
|
-
|
|
11769
|
-
|
|
11770
|
-
|
|
11771
|
-
|
|
11772
|
-
|
|
11773
|
-
|
|
11774
|
-
|
|
12971
|
+
else if (drawable instanceof Line) {
|
|
12972
|
+
// Add some lines
|
|
12973
|
+
this.drawables.push(drawable);
|
|
12974
|
+
this.vertices.push(drawable.a);
|
|
12975
|
+
this.vertices.push(drawable.b);
|
|
12976
|
+
addedDrawables.push(drawable.a, drawable.b);
|
|
12977
|
+
}
|
|
12978
|
+
else if (drawable instanceof Vector) {
|
|
12979
|
+
this.drawables.push(drawable);
|
|
12980
|
+
this.vertices.push(drawable.a);
|
|
12981
|
+
this.vertices.push(drawable.b);
|
|
12982
|
+
addedDrawables.push(drawable.a, drawable.b);
|
|
12983
|
+
drawable.a.listeners.addDragListener((event) => {
|
|
12984
|
+
drawable.b.add(event.params.dragAmount);
|
|
12985
|
+
});
|
|
12986
|
+
}
|
|
12987
|
+
else if (drawable instanceof VEllipse) {
|
|
12988
|
+
this.vertices.push(drawable.center);
|
|
12989
|
+
this.vertices.push(drawable.axis);
|
|
12990
|
+
addedDrawables.push(drawable.center, drawable.axis);
|
|
12991
|
+
this.drawables.push(drawable);
|
|
12992
|
+
drawable.center.listeners.addDragListener((event) => {
|
|
12993
|
+
drawable.axis.add(event.params.dragAmount);
|
|
12994
|
+
});
|
|
12995
|
+
}
|
|
12996
|
+
else if (drawable instanceof VEllipseSector) {
|
|
12997
|
+
this.vertices.push(drawable.ellipse.center);
|
|
12998
|
+
this.vertices.push(drawable.ellipse.axis);
|
|
12999
|
+
addedDrawables.push(drawable.ellipse.center, drawable.ellipse.axis);
|
|
13000
|
+
this.drawables.push(drawable);
|
|
13001
|
+
drawable.ellipse.center.listeners.addDragListener((event) => {
|
|
13002
|
+
drawable.ellipse.axis.add(event.params.dragAmount);
|
|
13003
|
+
});
|
|
13004
|
+
}
|
|
13005
|
+
else if (drawable instanceof Circle) {
|
|
13006
|
+
this.vertices.push(drawable.center);
|
|
13007
|
+
addedDrawables.push(drawable.center);
|
|
13008
|
+
this.drawables.push(drawable);
|
|
13009
|
+
}
|
|
13010
|
+
else if (drawable instanceof CircleSector) {
|
|
13011
|
+
this.vertices.push(drawable.circle.center);
|
|
13012
|
+
addedDrawables.push(drawable.circle.center);
|
|
13013
|
+
this.drawables.push(drawable);
|
|
13014
|
+
}
|
|
13015
|
+
else if (drawable instanceof Polygon) {
|
|
13016
|
+
this.drawables.push(drawable);
|
|
13017
|
+
for (var i = 0; i < drawable.vertices.length; i++) {
|
|
13018
|
+
this.vertices.push(drawable.vertices[i]);
|
|
13019
|
+
addedDrawables.push(drawable.vertices[i]);
|
|
11775
13020
|
}
|
|
11776
|
-
this.vertices.push(bezierPath.bezierCurves[i].endPoint);
|
|
11777
|
-
this.vertices.push(bezierPath.bezierCurves[i].startControlPoint);
|
|
11778
|
-
this.vertices.push(bezierPath.bezierCurves[i].endControlPoint);
|
|
11779
|
-
bezierPath.bezierCurves[i].startControlPoint.attr.selectable = false;
|
|
11780
|
-
bezierPath.bezierCurves[i].endControlPoint.attr.selectable = false;
|
|
11781
13021
|
}
|
|
11782
|
-
|
|
11783
|
-
|
|
11784
|
-
|
|
11785
|
-
|
|
11786
|
-
|
|
11787
|
-
|
|
11788
|
-
|
|
11789
|
-
drawable
|
|
11790
|
-
|
|
11791
|
-
|
|
11792
|
-
|
|
11793
|
-
|
|
11794
|
-
|
|
11795
|
-
|
|
11796
|
-
|
|
11797
|
-
|
|
11798
|
-
|
|
11799
|
-
|
|
11800
|
-
|
|
13022
|
+
else if (drawable instanceof Triangle) {
|
|
13023
|
+
this.drawables.push(drawable);
|
|
13024
|
+
this.vertices.push(drawable.a);
|
|
13025
|
+
this.vertices.push(drawable.b);
|
|
13026
|
+
this.vertices.push(drawable.c);
|
|
13027
|
+
addedDrawables.push(drawable.a, drawable.b, drawable.c);
|
|
13028
|
+
}
|
|
13029
|
+
else if (drawable instanceof BezierPath) {
|
|
13030
|
+
this.drawables.push(drawable);
|
|
13031
|
+
const bezierPath = drawable;
|
|
13032
|
+
for (var i = 0; i < bezierPath.bezierCurves.length; i++) {
|
|
13033
|
+
if (!drawable.adjustCircular && i == 0) {
|
|
13034
|
+
this.vertices.push(bezierPath.bezierCurves[i].startPoint);
|
|
13035
|
+
addedDrawables.push(bezierPath.bezierCurves[i].startPoint);
|
|
13036
|
+
}
|
|
13037
|
+
this.vertices.push(bezierPath.bezierCurves[i].endPoint);
|
|
13038
|
+
this.vertices.push(bezierPath.bezierCurves[i].startControlPoint);
|
|
13039
|
+
this.vertices.push(bezierPath.bezierCurves[i].endControlPoint);
|
|
13040
|
+
addedDrawables.push(bezierPath.bezierCurves[i].endPoint, bezierPath.bezierCurves[i].startControlPoint, bezierPath.bezierCurves[i].endControlPoint);
|
|
13041
|
+
bezierPath.bezierCurves[i].startControlPoint.attr.selectable = false;
|
|
13042
|
+
bezierPath.bezierCurves[i].endControlPoint.attr.selectable = false;
|
|
13043
|
+
}
|
|
13044
|
+
PlotBoilerplate.utils.enableBezierPathAutoAdjust(drawable);
|
|
13045
|
+
}
|
|
13046
|
+
else if (drawable instanceof PBImage) {
|
|
13047
|
+
this.vertices.push(drawable.upperLeft);
|
|
13048
|
+
this.vertices.push(drawable.lowerRight);
|
|
13049
|
+
addedDrawables.push(drawable.upperLeft, drawable.lowerRight);
|
|
13050
|
+
this.drawables.push(drawable);
|
|
13051
|
+
// Todo: think about a IDragEvent interface
|
|
13052
|
+
drawable.upperLeft.listeners.addDragListener((e) => {
|
|
13053
|
+
drawable.lowerRight.add(e.params.dragAmount);
|
|
13054
|
+
});
|
|
13055
|
+
drawable.lowerRight.attr.selectable = false;
|
|
13056
|
+
}
|
|
13057
|
+
else if (drawable instanceof PBText) {
|
|
13058
|
+
this.vertices.push(drawable.anchor);
|
|
13059
|
+
addedDrawables.push(drawable.anchor);
|
|
13060
|
+
this.drawables.push(drawable);
|
|
13061
|
+
drawable.anchor.attr.selectable = false;
|
|
13062
|
+
}
|
|
13063
|
+
else {
|
|
13064
|
+
throw "Cannot add drawable of unrecognized type: " + typeof drawable + ".";
|
|
13065
|
+
}
|
|
13066
|
+
!doNotFireEvent && this._fireContentChanged(addedDrawables, []);
|
|
11801
13067
|
}
|
|
11802
13068
|
// This is a workaround for backwards compatibility when the 'redraw' param was not yet present.
|
|
11803
|
-
if (redraw || typeof redraw == "undefined")
|
|
13069
|
+
if (redraw || typeof redraw == "undefined") {
|
|
11804
13070
|
this.redraw();
|
|
13071
|
+
}
|
|
11805
13072
|
}
|
|
11806
13073
|
/**
|
|
11807
13074
|
* Remove a drawable object.<br>
|
|
@@ -11821,88 +13088,113 @@ class PlotBoilerplate {
|
|
|
11821
13088
|
*
|
|
11822
13089
|
* @param {Drawable|Array<Drawable>} drawable - The drawable (of one of the allowed class instance) to remove.
|
|
11823
13090
|
* @param {boolean} [redraw=false]
|
|
13091
|
+
* @param {removeWidth}
|
|
11824
13092
|
* @method remove
|
|
11825
13093
|
* @instance
|
|
11826
13094
|
* @memberof PlotBoilerplate
|
|
11827
13095
|
* @return {void}
|
|
11828
13096
|
**/
|
|
11829
|
-
remove(drawable, redraw, removeWithVertices) {
|
|
13097
|
+
remove(drawable, redraw, removeWithVertices, doNotFireEvent) {
|
|
11830
13098
|
if (Array.isArray(drawable)) {
|
|
13099
|
+
const removedDrawables = [];
|
|
11831
13100
|
for (var i = 0; i < drawable.length; i++) {
|
|
11832
|
-
this.remove(drawable[i], false, removeWithVertices)
|
|
13101
|
+
if (this.remove(drawable[i], false, removeWithVertices, true)) {
|
|
13102
|
+
removedDrawables.push(drawable[i]);
|
|
13103
|
+
}
|
|
11833
13104
|
}
|
|
11834
13105
|
if (redraw) {
|
|
11835
13106
|
this.redraw();
|
|
11836
13107
|
}
|
|
11837
|
-
|
|
13108
|
+
!doNotFireEvent && this._fireContentChanged([], removedDrawables);
|
|
13109
|
+
return removedDrawables.length > 0;
|
|
11838
13110
|
}
|
|
11839
13111
|
if (drawable instanceof Vertex) {
|
|
11840
|
-
this.removeVertex(drawable, false);
|
|
13112
|
+
const wasRemoved = this.removeVertex(drawable, false, false);
|
|
11841
13113
|
if (redraw) {
|
|
11842
13114
|
this.redraw();
|
|
11843
13115
|
}
|
|
13116
|
+
!doNotFireEvent && this._fireContentChanged([], [drawable]);
|
|
13117
|
+
return wasRemoved;
|
|
11844
13118
|
}
|
|
13119
|
+
let wasRemoved = false;
|
|
13120
|
+
const removedDrawables = [];
|
|
11845
13121
|
for (var i = 0; i < this.drawables.length; i++) {
|
|
11846
13122
|
if (this.drawables[i] === drawable || this.drawables[i].uid === drawable.uid) {
|
|
11847
13123
|
this.drawables.splice(i, 1);
|
|
13124
|
+
removedDrawables.push(drawable);
|
|
11848
13125
|
if (removeWithVertices) {
|
|
11849
13126
|
// Check if some listeners need to be removed
|
|
11850
13127
|
if (drawable instanceof Line) {
|
|
11851
13128
|
// Add some lines
|
|
11852
|
-
this.removeVertex(drawable.a, false);
|
|
11853
|
-
this.removeVertex(drawable.b, false);
|
|
13129
|
+
this.removeVertex(drawable.a, false, true);
|
|
13130
|
+
this.removeVertex(drawable.b, false, true);
|
|
13131
|
+
removedDrawables.push(drawable.a, drawable.b);
|
|
11854
13132
|
}
|
|
11855
13133
|
else if (drawable instanceof Vector) {
|
|
11856
|
-
this.removeVertex(drawable.a, false);
|
|
11857
|
-
this.removeVertex(drawable.b, false);
|
|
13134
|
+
this.removeVertex(drawable.a, false, true);
|
|
13135
|
+
this.removeVertex(drawable.b, false, true);
|
|
13136
|
+
removedDrawables.push(drawable.a, drawable.b);
|
|
11858
13137
|
}
|
|
11859
13138
|
else if (drawable instanceof VEllipse) {
|
|
11860
|
-
this.removeVertex(drawable.center, false);
|
|
11861
|
-
this.removeVertex(drawable.axis, false);
|
|
13139
|
+
this.removeVertex(drawable.center, false, true);
|
|
13140
|
+
this.removeVertex(drawable.axis, false, true);
|
|
13141
|
+
removedDrawables.push(drawable.center, drawable.axis);
|
|
11862
13142
|
}
|
|
11863
13143
|
else if (drawable instanceof VEllipseSector) {
|
|
11864
|
-
this.removeVertex(drawable.ellipse.center);
|
|
11865
|
-
this.removeVertex(drawable.ellipse.axis);
|
|
13144
|
+
this.removeVertex(drawable.ellipse.center, false, true);
|
|
13145
|
+
this.removeVertex(drawable.ellipse.axis, false, true);
|
|
13146
|
+
removedDrawables.push(drawable.ellipse.center, drawable.ellipse.axis);
|
|
11866
13147
|
}
|
|
11867
13148
|
else if (drawable instanceof Circle) {
|
|
11868
|
-
this.removeVertex(drawable.center, false);
|
|
13149
|
+
this.removeVertex(drawable.center, false, true);
|
|
11869
13150
|
}
|
|
11870
13151
|
else if (drawable instanceof CircleSector) {
|
|
11871
|
-
this.removeVertex(drawable.circle.center, false);
|
|
13152
|
+
this.removeVertex(drawable.circle.center, false, true);
|
|
13153
|
+
removedDrawables.push(drawable.circle.center);
|
|
11872
13154
|
}
|
|
11873
13155
|
else if (drawable instanceof Polygon) {
|
|
11874
13156
|
// for( var i in drawable.vertices )
|
|
11875
|
-
for (var i = 0; i < drawable.vertices.length; i++)
|
|
11876
|
-
this.removeVertex(drawable.vertices[i], false);
|
|
13157
|
+
for (var i = 0; i < drawable.vertices.length; i++) {
|
|
13158
|
+
this.removeVertex(drawable.vertices[i], false, true);
|
|
13159
|
+
removedDrawables.push(drawable.vertices[i]);
|
|
13160
|
+
}
|
|
11877
13161
|
}
|
|
11878
13162
|
else if (drawable instanceof Triangle) {
|
|
11879
|
-
this.removeVertex(drawable.a, false);
|
|
11880
|
-
this.removeVertex(drawable.b, false);
|
|
11881
|
-
this.removeVertex(drawable.c, false);
|
|
13163
|
+
this.removeVertex(drawable.a, false, true);
|
|
13164
|
+
this.removeVertex(drawable.b, false, true);
|
|
13165
|
+
this.removeVertex(drawable.c, false, true);
|
|
13166
|
+
removedDrawables.push(drawable.a, drawable.b, drawable.c);
|
|
11882
13167
|
}
|
|
11883
13168
|
else if (drawable instanceof BezierPath) {
|
|
11884
13169
|
for (var i = 0; i < drawable.bezierCurves.length; i++) {
|
|
11885
|
-
this.removeVertex(drawable.bezierCurves[i].startPoint, false);
|
|
11886
|
-
this.removeVertex(drawable.bezierCurves[i].startControlPoint, false);
|
|
11887
|
-
this.removeVertex(drawable.bezierCurves[i].endControlPoint, false);
|
|
13170
|
+
this.removeVertex(drawable.bezierCurves[i].startPoint, false, true);
|
|
13171
|
+
this.removeVertex(drawable.bezierCurves[i].startControlPoint, false, true);
|
|
13172
|
+
this.removeVertex(drawable.bezierCurves[i].endControlPoint, false, true);
|
|
13173
|
+
removedDrawables.push(drawable.bezierCurves[i].startPoint, drawable.bezierCurves[i].startControlPoint, drawable.bezierCurves[i].endControlPoint);
|
|
11888
13174
|
if (i + 1 == drawable.bezierCurves.length) {
|
|
11889
|
-
this.removeVertex(drawable.bezierCurves[i].endPoint, false);
|
|
13175
|
+
this.removeVertex(drawable.bezierCurves[i].endPoint, false, true);
|
|
13176
|
+
removedDrawables.push(drawable.bezierCurves[i].endPoint);
|
|
11890
13177
|
}
|
|
11891
13178
|
}
|
|
11892
13179
|
}
|
|
11893
13180
|
else if (drawable instanceof PBImage) {
|
|
11894
|
-
this.removeVertex(drawable.upperLeft, false);
|
|
11895
|
-
this.removeVertex(drawable.lowerRight, false);
|
|
13181
|
+
this.removeVertex(drawable.upperLeft, false, true);
|
|
13182
|
+
this.removeVertex(drawable.lowerRight, false, true);
|
|
13183
|
+
removedDrawables.push(drawable.upperLeft, drawable.lowerRight);
|
|
11896
13184
|
}
|
|
11897
13185
|
else if (drawable instanceof PBText) {
|
|
11898
|
-
this.removeVertex(drawable.anchor, false);
|
|
13186
|
+
this.removeVertex(drawable.anchor, false, true);
|
|
13187
|
+
removedDrawables.push(drawable.anchor);
|
|
11899
13188
|
}
|
|
11900
13189
|
} // END removeWithVertices
|
|
11901
13190
|
if (redraw) {
|
|
11902
13191
|
this.redraw();
|
|
11903
13192
|
}
|
|
11904
|
-
|
|
11905
|
-
|
|
13193
|
+
!doNotFireEvent && this._fireContentChanged([], removedDrawables);
|
|
13194
|
+
wasRemoved = true;
|
|
13195
|
+
} // END if
|
|
13196
|
+
} // END for
|
|
13197
|
+
return wasRemoved;
|
|
11906
13198
|
}
|
|
11907
13199
|
/**
|
|
11908
13200
|
* Remove a vertex from the vertex list.<br>
|
|
@@ -11912,18 +13204,20 @@ class PlotBoilerplate {
|
|
|
11912
13204
|
* @method removeVertex
|
|
11913
13205
|
* @instance
|
|
11914
13206
|
* @memberof PlotBoilerplate
|
|
11915
|
-
* @return {
|
|
13207
|
+
* @return {boolean}
|
|
11916
13208
|
**/
|
|
11917
|
-
removeVertex(vert, redraw) {
|
|
13209
|
+
removeVertex(vert, redraw, doNotFireEvent) {
|
|
11918
13210
|
for (var i = 0; i < this.vertices.length; i++) {
|
|
11919
13211
|
if (this.vertices[i] === vert) {
|
|
11920
13212
|
this.vertices.splice(i, 1);
|
|
11921
13213
|
if (redraw) {
|
|
11922
13214
|
this.redraw();
|
|
11923
13215
|
}
|
|
11924
|
-
|
|
13216
|
+
!doNotFireEvent && this._fireContentChanged([], [vert]);
|
|
13217
|
+
return true;
|
|
11925
13218
|
}
|
|
11926
13219
|
}
|
|
13220
|
+
return false;
|
|
11927
13221
|
}
|
|
11928
13222
|
/**
|
|
11929
13223
|
* Remove all elements.
|
|
@@ -11938,13 +13232,16 @@ class PlotBoilerplate {
|
|
|
11938
13232
|
* @return {void}
|
|
11939
13233
|
*/
|
|
11940
13234
|
removeAll(keepVertices, triggerRedraw) {
|
|
13235
|
+
let removedDrawables = this.drawables;
|
|
11941
13236
|
this.drawables = [];
|
|
11942
13237
|
if (!Boolean(keepVertices)) {
|
|
13238
|
+
removedDrawables = removedDrawables.concat(this.vertices);
|
|
11943
13239
|
this.vertices = [];
|
|
11944
13240
|
}
|
|
11945
13241
|
if (triggerRedraw || typeof triggerRedraw === "undefined") {
|
|
11946
13242
|
this.redraw();
|
|
11947
13243
|
}
|
|
13244
|
+
removedDrawables.length > 0 && this._fireContentChanged([], removedDrawables);
|
|
11948
13245
|
}
|
|
11949
13246
|
/**
|
|
11950
13247
|
* Find the vertex near the given position.
|
|
@@ -12149,18 +13446,17 @@ class PlotBoilerplate {
|
|
|
12149
13446
|
}
|
|
12150
13447
|
else if (d instanceof Triangle) {
|
|
12151
13448
|
draw.polyline([d.a, d.b, d.c], false, this.drawConfig.triangle.color, this.drawConfig.triangle.lineWidth);
|
|
12152
|
-
if (!this.drawConfig.drawHandlePoints)
|
|
13449
|
+
if (!this.drawConfig.drawHandlePoints) {
|
|
12153
13450
|
d.a.attr.renderTime = d.b.attr.renderTime = d.c.attr.renderTime = renderTime;
|
|
13451
|
+
}
|
|
12154
13452
|
}
|
|
12155
13453
|
else if (d instanceof VEllipse) {
|
|
12156
13454
|
if (this.drawConfig.drawHandleLines) {
|
|
12157
13455
|
draw.setCurrentId(`${d.uid}_e0`);
|
|
12158
13456
|
draw.setCurrentClassName(`${d.className}-v-line`);
|
|
12159
|
-
// draw.line( d.center.clone().add(0,d.axis.y-d.center.y), d.axis, '#c8c8c8' );
|
|
12160
13457
|
draw.handleLine(d.center.clone().add(0, d.signedRadiusV()).rotate(d.rotation, d.center), d.axis); // , "#c8c8c8");
|
|
12161
13458
|
draw.setCurrentId(`${d.uid}_e1`);
|
|
12162
13459
|
draw.setCurrentClassName(`${d.className}-h-line`);
|
|
12163
|
-
// draw.line( d.center.clone().add(d.axis.x-d.center.x,0), d.axis, '#c8c8c8' );
|
|
12164
13460
|
draw.handleLine(d.center.clone().add(d.signedRadiusH(), 0).rotate(d.rotation, d.center), d.axis); // , "#c8c8c8");
|
|
12165
13461
|
}
|
|
12166
13462
|
draw.setCurrentId(d.uid);
|
|
@@ -12176,12 +13472,6 @@ class PlotBoilerplate {
|
|
|
12176
13472
|
else if (d instanceof VEllipseSector) {
|
|
12177
13473
|
draw.setCurrentId(d.uid);
|
|
12178
13474
|
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
13475
|
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
13476
|
draw.path(data, this.drawConfig.ellipseSector.color, this.drawConfig.ellipseSector.lineWidth);
|
|
12187
13477
|
}
|
|
@@ -12206,7 +13496,7 @@ class PlotBoilerplate {
|
|
|
12206
13496
|
d.b.attr.renderTime = renderTime;
|
|
12207
13497
|
}
|
|
12208
13498
|
else if (d instanceof Vector) {
|
|
12209
|
-
draw.arrow(d.a, d.b, this.drawConfig.vector.color);
|
|
13499
|
+
draw.arrow(d.a, d.b, this.drawConfig.vector.color, this.drawConfig.vector.lineWidth);
|
|
12210
13500
|
if (this.drawConfig.drawHandlePoints && d.b.attr.selectable && d.b.attr.visible) {
|
|
12211
13501
|
draw.setCurrentId(`${d.uid}_h0`);
|
|
12212
13502
|
draw.setCurrentClassName(`${d.className}-handle`);
|