jsxgraph 1.4.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +1 -1
  2. package/distrib/README.md +1 -1
  3. package/distrib/geogebra.min.js +3 -3
  4. package/distrib/geonext.min.js +3 -3
  5. package/distrib/index.d.ts +650 -447
  6. package/distrib/intergeo.min.js +3 -3
  7. package/distrib/jsxgraph.css +4 -13
  8. package/distrib/jsxgraphcore.js +9 -25
  9. package/distrib/jsxgraphsrc.js +2544 -2060
  10. package/distrib/sketch.min.js +3 -3
  11. package/package.json +3 -2
  12. package/src/base/board.js +99 -59
  13. package/src/base/chart.js +1 -1
  14. package/src/base/circle.js +1 -1
  15. package/src/base/composition.js +1 -1
  16. package/src/base/constants.js +14 -14
  17. package/src/base/coords.js +3 -3
  18. package/src/base/coordselement.js +4 -4
  19. package/src/base/curve.js +1 -1
  20. package/src/base/element.js +13 -14
  21. package/src/base/foreignobject.js +5 -1
  22. package/src/base/group.js +1 -1
  23. package/src/base/image.js +1 -1
  24. package/src/base/line.js +1 -1
  25. package/src/base/point.js +7 -18
  26. package/src/base/polygon.js +1 -1
  27. package/src/base/text.js +8 -7
  28. package/src/base/ticks.js +9 -2
  29. package/src/base/transformation.js +1 -1
  30. package/src/base/turtle.js +1 -1
  31. package/src/element/arc.js +1 -1
  32. package/src/element/button.js +1 -1
  33. package/src/element/checkbox.js +1 -1
  34. package/src/element/comb.js +1 -1
  35. package/src/element/composition.js +1 -1
  36. package/src/element/conic.js +3 -3
  37. package/src/element/input.js +1 -1
  38. package/src/element/locus.js +1 -1
  39. package/src/element/measure.js +9 -4
  40. package/src/element/sector.js +5 -5
  41. package/src/element/slider.js +17 -12
  42. package/src/element/slopetriangle.js +1 -1
  43. package/src/jsxgraph.js +9 -3
  44. package/src/jxg.js +3 -3
  45. package/src/loadjsxgraph.js +1 -1
  46. package/src/math/bst.js +1 -1
  47. package/src/math/clip.js +550 -345
  48. package/src/math/complex.js +1 -1
  49. package/src/math/extrapolate.js +1 -1
  50. package/src/math/geometry.js +5 -5
  51. package/src/math/ia.js +69 -69
  52. package/src/math/math.js +1 -1
  53. package/src/math/metapost.js +1 -1
  54. package/src/math/nlp.js +4 -4
  55. package/src/math/numerics.js +2 -1
  56. package/src/math/plot.js +4 -4
  57. package/src/math/poly.js +1 -1
  58. package/src/math/probfuncs.js +2 -1
  59. package/src/math/qdt.js +11 -11
  60. package/src/math/statistics.js +10 -10
  61. package/src/math/symbolic.js +1 -1
  62. package/src/options.js +72 -31
  63. package/src/parser/ca.js +1297 -1291
  64. package/src/parser/datasource.js +1 -1
  65. package/src/parser/geonext.js +6 -6
  66. package/src/parser/jessiecode.js +266 -73
  67. package/src/parser/jessiescript.js +1 -1
  68. package/src/reader/cinderella.js +1 -1
  69. package/src/reader/file.js +1 -1
  70. package/src/reader/geogebra.js +1 -1
  71. package/src/reader/geonext.js +1 -1
  72. package/src/reader/graph.js +1 -1
  73. package/src/reader/intergeo.js +1 -1
  74. package/src/reader/sketch.js +1 -1
  75. package/src/renderer/abstract.js +4 -1
  76. package/src/renderer/canvas.js +9 -5
  77. package/src/renderer/no.js +2 -1
  78. package/src/renderer/svg.js +10 -13
  79. package/src/renderer/vml.js +1 -1
  80. package/src/server/server.js +1 -1
  81. package/src/utils/base64.js +2 -2
  82. package/src/utils/color.js +7 -7
  83. package/src/utils/dump.js +20 -5
  84. package/src/utils/env.js +13 -54
  85. package/src/utils/event.js +1 -1
  86. package/src/utils/expect.js +1 -1
  87. package/src/utils/type.js +10 -8
  88. package/src/utils/xml.js +1 -1
  89. package/src/utils/zip.js +1 -1
  90. package/CHANGELOG.md +0 -1366
package/src/math/clip.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2008-2021
2
+ Copyright 2008-2022
3
3
  Matthias Ehmann,
4
4
  Michael Gerhaeuser,
5
5
  Carsten Miller,
@@ -260,8 +260,6 @@ define([
260
260
  pathname: pathname,
261
261
  done: false,
262
262
  type: type,
263
- revtype: type,
264
- link: null,
265
263
  idx: 0
266
264
  };
267
265
 
@@ -307,10 +305,11 @@ define([
307
305
  P_crossings[i].sort(function(a, b) { return (a.data.alpha > b.data.alpha) ? 1 : -1; });
308
306
 
309
307
  if (P_crossings[i].length > 0) {
310
- // console.log("Crossings", P_crossings[i])
308
+ // console.log("Crossings", P_crossings[i])
311
309
  last = P_crossings[i].length - 1;
312
310
  P = P_crossings[i][0];
313
- //console.log("SORT", P.coords.usrCoords)
311
+
312
+ //console.log("SORT", P.coords.usrCoords)
314
313
  Q = P.data.path[P.pos];
315
314
  next_node = Q._next; // Store the next "normal" node
316
315
 
@@ -319,7 +318,7 @@ define([
319
318
  }
320
319
 
321
320
  if (P.data.alpha === 0.0 && P.data.type === 'T') {
322
- // console.log("SKIP", P.coords.usrCoords, P.data.type, P.neighbour.data.type);
321
+ // console.log("SKIP", P.coords.usrCoords, P.data.type, P.neighbour.data.type);
323
322
  Q.intersection = true;
324
323
  Q.data = P.data;
325
324
  Q.neighbour = P.neighbour;
@@ -346,7 +345,7 @@ define([
346
345
 
347
346
  if (i === P_le - 1) {
348
347
  P._end = true;
349
- //console.log("END", P._end, P.coords.usrCoords, P._prev.coords.usrCoords, P._next.coords.usrCoords);
348
+ //console.log("END", P._end, P.coords.usrCoords, P._prev.coords.usrCoords, P._next.coords.usrCoords);
350
349
  }
351
350
 
352
351
  P_intersect = P_intersect.concat(P_crossings[i]);
@@ -387,7 +386,7 @@ define([
387
386
  if (arr[i]._end) {
388
387
  end = " end";
389
388
  }
390
- console.log(i, arr[i].coords.usrCoords,
389
+ console.log(i, arr[i].coords.usrCoords, arr[i].data.type, "\t",
391
390
  "prev", arr[i]._prev.coords.usrCoords,
392
391
  "next", arr[i]._next.coords.usrCoords + end);
393
392
  } catch (e) {
@@ -420,7 +419,7 @@ define([
420
419
 
421
420
  _noOverlap: function(p1, p2, q1, q2) {
422
421
  var k,
423
- eps = Mat.eps * Mat.eps,
422
+ eps = Math.sqrt(Mat.eps),
424
423
  minp, maxp, minq, maxq,
425
424
  no_overlap = false;
426
425
 
@@ -429,7 +428,7 @@ define([
429
428
  maxp = Math.max(p1[k], p2[k]);
430
429
  minq = Math.min(q1[k], q2[k]);
431
430
  maxq = Math.max(q1[k], q2[k]);
432
- if (maxp < minq - eps|| minp > maxq + eps) {
431
+ if (maxp < minq - eps || minp > maxq + eps) {
433
432
  no_overlap = true;
434
433
  break;
435
434
  }
@@ -465,7 +464,8 @@ define([
465
464
  S_crossings = [],
466
465
  C_crossings = [],
467
466
  hasMultCompsS = false,
468
- hasMultCompsC = false;
467
+ hasMultCompsC = false,
468
+ DEBUG = false;
469
469
 
470
470
  for (j = 0; j < C_le; j++) {
471
471
  C_crossings.push([]);
@@ -492,7 +492,6 @@ define([
492
492
 
493
493
  Si = S[i].coords.usrCoords;
494
494
  Si1 = S[(i + 1) % S_le].coords.usrCoords;
495
-
496
495
  // Run through the clip path.
497
496
  for (j = 0; j < C_le; j++) {
498
497
  // Test if C[j] or its successor is a path separator.
@@ -521,80 +520,95 @@ define([
521
520
 
522
521
  // Intersection test
523
522
  res = Geometry.meetSegmentSegment(Si, Si1, Cj, Cj1);
524
- // console.log(i, j, ":", eps, res[0][1] / res[0][0], res[0][2] / res[0][0], res[1], res[2]);
525
523
 
526
524
  d1 = Geometry.distance(Si, Si1, 3);
527
525
  d2 = Geometry.distance(Cj, Cj1, 3);
526
+
528
527
  // Found an intersection point
529
- // isCollinear = false;
530
- if ((res[1] * d1 > -eps && res[1] < 1 - eps / d1 && // "regular" intersection
531
- res[2] * d2 > -eps && res[2] < 1 - eps / d2) ||
532
- (res[1] === Infinity &&
533
- res[2] === Infinity && Mat.norm(res[0], 3) < eps) // collinear
528
+ if ( // "Regular" intersection
529
+ (res[1] * d1 > -eps && res[1] < 1 - eps / d1 && res[2] * d2 > -eps && res[2] < 1 - eps / d2) ||
530
+ // Collinear segments
531
+ (res[1] === Infinity && res[2] === Infinity && Mat.norm(res[0], 3) < eps)
534
532
  ) {
535
533
 
536
- crds = new Coords(Const.COORDS_BY_USER, res[0], board);
537
- type = 'X';
538
- // console.log("IS", i, j, crds.usrCoords, res[1], d1, res[1] * d1);
539
- // console.log(res[2], d2, res[2] * d2);
540
-
541
- // Degenerate cases
542
- if (Math.abs(res[1]) * d1 < eps || Math.abs(res[2]) * d2 < eps) {
543
- // Crossing / bouncing at vertex or
544
- // end of delayed crossing / bouncing
545
- type = 'T';
546
- if (Math.abs(res[1]) * d1 < eps) {
547
- res[1] = 0;
548
- }
549
- if (Math.abs(res[2]) * d2 < eps) {
550
- res[2] = 0;
551
- }
552
- if (res[1] === 0) {
553
- crds = new Coords(Const.COORDS_BY_USER, Si, board);
554
- } else {
555
- crds = new Coords(Const.COORDS_BY_USER, Cj, board);
556
- }
557
- } else if (res[1] === Infinity &&
558
- res[2] === Infinity &&
559
- Mat.norm(res[0], 3) < eps) {
560
-
561
- // In this case there might be two intersection points to be added
562
- // Collinear segments
563
- alpha = this._inbetween(Si, Cj, Cj1);
564
- // console.log("alpha Si", alpha, Si);
565
- // console.log(j, Cj)
566
- // console.log((j + 1) % C_le, Cj1)
567
- if (alpha >= 0 && alpha < 1) {
568
- type = 'T';
569
- crds = new Coords(Const.COORDS_BY_USER, Si, board);
570
- res[1] = 0;
571
- res[2] = alpha;
572
- IS = new this.Vertex(crds, i, res[1], S, 'S', type);
573
- IC = new this.Vertex(crds, j, res[2], C, 'C', type);
574
- IS.neighbour = IC;
575
- IC.neighbour = IS;
576
-
577
- S_crossings[i].push(IS);
578
- C_crossings[j].push(IC);
534
+ crds = new Coords(Const.COORDS_BY_USER, res[0], board);
535
+ type = 'X';
536
+
537
+ // Handle degenerated cases
538
+ if (Math.abs(res[1]) * d1 < eps || Math.abs(res[2]) * d2 < eps) {
539
+ // Crossing / bouncing at vertex or
540
+ // end of delayed crossing / bouncing
541
+ type = 'T';
542
+ if (Math.abs(res[1]) * d1 < eps) {
543
+ res[1] = 0;
544
+ }
545
+ if (Math.abs(res[2]) * d2 < eps) {
546
+ res[2] = 0;
547
+ }
548
+ if (res[1] === 0) {
549
+ crds = new Coords(Const.COORDS_BY_USER, Si, board);
550
+ } else {
551
+ crds = new Coords(Const.COORDS_BY_USER, Cj, board);
552
+ }
553
+
554
+ if (DEBUG) {
555
+ console.log("Degenerate case I", res[1], res[2], crds.usrCoords, "type", type);
556
+ }
557
+ } else if (res[1] === Infinity &&
558
+ res[2] === Infinity &&
559
+ Mat.norm(res[0], 3) < eps) { // console.log(C_intersect);
560
+
561
+
562
+ // Collinear segments
563
+ // Here, there might be two intersection points to be added
564
+
565
+ alpha = this._inbetween(Si, Cj, Cj1);
566
+ if (DEBUG) {
567
+ // console.log("alpha Si", alpha, Si);
568
+ // console.log(j, Cj)
569
+ // console.log((j + 1) % C_le, Cj1)
570
+ }
571
+ if (alpha >= 0 && alpha < 1) {
572
+ type = 'T';
573
+ crds = new Coords(Const.COORDS_BY_USER, Si, board);
574
+ res[1] = 0;
575
+ res[2] = alpha;
576
+ IS = new this.Vertex(crds, i, res[1], S, 'S', type);
577
+ IC = new this.Vertex(crds, j, res[2], C, 'C', type);
578
+ IS.neighbour = IC;
579
+ IC.neighbour = IS;
580
+ S_crossings[i].push(IS);
581
+ C_crossings[j].push(IC);
582
+ if (DEBUG) {
583
+ console.log("Degenerate case II", res[1], res[2], crds.usrCoords, "type T");
579
584
  }
580
- alpha = this._inbetween(Cj, Si, Si1);
581
- // console.log("alpha Cj", alpha, Si, Geometry.distance(Si, Cj, 3));
582
- if (Geometry.distance(Si, Cj, 3) > eps &&
583
- alpha >= 0 && alpha < 1) {
584
- type = 'T';
585
- crds = new Coords(Const.COORDS_BY_USER, Cj, board);
586
- res[1] = alpha;
587
- res[2] = 0;
588
- IS = new this.Vertex(crds, i, res[1], S, 'S', type);
589
- IC = new this.Vertex(crds, j, res[2], C, 'C', type);
590
- IS.neighbour = IC;
591
- IC.neighbour = IS;
592
-
593
- S_crossings[i].push(IS);
594
- C_crossings[j].push(IC);
585
+ }
586
+ alpha = this._inbetween(Cj, Si, Si1);
587
+ if (DEBUG) {
588
+ // console.log("alpha Cj", alpha, Si, Geometry.distance(Si, Cj, 3));
589
+ }
590
+ if (Geometry.distance(Si, Cj, 3) > eps &&
591
+ alpha >= 0 && alpha < 1) {
592
+
593
+ type = 'T';
594
+ crds = new Coords(Const.COORDS_BY_USER, Cj, board);
595
+ res[1] = alpha;
596
+ res[2] = 0;
597
+ IS = new this.Vertex(crds, i, res[1], S, 'S', type);
598
+ IC = new this.Vertex(crds, j, res[2], C, 'C', type);
599
+ IS.neighbour = IC;
600
+ IC.neighbour = IS;
601
+ S_crossings[i].push(IS);
602
+ C_crossings[j].push(IC);
603
+ if (DEBUG) {
604
+ console.log("Degenerate case III", res[1], res[2], crds.usrCoords, "type T");
595
605
  }
596
- continue;
597
606
  }
607
+ continue;
608
+ }
609
+ if (DEBUG) {
610
+ console.log("IS", i, j, crds.usrCoords, type);
611
+ }
598
612
 
599
613
  IS = new this.Vertex(crds, i, res[1], S, 'S', type);
600
614
  IC = new this.Vertex(crds, j, res[2], C, 'C', type);
@@ -610,49 +624,84 @@ define([
610
624
  // For both paths, sort their intersection points
611
625
  S_intersect = this.sortIntersections(S_crossings);
612
626
 
613
- // console.log('>>>>>> Intersections ')
614
- // this._print_array(S_intersect);
615
- // // console.log(S_intersect)
616
- // console.log('----------')
627
+ if (DEBUG) {
628
+ console.log('>>>>>> Intersections ');
629
+ console.log("S_intersect");
630
+ this._print_array(S_intersect);
631
+ console.log('----------');
632
+ }
617
633
  for (i = 0; i < S_intersect.length; i++) {
618
634
  S_intersect[i].data.idx = i;
619
635
  S_intersect[i].neighbour.data.idx = i;
620
636
  }
621
637
  C_intersect = this.sortIntersections(C_crossings);
622
638
 
623
- // this._print_array(C_intersect);
624
- // console.log(C_intersect)
625
- // console.log('<<<<<< Phase 1 done')
639
+ if (DEBUG) {
640
+ console.log("C_intersect");
641
+ this._print_array(C_intersect);
642
+ console.log('<<<<<< Phase 1 done');
643
+ }
626
644
  return [S_intersect, C_intersect];
627
645
  },
628
646
 
647
+ /**
648
+ * It is testedd if the point q lies to the left or right
649
+ * of the poylgonal chain [p1, p2, p3].
650
+ * @param {Array} q User coords array
651
+ * @param {Array} p1 User coords array
652
+ * @param {Array} p2 User coords array
653
+ * @param {Array} p3 User coords array
654
+ * @returns string 'left' or 'right'
655
+ * @private
656
+ */
629
657
  _getPosition: function(q, p1, p2, p3) {
630
658
  var s1 = this.det(q, p1, p2),
631
659
  s2 = this.det(q, p2, p3),
632
660
  s3 = this.det(p1, p2, p3);
633
661
 
634
- if (s3 >= 0) { // Left turn or straight
635
- if (s1 > 0 && s2 > 0) {
662
+ // Left turn
663
+ if (s3 >= 0) {
664
+ if (s1 >= 0 && s2 >= 0) {
636
665
  return 'left';
637
666
  }
638
667
  return 'right';
639
668
  }
640
669
  // Right turn
641
- if (s1 < 0 && s2 < 0) {
642
- return 'right';
670
+ if (s1 >= 0 || s2 >= 0) {
671
+ return 'left';
643
672
  }
644
- return 'left';
673
+ return 'right';
645
674
  },
646
675
 
676
+ /**
677
+ * Determine the delayed status of degenerated intersection points.
678
+ * It is of the form
679
+ * ['on|left|right', 'on|left|right']
680
+ * <p>
681
+ * If all four determinants are zero, we add random noise to the point.
682
+ *
683
+ * @param {JXG.Math.Clip.Vertex} P Start of path
684
+ * @private
685
+ * @see JXG.Math.Clip#markEntryExit
686
+ * @see JXG.Math.Clip#_handleIntersectionChains
687
+ */
647
688
  _classifyDegenerateIntersections: function(P) {
648
689
  var Pp, Pm, Qp, Qm, Q, side,
649
- cnt;
690
+ cnt, tmp,
691
+ oppositeDir,
692
+ s1, s2, s3, s4,
693
+ DEBUG = false;
650
694
 
695
+ if (DEBUG) {
696
+ console.log("\n-------------- _classifyDegenerateIntersections()", (Type.exists(P.data))?P.data.pathname:' ');
697
+ }
651
698
  cnt = 0;
652
- P._start = 0;
699
+ P._tours = 0;
653
700
  while (true) {
654
- // console.log("P:", P.coords.usrCoords, (P.data) ? P.data.type : " ")
655
- if (P.intersection && P.data.type === 'T') {
701
+ if (DEBUG) {
702
+ console.log("Inspect P:", P.coords.usrCoords, (P.data) ? P.data.type : " ");
703
+ }
704
+ if (P.intersection && (P.data.type === 'T')) {
656
705
 
657
706
  // Handle the degenerate cases
658
707
  // Decide if they are (delayed) bouncing or crossing intersections
@@ -665,93 +714,134 @@ define([
665
714
  // the same coordinate.
666
715
  // In that case, we proceed to the next node.
667
716
  if (Geometry.distance(P.coords.usrCoords, Pp, 3) < Mat.eps) {
668
- P._next = P._next._next;
669
- Pp = P._next.coords.usrCoords;
717
+ Pp = P._next._next.coords.usrCoords;
670
718
  }
671
719
  if (Geometry.distance(P.coords.usrCoords, Pm, 3) < Mat.eps) {
672
- P._prev = P._prev._prev;
673
- Pm = P._prev.coords.usrCoords;
720
+ Pm = P._prev._prev.coords.usrCoords;
674
721
  }
675
722
 
676
723
  Q = P.neighbour;
677
- Qm = P.neighbour._prev.coords.usrCoords; // Q-
678
- Qp = P.neighbour._next.coords.usrCoords; // Q+
679
-
680
- // console.log("Chain 1:", Pm, P.coords.usrCoords, Pp)
681
- // console.log("Chain 2:", Qm, P.neighbour.coords.usrCoords, Qp)
682
- // console.log(P._next.neighbour, Q._prev)
683
- // console.log(P._next.intersection, P._next.neighbour === Q._prev)
684
- if (P._next.intersection && P._next.neighbour === Q._next) {
685
- if (P._prev.intersection && P._prev.neighbour === Q._prev) {
686
- P.delayedStatus = ['on', 'on'];
687
- } else {
688
- side = this._getPosition(Qm, Pm, P.coords.usrCoords, Pp);
689
- if (side === 'right') {
690
- P.delayedStatus = ['left', 'on'];
691
- } else {
692
- P.delayedStatus = ['right', 'on'];
693
- }
724
+ Qm = Q._prev.coords.usrCoords; // Q-
725
+ Qp = Q._next.coords.usrCoords; // Q+
726
+ if (Geometry.distance(Q.coords.usrCoords, Qp, 3) < Mat.eps) {
727
+ Qp = Q._next._next.coords.usrCoords;
728
+ }
729
+ if (Geometry.distance(Q.coords.usrCoords, Qm, 3) < Mat.eps) {
730
+ Qm = Q._prev._prev.coords.usrCoords;
731
+ }
732
+
733
+ if (DEBUG) {
734
+ console.log("P chain:", Pm, P.coords.usrCoords, Pp);
735
+ console.log("Q chain:", Qm, P.neighbour.coords.usrCoords, Qp);
736
+ console.log("Pm", this._getPosition(Pm, Qm, Q.coords.usrCoords, Qp));
737
+ console.log("Pp", this._getPosition(Pp, Qm, Q.coords.usrCoords, Qp));
738
+ }
739
+
740
+ s1 = this.det(P.coords.usrCoords, Pm, Qm);
741
+ s2 = this.det(P.coords.usrCoords, Pp, Qp);
742
+ s3 = this.det(P.coords.usrCoords, Pm, Qp);
743
+ s4 = this.det(P.coords.usrCoords, Pp, Qm);
744
+
745
+ if (s1 === 0 && s2 === 0 && s3 === 0 && s4 === 0) {
746
+ P.coords.usrCoords[1] *= 1 + Math.random() * Mat.eps;
747
+ P.coords.usrCoords[2] *= 1 + Math.random() * Mat.eps;
748
+ Q.coords.usrCoords[1] = P.coords.usrCoords[1];
749
+ Q.coords.usrCoords[2] = P.coords.usrCoords[2];
750
+ s1 = this.det(P.coords.usrCoords, Pm, Qm);
751
+ s2 = this.det(P.coords.usrCoords, Pp, Qp);
752
+ s3 = this.det(P.coords.usrCoords, Pm, Qp);
753
+ s4 = this.det(P.coords.usrCoords, Pp, Qm);
754
+ if (DEBUG) {
755
+ console.log("Random shift", P.coords.usrCoords);
756
+ console.log(s1, s2, s3, s4, s2 === 0);
757
+ console.log(this._getPosition(Pm, Qm, Q.coords.usrCoords, Qp),
758
+ this._getPosition(Pp, Qm, Q.coords.usrCoords, Qp));
694
759
  }
695
- } else if (P._next.intersection && P._next.neighbour === Q._prev) {
696
- if (P._prev.intersection && P._prev.neighbour === Q._next) {
697
- P.delayedStatus = ['on', 'on'];
698
- } else {
699
- side = this._getPosition(Qp, Pm, P.coords.usrCoords, Pp);
700
- if (side === 'right') {
701
- P.delayedStatus = ['left', 'on'];
702
- } else {
703
- P.delayedStatus = ['right', 'on'];
704
- }
760
+ }
761
+ oppositeDir = false;
762
+ if (s1 === 0) {
763
+ // Q-, Q=P, P- on straight line
764
+ if (Geometry.affineRatio(P.coords.usrCoords, Pm, Qm) < 0) {
765
+ oppositeDir = true;
766
+ }
767
+ } else if (s2 === 0) {
768
+ if (Geometry.affineRatio(P.coords.usrCoords, Pp, Qp) < 0) {
769
+ oppositeDir = true;
705
770
  }
771
+ } else if (s3 === 0) {
772
+ if (Geometry.affineRatio(P.coords.usrCoords, Pm, Qp) > 0) {
773
+ oppositeDir = true;
774
+ }
775
+ } else if (s4 === 0) {
776
+ if (Geometry.affineRatio(P.coords.usrCoords, Pp, Qm) > 0) {
777
+ oppositeDir = true;
778
+ }
779
+ }
780
+ if (oppositeDir) {
781
+ // Swap Qm and Qp
782
+ // Then Qm Q Qp has the same direction as Pm P Pp
783
+ tmp = Qm; Qm = Qp; Qp = tmp;
784
+ tmp = s1; s1 = s3; s3 = tmp;
785
+ tmp = s2; s2 = s4; s4 = tmp;
706
786
  }
707
787
 
708
- if (P._prev.intersection && P._prev.neighbour === Q._prev) {
709
- if (!(P._next.intersection && P._next.neighbour === Q._next)) {
710
- side = this._getPosition(Qp, Pm, P.coords.usrCoords, Pp);
711
- if (side === 'right') {
712
- P.delayedStatus = ['on', 'left'];
713
- } else {
714
- P.delayedStatus = ['on', 'right'];
715
- }
716
- }
717
- } else if (P._prev.intersection && P._prev.neighbour === Q._next) {
718
- if (!(P._next.intersection && P._next.neighbour === Q._prev)) {
719
- side = this._getPosition(Qm, Pm, P.coords.usrCoords, Pp);
720
- if (side === 'right') {
721
- P.delayedStatus = ['on', 'left'];
788
+ if (DEBUG) {
789
+ console.log(s1, s2, s3, s4, oppositeDir);
790
+ }
791
+
792
+ if (!Type.exists(P.delayedStatus)) {
793
+ P.delayedStatus = [];
794
+ }
795
+
796
+ if (s1 === 0 && s2 === 0) {
797
+ // Line [P-,P] equals [Q-,Q] and line [P,P+] equals [Q,Q+]
798
+ // Interior of delayed crossing / bouncing
799
+ P.delayedStatus = ['on', 'on'];
800
+
801
+ } else if (s1 === 0) {
802
+ // P- on line [Q-,Q], P+ not on line [Q,Q+]
803
+ // Begin / end of delayed crossing / bouncing
804
+ side = this._getPosition(Pp, Qm, Q.coords.usrCoords, Qp);
805
+ P.delayedStatus = ['on', side];
806
+
807
+ } else if (s2 === 0) {
808
+ // P+ on line [Q,Q+], P- not on line [Q-,Q]
809
+ // Begin / end of delayed crossing / bouncing
810
+ side = this._getPosition(Pm, Qm, Q.coords.usrCoords, Qp);
811
+ P.delayedStatus = [side, 'on'];
812
+
813
+ } else {
814
+ // Neither P+ on line [Q,Q+], nor P- on line [Q-,Q]
815
+ // No delayed crossing / bouncing
816
+ if (P.delayedStatus.length === 0) {
817
+ if (this._getPosition(Pm, Qm, Q.coords.usrCoords, Qp) !== this._getPosition(Pp, Qm, Q.coords.usrCoords, Qp)) {
818
+ P.data.type = 'X';
722
819
  } else {
723
- P.delayedStatus = ['on', 'right'];
820
+ P.data.type = 'B';
724
821
  }
725
822
  }
726
823
  }
727
824
 
728
- if ((!P._next.intersection || (P._next.neighbour !== Q._prev && P._next.neighbour !== Q._next)) &&
729
- (!P._prev.intersection || (P._prev.neighbour !== Q._prev && P._prev.neighbour !== Q._next))
730
- ) {
731
- // Neither P- nor P+ are intersections
732
-
733
- side = this._getPosition(Qm, Pm, P.coords.usrCoords, Pp);
734
- if (side !== this._getPosition(Qp, Pm, P.coords.usrCoords, Pp)) {
735
- P.data.type = 'X';
736
- P.data.revtype = 'X';
737
- } else {
738
- P.data.type = 'B';
739
- P.data.revtype = 'B';
740
- }
825
+ if (DEBUG) {
826
+ console.log(">>>> P:", P.coords.usrCoords, "delayedStatus:", P.delayedStatus.toString(), (P.data) ? P.data.type : " ", "\n---");
741
827
  }
742
- // console.log(">P:", P.coords.usrCoords, (P.data) ? P.data.type : " ")
743
828
 
744
829
  }
745
830
 
746
- if (Type.exists(P._start)) {
747
- P._start++;
831
+ if (Type.exists(P._tours)) {
832
+ P._tours++;
748
833
  }
749
- if (P._start > 3 || P._end || cnt > 1000) {
834
+
835
+ if (P._tours > 3 || P._end || cnt > 1000) {
836
+ // Jump out if either
837
+ // - we reached the end
838
+ // - there are more than 1000 intersection points
839
+ // - P._tours > 3: We went already 4 times through this path.
750
840
  if (cnt > 1000) {
751
841
  console.log("Clipping: _classifyDegenerateIntersections exit");
752
842
  }
753
- if (Type.exists(P._start)) {
754
- delete P._start;
843
+ if (Type.exists(P._tours)) {
844
+ delete P._tours;
755
845
  }
756
846
  break;
757
847
  }
@@ -760,43 +850,76 @@ define([
760
850
  }
761
851
  P = P._next;
762
852
  }
763
- // console.log("------------------------")
853
+ if (DEBUG) {
854
+ console.log("------------------------");
855
+ }
764
856
  },
765
857
 
858
+ /**
859
+ * At this point the degenerated intersections have been classified.
860
+ * Now we decide if the intersection chains of the given path
861
+ * ultimatively cross the other path or bounce.
862
+ *
863
+ * @param {JXG.Math.Clip.Vertex} P Start of path
864
+ *
865
+ * @see JXG.Math.Clip#markEntryExit
866
+ * @see JXG.Math.Clip#_classifyDegenerateIntersections
867
+ * @private
868
+ */
766
869
  _handleIntersectionChains: function(P) {
767
870
  var cnt = 0,
768
871
  start_status = 'Null',
769
872
  P_start,
770
873
  intersection_chain = false,
771
- wait_for_exit = false;
874
+ wait_for_exit = false,
875
+ DEBUG = false;
772
876
 
877
+ if (DEBUG) {
878
+ console.log("\n-------------- _handleIntersectionChains()",
879
+ (Type.exists(P.data))?P.data.pathname:' ');
880
+ }
773
881
  while (true) {
774
882
  if (P.intersection === true) {
775
- //console.log("Chain point", P.coords.usrCoords, P.data.type);
883
+ if (DEBUG) {
884
+ if (P.data.type === 'T') {
885
+ console.log("Degenerate point", P.coords.usrCoords, P.data.type, (P.data.type === 'T')?P.delayedStatus:' ');
886
+ } else {
887
+ console.log("Intersection point", P.coords.usrCoords, P.data.type);
888
+ }
889
+ }
776
890
  if (P.data.type === 'T') {
777
891
  if (P.delayedStatus[0] !== 'on' && P.delayedStatus[1] === 'on') {
892
+ // First point of intersection chain
778
893
  intersection_chain = true;
779
894
  P_start = P;
780
895
  start_status = P.delayedStatus[0];
781
- } else if (P.delayedStatus[0] === 'on' && P.delayedStatus[1] === 'on') {
896
+
897
+ } else if (intersection_chain &&
898
+ P.delayedStatus[0] === 'on' && P.delayedStatus[1] === 'on') {
899
+ // Interior of intersection chain
782
900
  P.data.type = 'B';
783
- P.data.revtype = 'B';
784
- } else if (P.delayedStatus[0] === 'on' && P.delayedStatus[1] !== 'on' &&
785
- intersection_chain) {
901
+ if (DEBUG) {
902
+ console.log("Interior", P.coords.usrCoords);
903
+ }
904
+ } else if (intersection_chain &&
905
+ P.delayedStatus[0] === 'on' && P.delayedStatus[1] !== 'on') {
906
+ // Last point of intersection chain
786
907
  intersection_chain = false;
787
908
  if (start_status === P.delayedStatus[1]) {
788
- P.data.type = 'B';
789
- P.data.revtype = 'B';
790
- P_start.data.type = 'B';
791
- P_start.data.revtype = 'B';
909
+ // Intersection chain is delayed bouncing
910
+ P_start.data.type = 'DB';
911
+ P.data.type = 'DB';
912
+ if (DEBUG) {
913
+ console.log("Chain: delayed bouncing", P_start.coords.usrCoords, '...', P.coords.usrCoords);
914
+ }
792
915
  } else {
793
- P.data.type = 'X';
794
- P.data.revtype = 'B';
795
- P_start.data.type = 'B';
796
- P_start.data.revtype = 'X';
916
+ // Intersection chain is delayed crossing
917
+ P_start.data.type = 'DX';
918
+ P.data.type = 'DX';
919
+ if (DEBUG) {
920
+ console.log("Chain: delayed crossing", P_start.coords.usrCoords, '...', P.coords.usrCoords);
921
+ }
797
922
  }
798
- P_start.data.link = P;
799
- P.data.link = P_start;
800
923
  }
801
924
  }
802
925
  cnt++;
@@ -808,7 +931,7 @@ define([
808
931
  break;
809
932
  }
810
933
  if (cnt > 1000) {
811
- console.log("Clipping: Intersection chain - exit");
934
+ console.log("Warning: _handleIntersectionChains: intersection chain reached maximum numbers of iterations");
812
935
  break;
813
936
  }
814
937
  P = P._next;
@@ -906,7 +1029,6 @@ define([
906
1029
  // Inside
907
1030
  status = 'exit';
908
1031
  }
909
- //console.log("STATUS", P.coords.usrCoords, status)
910
1032
 
911
1033
  return [P, status];
912
1034
  },
@@ -914,10 +1036,16 @@ define([
914
1036
  /**
915
1037
  * Mark the intersection vertices of path1 as entry points or as exit points
916
1038
  * in respect to path2.
917
- *
1039
+ * <p>
918
1040
  * This is the simple algorithm as in
919
1041
  * Greiner, Günther; Kai Hormann (1998). "Efficient clipping of arbitrary polygons".
920
1042
  * ACM Transactions on Graphics. 17 (2): 71–83
1043
+ * <p>
1044
+ * The algorithm handles also "delayed crossings" from
1045
+ * Erich, L. Foster, and Kai Hormann, Kai, and Romeo Traaian Popa (2019),
1046
+ * "Clipping simple polygons with degenerate intersections", Computers & Graphics:X, 2.
1047
+ * and - as an additional improvement -
1048
+ * handles self intersections of delayed crossings (A.W. 2021).
921
1049
  *
922
1050
  * @private
923
1051
  * @param {Array} path1 First path
@@ -925,39 +1053,106 @@ define([
925
1053
  */
926
1054
  markEntryExit: function(path1, path2, starters) {
927
1055
  var status, P, cnt, res,
928
- i, len, start;
1056
+ i, len, start,
1057
+ chain_start = null,
1058
+ intersection_chain = 0,
1059
+ DEBUG = false;
929
1060
 
930
1061
  len = starters.length;
931
1062
  for (i = 0; i < len; i++) {
932
- // console.log(";;;;;;;;;;")
933
1063
  start = starters[i];
1064
+ if (DEBUG) {
1065
+ console.log("\n;;;;;;;;;; Labelling phase",
1066
+ (Type.exists(path1[start].data))?path1[start].data.pathname:' ',
1067
+ path1[start].coords.usrCoords);
1068
+ }
934
1069
  this._classifyDegenerateIntersections(path1[start]);
935
1070
  this._handleIntersectionChains(path1[start]);
1071
+ if (DEBUG) {
1072
+ console.log("\n---- back to markEntryExit");
1073
+ }
936
1074
 
937
1075
  // Decide if the first point of the component is inside or outside
938
1076
  // of the other path.
939
1077
  res = this._getStatus(path1[start], path2);
940
1078
  P = res[0];
941
1079
  status = res[1];
942
- // console.log("status", P.coords.usrCoords, status);
1080
+ if (DEBUG) {
1081
+ console.log("Start node:", P.coords.usrCoords, status);
1082
+ }
943
1083
 
944
1084
  P._starter = true;
1085
+
945
1086
  // Greiner-Hormann entry/exit algorithm
1087
+ // with additional handling of delayed crossing / bouncing
946
1088
  cnt = 0;
1089
+ chain_start = null;
1090
+ intersection_chain = 0;
1091
+
947
1092
  while (true) {
948
- if (P.intersection === true && P.data.type === 'X') {
949
- P.entry_exit = status;
950
- status = (status === 'entry') ? 'exit' : 'entry';
951
- if (P.data.link !== null && !P.data.link.entry_exit) {
952
- P.data.link.entry_exit = P.entry_exit;
1093
+ if (P.intersection === true) {
1094
+ if (P.data.type === 'X' && intersection_chain === 1) {
1095
+ // While we are in an intersection chain, i.e. a delayed crossing,
1096
+ // we stumble on a crossing intersection.
1097
+ // Probably, the other path is self intersecting.
1098
+ // We end the intersection chain here and
1099
+ // mark this event by setting intersection_chain = 2.
1100
+ chain_start.entry_exit = status;
1101
+ if (status === 'exit') {
1102
+ chain_start.data.type = 'X';
1103
+ }
1104
+ intersection_chain = 2;
953
1105
  }
954
- }
955
- if (P.intersection === true && P.data.type !== 'X') {
956
- if (!P.entry_exit && P.data.link !== null) {
957
- P.entry_exit = P.data.link.entry_exit;
1106
+
1107
+ if (P.data.type === 'X' || P.data.type === 'DB') {
1108
+ P.entry_exit = status;
1109
+ status = (status === 'entry') ? 'exit' : 'entry';
1110
+ if (DEBUG) {
1111
+ console.log("mark:", P.coords.usrCoords, P.data.type, P.entry_exit);
1112
+ }
1113
+ }
1114
+
1115
+ if (P.data.type === 'DX') {
1116
+ if (intersection_chain === 0) {
1117
+ // Start of intersection chain.
1118
+ // No active intersection chain yet,
1119
+ // i.e. we did not pass a the first node of a delayed crossing.
1120
+ chain_start = P;
1121
+ intersection_chain = 1;
1122
+ if (DEBUG) {
1123
+ console.log("Start intersection chain:", P.coords.usrCoords, P.data.type, status);
1124
+ }
1125
+
1126
+ } else if (intersection_chain === 1) {
1127
+ // Active intersection chain (intersection_chain===1)!
1128
+ // End of delayed crossing chain reached
1129
+ P.entry_exit = status;
1130
+ chain_start.entry_exit = status;
1131
+ if (status === 'exit') {
1132
+ chain_start.data.type = 'X';
1133
+ } else {
1134
+ P.data.type = 'X';
1135
+ }
1136
+ status = (status === 'entry') ? 'exit' : 'entry';
1137
+
1138
+ if (DEBUG) {
1139
+ console.log("mark':", chain_start.coords.usrCoords, chain_start.data.type, chain_start.entry_exit);
1140
+ console.log("mark:", P.coords.usrCoords, P.data.type, P.entry_exit);
1141
+ }
1142
+ chain_start = null;
1143
+ intersection_chain = 0;
1144
+
1145
+ } else if (intersection_chain === 2) {
1146
+ // The delayed crossing had been interrupted by a crossing intersection.
1147
+ // Now we treat the end of the delayed crossing as regular crossing.
1148
+ P.entry_exit = status;
1149
+ P.data.type = 'X';
1150
+ status = (status === 'entry') ? 'exit' : 'entry';
1151
+ chain_start = null;
1152
+ intersection_chain = 0;
1153
+ }
958
1154
  }
959
1155
  }
960
- // if (P.intersection) { console.log("s>>>", P.coords.usrCoords, P.entry_exit)}
961
1156
 
962
1157
  P = P._next;
963
1158
  if (Type.exists(P._starter) || cnt > 10000) {
@@ -976,9 +1171,48 @@ define([
976
1171
  * @param {Boolean} isBackward
977
1172
  * @returns {Boolean} True, if the node is an intersection and is of type 'X'
978
1173
  */
979
- _isCrossing: function(P, isBackward) {
980
- isBackward = isBackward || false;
981
- return P.intersection && ((isBackward ? P.data.revtype : P.data.type) === 'X');
1174
+ _stayOnPath: function(P, status) {
1175
+ var stay = true;
1176
+
1177
+ if (P.intersection && P.data.type !== 'B') {
1178
+ stay = (status === P.entry_exit);
1179
+ }
1180
+ return stay;
1181
+ },
1182
+
1183
+ /**
1184
+ * Add a point to the clipping path and returns if the algorithms
1185
+ * arrived at an intersection point which has already been visited.
1186
+ * In this case, true is returned.
1187
+ *
1188
+ * @param {Array} path Resulting path
1189
+ * @param {JXG.Math.Clip.Vertex} vertex Point to be added
1190
+ * @param {Boolean} DEBUG debug output to console.log
1191
+ * @returns {Boolean} true: point has been visited before, false otherwise
1192
+ * @private
1193
+ */
1194
+ _addVertex: function(path, vertex, DEBUG) {
1195
+ if (!isNaN(vertex.coords.usrCoords[1]) && !isNaN(vertex.coords.usrCoords[2])) {
1196
+ path.push(vertex);
1197
+ }
1198
+ if (vertex.intersection && vertex.data.done) {
1199
+ if (DEBUG) {
1200
+ console.log("Add last intersection point", vertex.coords.usrCoords,
1201
+ "on", vertex.data.pathname, vertex.entry_exit,
1202
+ vertex.data.type);
1203
+ }
1204
+ return true;
1205
+ }
1206
+ if (vertex.intersection) {
1207
+ vertex.data.done = true;
1208
+
1209
+ if (DEBUG) {
1210
+ console.log("Add intersection point", vertex.coords.usrCoords,
1211
+ "on", vertex.data.pathname, vertex.entry_exit,
1212
+ vertex.data.type);
1213
+ }
1214
+ }
1215
+ return false;
982
1216
  },
983
1217
 
984
1218
  /**
@@ -998,24 +1232,30 @@ define([
998
1232
  tracing: function(S, S_intersect, clip_type) {
999
1233
  var P, current, start,
1000
1234
  cnt = 0,
1001
- reverse,
1235
+ status,
1002
1236
  maxCnt = 10000,
1003
1237
  S_idx = 0,
1004
- path = [];
1238
+ path = [],
1239
+ done = false,
1240
+ DEBUG = false;
1005
1241
 
1006
- // console.log("------ Start Phase 3");
1242
+ if (DEBUG) {
1243
+ console.log("\n------ Start Phase 3");
1244
+ }
1007
1245
 
1008
- reverse = (clip_type === 'difference' || clip_type === 'union') ? true : false;
1246
+ // reverse = (clip_type === 'difference' || clip_type === 'union') ? true : false;
1009
1247
  while (S_idx < S_intersect.length && cnt < maxCnt) {
1010
1248
  // Take the first intersection node of the subject path
1011
1249
  // which is not yet included as start point.
1012
1250
  current = S_intersect[S_idx];
1013
- if (current.data.done || !this._isCrossing(current, reverse)) {
1251
+ if (current.data.done || current.data.type !== 'X' /*|| !this._isCrossing(current, reverse)*/) {
1014
1252
  S_idx++;
1015
1253
  continue;
1016
1254
  }
1017
1255
 
1018
- // console.log("\nStart", current.data.pathname, current.coords.usrCoords, current.data.type, current.data.revtype, current.entry_exit, S_idx);
1256
+ if (DEBUG) {
1257
+ console.log("\nStart", current.data.pathname, current.coords.usrCoords, current.data.type, current.entry_exit, S_idx);
1258
+ }
1019
1259
  if (path.length > 0) { // Add a new path
1020
1260
  path.push([NaN, NaN]);
1021
1261
  }
@@ -1023,13 +1263,13 @@ define([
1023
1263
  // Start now the tracing with that node of the subject path
1024
1264
  start = current.data.idx;
1025
1265
  P = S;
1026
- do {
1027
- // Add the "current" intersection vertex.
1028
- path.push(current);
1029
- current.data.done = true;
1030
1266
 
1031
- // console.log("Add intersection", current.coords.usrCoords);
1032
- // console.log("AT", current.data.pathname, current.entry_exit, current.coords.usrCoords, current.data.type, current.data.revtype);
1267
+ done = this._addVertex(path, current, DEBUG);
1268
+ status = current.entry_exit;
1269
+ do {
1270
+ if (done) {
1271
+ break;
1272
+ }
1033
1273
  //
1034
1274
  // Decide if we follow the current path forward or backward.
1035
1275
  // for example, in case the clipping is of type "intersection"
@@ -1039,71 +1279,90 @@ define([
1039
1279
  (clip_type === 'union' && current.entry_exit === 'exit') ||
1040
1280
  (clip_type === 'difference' && (P === S) === (current.entry_exit === 'exit')) ) {
1041
1281
 
1282
+ if (DEBUG) {
1283
+ console.log("Go forward on", current.data.pathname, current.entry_exit);
1284
+ }
1285
+
1042
1286
  //
1043
1287
  // Take the next nodes and add them to the path
1044
1288
  // as long as they are not intersection nodes of type 'X'.
1045
1289
  //
1046
- current = current._next;
1047
1290
  do {
1048
- cnt++;
1049
-
1050
- if (!this._isCrossing(current, reverse)) {
1051
- if (!isNaN(current.coords.usrCoords[1]) && !isNaN(current.coords.usrCoords[2])) {
1052
- // if (true ||current.intersection) console.log("Add fw", current.coords.usrCoords, "NEXT", current._next.coords.usrCoords);
1053
- path.push(current);
1054
- }
1055
- current = current._next;
1291
+ current = current._next;
1292
+ done = this._addVertex(path, current, DEBUG);
1293
+ if (done) {
1294
+ break;
1056
1295
  }
1057
- } while (!this._isCrossing(current, reverse) && cnt < maxCnt);
1296
+ } while (this._stayOnPath(current, status));
1297
+ cnt++;
1058
1298
  } else {
1059
-
1299
+ if (DEBUG) {
1300
+ console.log("Go backward on", current.data.pathname);
1301
+ }
1060
1302
  //
1061
1303
  // Here, we go backward:
1062
1304
  // Take the previous nodes and add them to the path
1063
1305
  // as long as they are not intersection nodes of type 'X'.
1064
1306
  //
1065
- current = current._prev;
1066
1307
  do {
1067
- cnt++;
1068
-
1069
- if (!this._isCrossing(current, true)) {
1070
- if (!isNaN(current.coords.usrCoords[1]) && !isNaN(current.coords.usrCoords[2])) {
1071
- // if (true ||current.intersection) console.log("Add fw", current.coords.usrCoords);
1072
- path.push(current);
1073
- }
1074
- current = current._prev;
1308
+ current = current._prev;
1309
+ done = this._addVertex(path, current, DEBUG);
1310
+ if (done) {
1311
+ break;
1075
1312
  }
1076
- } while (!this._isCrossing(current, true) && cnt < maxCnt);
1313
+ } while (this._stayOnPath(current, status));
1314
+ cnt++;
1315
+ }
1316
+
1317
+ if (done) {
1318
+ break;
1077
1319
  }
1078
- current.data.done = true;
1079
1320
 
1080
1321
  if (!current.neighbour) {
1081
- console.log("BREAK!!!!!!!!!!!!!!!!!", cnt);
1322
+ console.log("Tracing: emergency break - no neighbour!!!!!!!!!!!!!!!!!", cnt);
1082
1323
  return [[0], [0]];
1083
1324
  }
1084
-
1085
- // console.log("Switch", current.coords.usrCoords, current.data.pathname, "to", current.neighbour.data.pathname);
1086
1325
  //
1087
- // We stopped the forwar or backward loop, because we've
1326
+ // We stopped the forward or backward loop, because we've
1088
1327
  // arrived at a crossing intersection node, i.e. we have to
1089
1328
  // switch to the other path now.
1329
+ if (DEBUG) {
1330
+ console.log("Switch from", current.coords.usrCoords, current.data.pathname, "to",
1331
+ current.neighbour.coords.usrCoords, "on", current.neighbour.data.pathname);
1332
+ }
1090
1333
  current = current.neighbour;
1091
1334
  if (current.data.done) {
1092
- // We arrived at an intersection node which is already
1093
- // added to the clipping path.
1094
- // We add it agian to close the clipping path and jump out of the
1095
- // loop.
1096
- path.push(current);
1097
- // console.log("Push last", current.coords.usrCoords);
1098
1335
  break;
1099
1336
  }
1337
+ current.data.done = true;
1338
+ status = current.entry_exit;
1339
+
1340
+ // if (current.data.done) {
1341
+ // // We arrived at an intersection node which is already
1342
+ // // added to the clipping path.
1343
+ // // We add it again to close the clipping path and jump out of the
1344
+ // // loop.
1345
+ // path.push(current);
1346
+ // if (DEBUG) {
1347
+ // console.log("Push last", current.coords.usrCoords);
1348
+ // }
1349
+ // break;
1350
+ // }
1100
1351
  P = current.data.path;
1101
1352
 
1102
- } while (!(current.data.pathname === 'S' && current.data.idx === start) && cnt < maxCnt);
1353
+ // Polygon closed:
1354
+ // if (DEBUG) {
1355
+ // console.log("End of loop:", "start=", start, "idx=", current.data.idx);
1356
+ // }
1357
+ // } while (!(current.data.pathname === 'S' && current.data.idx === start) && cnt < maxCnt);
1358
+ } while (current.data.idx !== start && cnt < maxCnt);
1359
+
1360
+ if (cnt >= maxCnt) {
1361
+ console.log("Tracing: stopping an infinite loop!", cnt);
1362
+ }
1103
1363
 
1104
1364
  S_idx++;
1105
1365
  }
1106
-
1107
1366
  return this._getCoordsArrays(path, false);
1108
1367
  },
1109
1368
 
@@ -1113,50 +1372,17 @@ define([
1113
1372
  * @param {Array} S First path, array of JXG.Coords
1114
1373
  * @param {Array} C Second path, array of JXG.Coords
1115
1374
  * @param {String} clip_type Type of Boolean operation: 'intersection', 'union', 'differrence'.
1116
- * @param {Array} pathX Array of x-coordinates of the resulting path
1117
- * @param {Array} pathY Array of y-coordinates of the resulting path
1118
1375
  * @return {Boolean} true, if one of the input paths is empty, false otherwise.
1119
1376
  */
1120
- isEmptyCase: function(S, C, clip_type, pathX, pathY) {
1121
- // var i;
1122
-
1377
+ isEmptyCase: function(S, C, clip_type) {
1123
1378
  if (clip_type === 'intersection' && (S.length === 0 || C.length === 0)) {
1124
- return true; //[pathX, pathY];
1379
+ return true;
1125
1380
  }
1126
- if (clip_type === 'union' && (S.length === 0 || C.length === 0)) {
1127
- // if (S.length === 0) {
1128
- // for (i = 0; i < C.length; ++i) {
1129
- // pathX.push(C[i].coords.usrCoords[1]);
1130
- // pathY.push(C[i].coords.usrCoords[2]);
1131
- // }
1132
- // if (C.length > 0) {
1133
- // pathX.push(C[0].coords.usrCoords[1]);
1134
- // pathY.push(C[0].coords.usrCoords[2]);
1135
- // }
1136
- // } else {
1137
- // for (i = 0; i < S.length; ++i) {
1138
- // pathX.push(S[i].coords.usrCoords[1]);
1139
- // pathY.push(S[i].coords.usrCoords[2]);
1140
- // }
1141
- // if (S.length > 0) {
1142
- // pathX.push(S[0].coords.usrCoords[1]);
1143
- // pathY.push(S[0].coords.usrCoords[2]);
1144
- // }
1145
- // }
1146
- return true; //[pathX, pathY];
1381
+ if (clip_type === 'union' && (S.length === 0 && C.length === 0)) {
1382
+ return true;
1147
1383
  }
1148
- if (clip_type === 'difference' && (S.length === 0 || C.length === 0)) {
1149
- // if (C.length === 0) {
1150
- // for (i = 0; i < S.length; ++i) {
1151
- // pathX.push(S[i].coords.usrCoords[1]);
1152
- // pathY.push(S[i].coords.usrCoords[2]);
1153
- // }
1154
- // if (S.length > 0) {
1155
- // pathX.push(S[0].coords.usrCoords[1]);
1156
- // pathY.push(S[0].coords.usrCoords[2]);
1157
- // }
1158
- // }
1159
- return true; //[pathX, pathY];
1384
+ if (clip_type === 'difference' && S.length === 0) {
1385
+ return true;
1160
1386
  }
1161
1387
 
1162
1388
  return false;
@@ -1186,11 +1412,6 @@ define([
1186
1412
  }
1187
1413
  }
1188
1414
 
1189
- // le = pathX.length;
1190
- // for (i = 0; i < le; i++) {
1191
- // console.log(pathX[i], pathY[i]);
1192
- // }
1193
-
1194
1415
  return [pathX, pathY];
1195
1416
  },
1196
1417
 
@@ -1231,22 +1452,11 @@ define([
1231
1452
  return this._getCoordsArrays(path, true);
1232
1453
  }
1233
1454
 
1234
- // From now on, both paths have non-zero length
1235
- //
1236
- // Handle union
1237
- if (clip_type === 'union') {
1238
- path = path.concat(S);
1239
- path.push(S[0]);
1240
- path.push([NaN, NaN]);
1241
- path = path.concat(C);
1242
- path.push(C[0]);
1243
- return this._getCoordsArrays(path, false);
1244
- }
1245
-
1455
+ // From now on, both paths have non-zero length.
1246
1456
  // The two paths have no crossing intersections,
1247
1457
  // but there might be bouncing intersections.
1248
1458
 
1249
- // First, we find - if possible - on each path a point which is not an intersection point.
1459
+ // First, we find -- if possible -- on each path a point which is not an intersection point.
1250
1460
  if (S.length > 0) {
1251
1461
  P = S[0];
1252
1462
  while (P.intersection) {
@@ -1268,12 +1478,15 @@ define([
1268
1478
 
1269
1479
  // Test if one curve is contained by the other
1270
1480
  if (this.windingNumber(P.coords.usrCoords, C) === 0) {
1271
- // S is outside of C.
1481
+ // P is outside of C:
1482
+ // Either S is disjoint from C or C is inside of S
1272
1483
  if (this.windingNumber(Q.coords.usrCoords, S) !== 0) {
1273
1484
  // C is inside of S, i.e. C subset of S
1274
1485
 
1275
- if (clip_type === 'difference') {
1276
-
1486
+ if (clip_type === 'union') {
1487
+ path = path.concat(S);
1488
+ path.push(S[0]);
1489
+ } else if (clip_type === 'difference') {
1277
1490
  path = path.concat(S);
1278
1491
  path.push(S[0]);
1279
1492
  if (Geometry.signedPolygon(S) * Geometry.signedPolygon(C) > 0) {
@@ -1282,13 +1495,21 @@ define([
1282
1495
  }
1283
1496
  path.push([NaN, NaN]);
1284
1497
  }
1285
- path = path.concat(C);
1286
- path.push(C[0]);
1287
- doClose = false;
1498
+ if (clip_type === 'difference' || clip_type === 'intersection') {
1499
+ path = path.concat(C);
1500
+ path.push(C[0]);
1501
+ doClose = false;
1502
+ }
1288
1503
  } else { // The curves are disjoint
1289
1504
  if (clip_type === 'difference') {
1290
1505
  path = path.concat(S);
1291
1506
  doClose = true;
1507
+ } else if (clip_type === 'union') {
1508
+ path = path.concat(S);
1509
+ path.push(S[0]);
1510
+ path.push([NaN, NaN]);
1511
+ path = path.concat(C);
1512
+ path.push(C[0]);
1292
1513
  }
1293
1514
  }
1294
1515
  } else {
@@ -1296,13 +1517,23 @@ define([
1296
1517
  if (clip_type === 'intersection') {
1297
1518
  path = path.concat(S);
1298
1519
  doClose = true;
1520
+ } else if (clip_type === 'union') {
1521
+ path = path.concat(C);
1522
+ path.push(C[0]);
1299
1523
  }
1524
+
1300
1525
  // 'difference': path is empty
1301
1526
  }
1302
1527
 
1303
1528
  return this._getCoordsArrays(path, doClose);
1304
1529
  },
1305
1530
 
1531
+ /**
1532
+ * Count intersection points of type 'X'.
1533
+ * @param {JXG.Mat.Clip.Vertex} intersections
1534
+ * @returns Number
1535
+ * @private
1536
+ */
1306
1537
  _countCrossingIntersections: function(intersections) {
1307
1538
  var i,
1308
1539
  le = intersections.length,
@@ -1317,7 +1548,8 @@ define([
1317
1548
  },
1318
1549
 
1319
1550
  /**
1320
- * Create path from all sorts of input elements to greinerHormann().
1551
+ * Create path from all sorts of input elements and convert it
1552
+ * to a suitable input path for greinerHormann().
1321
1553
  *
1322
1554
  * @private
1323
1555
  * @param {Object} obj Maybe curve, arc, sector, circle, polygon, array of points, array of JXG.Coords,
@@ -1599,12 +1831,12 @@ define([
1599
1831
  * [bbox[2], bbox[1]], // ur
1600
1832
  * [bbox[0], bbox[1]]] // ul
1601
1833
  * triangle = [[-1,1], [1,1], [0,-1], [-1,1]];
1602
- *
1834
+ *
1603
1835
  * var a = JXG.Math.Clip.greinerHormann(canvas, triangle, 'difference', this.board);
1604
1836
  * this.dataX = a[0];
1605
1837
  * this.dataY = a[1];
1606
1838
  * };
1607
- *
1839
+ *
1608
1840
  * </pre><div id="JXGe94da07a-2a01-4498-ad62-f71a327f8e25" class="jxgbox" style="width: 300px; height: 300px;"></div>
1609
1841
  * <script type="text/javascript">
1610
1842
  * (function() {
@@ -1642,9 +1874,11 @@ define([
1642
1874
  S_starters,
1643
1875
  C_starters,
1644
1876
  res = [],
1645
- pathX = [],
1646
- pathY = [];
1877
+ DEBUG = false;
1647
1878
 
1879
+ if (DEBUG) {
1880
+ console.log("\n------------ GREINER-HORMANN --------------");
1881
+ }
1648
1882
  // Collect all subject points into subject array S
1649
1883
  S = this._getPath(subject, board);
1650
1884
  len = S.length;
@@ -1661,53 +1895,24 @@ define([
1661
1895
  }
1662
1896
 
1663
1897
  // Handle cases where at least one of the paths is empty
1664
- if (this.isEmptyCase(S, C, clip_type, pathX, pathY)) {
1665
- return [pathX, pathY];
1898
+ if (this.isEmptyCase(S, C, clip_type)) {
1899
+ return [[], []];
1666
1900
  }
1667
1901
 
1668
1902
  // Add pointers for doubly linked lists
1669
1903
  S_starters = this.makeDoublyLinkedList(S);
1670
1904
  C_starters = this.makeDoublyLinkedList(C);
1671
1905
 
1672
- // this._print_array(S);
1673
- // console.log("Components:", S_starters);
1674
- // this._print_array(C);
1675
- // console.log("Components:", C_starters);
1906
+ if (DEBUG) {
1907
+ this._print_array(S);
1908
+ console.log("Components:", S_starters);
1909
+ this._print_array(C);
1910
+ console.log("Components:", C_starters);
1911
+ }
1676
1912
 
1677
1913
  res = this.findIntersections(S, C, board);
1678
1914
  S_intersect = res[0];
1679
1915
 
1680
- // console.log("------- START ------------------")
1681
- // let cnt = 0;
1682
- // for (let start of S_starters) {
1683
- // console.log("----")
1684
- // let P = S[start];
1685
- // P._start = true;
1686
- // do {
1687
- // console.log(">", P.coords.usrCoords, "NEXT", P._next.coords.usrCoords, "NEXT^2", P._next._next.coords.usrCoords)
1688
- // P = P._next;
1689
- // cnt++;
1690
- // } while (!P._start && cnt < 15);
1691
- // P._start = null;
1692
- // }
1693
- // console.log("------- END ------------------")
1694
-
1695
- // C_intersect = res[1];
1696
-
1697
- // For non-closed paths
1698
- // if (true && typeof subject_first_point_type === 'string') {
1699
- // S[0].neighbour = C[C.length - 1];
1700
- // S[0].first_point_type = subject_first_point_type;
1701
- // S[S.length - 1].neighbour = C[0];
1702
- // S[S.length - 1].first_point_type = subject_first_point_type;
1703
- // }
1704
- // if (true && typeof clip_first_point_type === 'string') {
1705
- // C[0].neighbour = S[S.length - 1];
1706
- // C[0].first_point_type = clip_first_point_type;
1707
- // C[C.length - 1].neighbour = S[0];
1708
- // C[C.length - 1].first_point_type = clip_first_point_type;
1709
- // }
1710
-
1711
1916
  this._handleFullyDegenerateCase(S, C, board);
1712
1917
 
1713
1918
  // Phase 2: mark intersection points as entry or exit points