jsxgraph 1.4.0 → 1.4.3
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 +1 -1
- package/distrib/README.md +1 -1
- package/distrib/geogebra.min.js +3 -3
- package/distrib/geonext.min.js +3 -3
- package/distrib/index.d.ts +5205 -4629
- package/distrib/intergeo.min.js +3 -3
- package/distrib/jsxgraph.css +10 -15
- package/distrib/jsxgraphcore.js +9 -25
- package/distrib/jsxgraphsrc.js +4497 -2180
- package/distrib/sketch.min.js +3 -3
- package/package.json +3 -2
- package/src/3d/box3d.js +264 -0
- package/src/3d/curve3d.js +97 -0
- package/src/3d/linspace3d.js +375 -0
- package/src/3d/point3d.js +200 -0
- package/src/3d/surface3d.js +81 -0
- package/src/3d/threed.js +38 -0
- package/src/3d/view3d.js +459 -0
- package/src/base/board.js +139 -85
- package/src/base/chart.js +1 -1
- package/src/base/circle.js +1 -1
- package/src/base/composition.js +1 -1
- package/src/base/constants.js +15 -14
- package/src/base/coords.js +3 -3
- package/src/base/coordselement.js +5 -8
- package/src/base/curve.js +1 -1
- package/src/base/element.js +36 -28
- package/src/base/foreignobject.js +12 -2
- package/src/base/group.js +1 -1
- package/src/base/image.js +1 -1
- package/src/base/line.js +2 -2
- package/src/base/point.js +8 -18
- package/src/base/polygon.js +89 -34
- package/src/base/text.js +8 -7
- package/src/base/ticks.js +17 -3
- package/src/base/transformation.js +1 -1
- package/src/base/turtle.js +1 -1
- package/src/element/arc.js +1 -1
- package/src/element/button.js +1 -1
- package/src/element/checkbox.js +1 -1
- package/src/element/comb.js +1 -1
- package/src/element/composition.js +2 -2
- package/src/element/conic.js +3 -3
- package/src/element/input.js +1 -1
- package/src/element/locus.js +1 -1
- package/src/element/measure.js +9 -4
- package/src/element/sector.js +5 -5
- package/src/element/slider.js +29 -12
- package/src/element/slopetriangle.js +1 -1
- package/src/jsxgraph.js +9 -3
- package/src/jxg.js +3 -3
- package/src/loadjsxgraph.js +18 -12
- package/src/math/bst.js +1 -1
- package/src/math/clip.js +550 -345
- package/src/math/complex.js +1 -1
- package/src/math/extrapolate.js +1 -1
- package/src/math/geometry.js +85 -6
- package/src/math/ia.js +69 -69
- package/src/math/math.js +10 -1
- package/src/math/metapost.js +1 -1
- package/src/math/nlp.js +4 -4
- package/src/math/numerics.js +2 -1
- package/src/math/plot.js +4 -4
- package/src/math/poly.js +1 -1
- package/src/math/probfuncs.js +2 -1
- package/src/math/qdt.js +11 -11
- package/src/math/statistics.js +10 -10
- package/src/math/symbolic.js +1 -1
- package/src/options.js +96 -40
- package/src/options3d.js +123 -0
- package/src/parser/ca.js +1297 -1291
- package/src/parser/datasource.js +1 -1
- package/src/parser/geonext.js +6 -6
- package/src/parser/jessiecode.js +369 -73
- package/src/parser/jessiescript.js +1 -1
- package/src/reader/cinderella.js +1 -1
- package/src/reader/file.js +1 -1
- package/src/reader/geogebra.js +1 -1
- package/src/reader/geonext.js +1 -1
- package/src/reader/graph.js +1 -1
- package/src/reader/intergeo.js +1 -1
- package/src/reader/sketch.js +7 -7
- package/src/renderer/abstract.js +8 -2
- package/src/renderer/canvas.js +9 -5
- package/src/renderer/no.js +2 -1
- package/src/renderer/svg.js +10 -13
- package/src/renderer/vml.js +1 -1
- package/src/server/server.js +1 -1
- package/src/utils/base64.js +2 -2
- package/src/utils/color.js +8 -9
- package/src/utils/dump.js +20 -5
- package/src/utils/env.js +13 -54
- package/src/utils/event.js +1 -1
- package/src/utils/expect.js +1 -1
- package/src/utils/type.js +12 -10
- package/src/utils/xml.js +1 -1
- package/src/utils/zip.js +1 -1
- package/CHANGELOG.md +0 -1366
package/src/math/clip.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Copyright 2008-
|
|
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
|
-
|
|
308
|
+
// console.log("Crossings", P_crossings[i])
|
|
311
309
|
last = P_crossings[i].length - 1;
|
|
312
310
|
P = P_crossings[i][0];
|
|
313
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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 =
|
|
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
|
-
//
|
|
530
|
-
|
|
531
|
-
|
|
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
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
//
|
|
540
|
-
|
|
541
|
-
//
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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
|
-
|
|
635
|
-
|
|
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
|
|
642
|
-
return '
|
|
670
|
+
if (s1 >= 0 || s2 >= 0) {
|
|
671
|
+
return 'left';
|
|
643
672
|
}
|
|
644
|
-
return '
|
|
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.
|
|
699
|
+
P._tours = 0;
|
|
653
700
|
while (true) {
|
|
654
|
-
|
|
655
|
-
|
|
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
|
-
|
|
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
|
-
|
|
673
|
-
Pm = P._prev.coords.usrCoords;
|
|
720
|
+
Pm = P._prev._prev.coords.usrCoords;
|
|
674
721
|
}
|
|
675
722
|
|
|
676
723
|
Q = P.neighbour;
|
|
677
|
-
Qm =
|
|
678
|
-
Qp =
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
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
|
-
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
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 (
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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.
|
|
820
|
+
P.data.type = 'B';
|
|
724
821
|
}
|
|
725
822
|
}
|
|
726
823
|
}
|
|
727
824
|
|
|
728
|
-
if (
|
|
729
|
-
(
|
|
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.
|
|
747
|
-
P.
|
|
831
|
+
if (Type.exists(P._tours)) {
|
|
832
|
+
P._tours++;
|
|
748
833
|
}
|
|
749
|
-
|
|
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.
|
|
754
|
-
delete P.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
784
|
-
|
|
785
|
-
|
|
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
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
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
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
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("
|
|
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
|
-
|
|
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
|
|
949
|
-
P.
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
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
|
-
|
|
956
|
-
|
|
957
|
-
|
|
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
|
-
|
|
980
|
-
|
|
981
|
-
|
|
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
|
-
|
|
1235
|
+
status,
|
|
1002
1236
|
maxCnt = 10000,
|
|
1003
1237
|
S_idx = 0,
|
|
1004
|
-
path = []
|
|
1238
|
+
path = [],
|
|
1239
|
+
done = false,
|
|
1240
|
+
DEBUG = false;
|
|
1005
1241
|
|
|
1006
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1032
|
-
|
|
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
|
-
|
|
1049
|
-
|
|
1050
|
-
if (
|
|
1051
|
-
|
|
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 (
|
|
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
|
-
|
|
1068
|
-
|
|
1069
|
-
if (
|
|
1070
|
-
|
|
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 (
|
|
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("
|
|
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
|
|
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
|
-
|
|
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
|
|
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;
|
|
1379
|
+
return true;
|
|
1125
1380
|
}
|
|
1126
|
-
if (clip_type === 'union' && (S.length === 0
|
|
1127
|
-
|
|
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' &&
|
|
1149
|
-
|
|
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
|
|
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
|
-
//
|
|
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 === '
|
|
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
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
1665
|
-
return [
|
|
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
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
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
|