1 /* 2 Copyright 2008-2024 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Alfred Wassermann 7 8 This file is part of JSXGraph. 9 10 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 11 12 You can redistribute it and/or modify it under the terms of the 13 14 * GNU Lesser General Public License as published by 15 the Free Software Foundation, either version 3 of the License, or 16 (at your option) any later version 17 OR 18 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 19 20 JSXGraph is distributed in the hope that it will be useful, 21 but WITHOUT ANY WARRANTY; without even the implied warranty of 22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 GNU Lesser General Public License for more details. 24 25 You should have received a copy of the GNU Lesser General Public License and 26 the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/> 27 and <https://opensource.org/licenses/MIT/>. 28 */ 29 30 /*global JXG: true, define: true*/ 31 /*jslint nomen: true, plusplus: true*/ 32 33 /** 34 * @fileoverview This file contains the Math.Clip namespace for clipping and computing boolean operations 35 * on polygons and curves 36 * 37 * // TODO: 38 * * Check if input polygons are closed. If not, handle this case. 39 */ 40 41 // import JXG from "../jxg.js"; 42 import Const from "../base/constants.js"; 43 import Coords from "../base/coords.js"; 44 import Mat from "./math.js"; 45 import Geometry from "./geometry.js"; 46 import Type from "../utils/type.js"; 47 48 /** 49 * Math.Clip namespace definition. This namespace contains algorithms for Boolean operations on paths, i.e. 50 * intersection, union and difference of paths. Base is the Greiner-Hormann algorithm. 51 * @name JXG.Math.Clip 52 * @exports Mat.Clip as JXG.Math.Clip 53 * @namespace 54 */ 55 Mat.Clip = { 56 _isSeparator: function (node) { 57 return isNaN(node.coords.usrCoords[1]) && isNaN(node.coords.usrCoords[2]); 58 }, 59 60 /** 61 * Add pointers to an array S such that it is a circular doubly-linked list. 62 * 63 * @private 64 * @param {Array} S Array 65 * @return {Array} return containing the starter indices of each component. 66 */ 67 makeDoublyLinkedList: function (S) { 68 var i, 69 first = null, 70 components = [], 71 le = S.length; 72 73 if (le > 0) { 74 for (i = 0; i < le; i++) { 75 // S[i]._next = S[(i + 1) % le]; 76 // S[i]._prev = S[(le + i - 1) % le]; 77 78 // If S[i] is component separator we proceed with the next node. 79 if (this._isSeparator(S[i])) { 80 S[i]._next = S[(i + 1) % le]; 81 S[i]._prev = S[(le + i - 1) % le]; 82 continue; 83 } 84 85 // Now we know that S[i] is a path component 86 if (first === null) { 87 // Start the component if it is not yet started. 88 first = i; 89 components.push(first); 90 } 91 if (this._isSeparator(S[(i + 1) % le]) || i === le - 1) { 92 // If the next node is a component separator or if the node is the last node, 93 // then we close the loop 94 95 S[i]._next = S[first]; 96 S[first]._prev = S[i]; 97 S[i]._end = true; 98 first = null; 99 } else { 100 // Here, we are not at the end of component 101 S[i]._next = S[(i + 1) % le]; 102 S[first]._prev = S[i]; 103 } 104 if (!this._isSeparator(S[(le + i - 1) % le])) { 105 S[i]._prev = S[(le + i - 1) % le]; 106 } 107 } 108 } 109 return components; 110 }, 111 112 /** 113 * JavaScript object containing the intersection of two paths. Every intersection point is on one path, but 114 * comes with a neighbour point having the same coordinates and being on the other path. 115 * 116 * The intersection point is inserted into the doubly linked list of the path. 117 * 118 * @private 119 * @param {JXG.Coords} coords JSXGraph Coords object containing the coordinates of the intersection 120 * @param {Number} i Number of the segment of the subject path (first path) containing the intersection. 121 * @param {Number} alpha The intersection is a p_1 + alpha*(p_2 - p_1), where p_1 and p_2 are the end points 122 * of the i-th segment. 123 * @param {Array} path Pointer to the path containing the intersection point 124 * @param {String} pathname Name of the path: 'S' or 'C'. 125 */ 126 Vertex: function (coords, i, alpha, path, pathname, type) { 127 this.pos = i; 128 this.intersection = true; 129 this.coords = coords; 130 this.elementClass = Const.OBJECT_CLASS_POINT; 131 132 this.data = { 133 alpha: alpha, 134 path: path, 135 pathname: pathname, 136 done: false, 137 type: type, 138 idx: 0 139 }; 140 141 // Set after initialisation 142 this.neighbour = null; 143 this.entry_exit = false; 144 }, 145 146 _addToList: function (list, coords, pos) { 147 var len = list.length, 148 eps = Mat.eps * Mat.eps; 149 150 if ( 151 len > 0 && 152 Math.abs(list[len - 1].coords.usrCoords[0] - coords.usrCoords[0]) < eps && 153 Math.abs(list[len - 1].coords.usrCoords[1] - coords.usrCoords[1]) < eps && 154 Math.abs(list[len - 1].coords.usrCoords[2] - coords.usrCoords[2]) < eps 155 ) { 156 // Skip point 157 return; 158 } 159 list.push({ 160 pos: pos, 161 intersection: false, 162 coords: coords, 163 elementClass: Const.OBJECT_CLASS_POINT 164 }); 165 }, 166 167 /** 168 * Sort the intersection points into their path. 169 * @private 170 * @param {Array} P_crossings Array of arrays. Each array contains the intersections of the path 171 * with one segment of the other path. 172 * @return {Array} Array of intersection points ordered by first occurrence in the path. 173 */ 174 sortIntersections: function (P_crossings) { 175 var i, 176 j, 177 P, 178 Q, 179 last, 180 next_node, 181 P_intersect = [], 182 P_le = P_crossings.length; 183 184 for (i = 0; i < P_le; i++) { 185 P_crossings[i].sort(function (a, b) { 186 return a.data.alpha > b.data.alpha ? 1 : -1; 187 }); 188 189 if (P_crossings[i].length > 0) { 190 // console.log("Crossings", P_crossings[i]) 191 last = P_crossings[i].length - 1; 192 P = P_crossings[i][0]; 193 194 //console.log("SORT", P.coords.usrCoords) 195 Q = P.data.path[P.pos]; 196 next_node = Q._next; // Store the next "normal" node 197 198 if (i === P_le - 1) { 199 Q._end = false; 200 } 201 202 if (P.data.alpha === 0.0 && P.data.type === "T") { 203 // console.log("SKIP", P.coords.usrCoords, P.data.type, P.neighbour.data.type); 204 Q.intersection = true; 205 Q.data = P.data; 206 Q.neighbour = P.neighbour; 207 Q.neighbour.neighbour = Q; 208 Q.entry_exit = false; 209 P_crossings[i][0] = Q; 210 } else { 211 // Insert the first intersection point 212 P._prev = Q; 213 P._prev._next = P; 214 } 215 216 // Insert the other intersection points, but the last 217 for (j = 1; j <= last; j++) { 218 P = P_crossings[i][j]; 219 P._prev = P_crossings[i][j - 1]; 220 P._prev._next = P; 221 } 222 223 // Link last intersection point to the next node 224 P = P_crossings[i][last]; 225 P._next = next_node; 226 P._next._prev = P; 227 228 if (i === P_le - 1) { 229 P._end = true; 230 //console.log("END", P._end, P.coords.usrCoords, P._prev.coords.usrCoords, P._next.coords.usrCoords); 231 } 232 233 P_intersect = P_intersect.concat(P_crossings[i]); 234 } 235 } 236 return P_intersect; 237 }, 238 239 _inbetween: function (q, p1, p2) { 240 var alpha, 241 eps = Mat.eps * Mat.eps, 242 px = p2[1] - p1[1], 243 py = p2[2] - p1[2], 244 qx = q[1] - p1[1], 245 qy = q[2] - p1[2]; 246 247 if (px === 0 && py === 0 && qx === 0 && qy === 0) { 248 // All three points are equal 249 return true; 250 } 251 if (Math.abs(qx) < eps && Math.abs(px) < eps) { 252 alpha = qy / py; 253 } else { 254 alpha = qx / px; 255 } 256 if (Math.abs(alpha) < eps) { 257 alpha = 0.0; 258 } 259 return alpha; 260 }, 261 262 _print_array: function (arr) { 263 var i, end; 264 for (i = 0; i < arr.length; i++) { 265 //console.log(i, arr[i].coords.usrCoords, arr[i].data.type); 266 try { 267 end = ""; 268 if (arr[i]._end) { 269 end = " end"; 270 } 271 console.log( 272 i, 273 arr[i].coords.usrCoords, 274 arr[i].data.type, 275 "\t", 276 "prev", 277 arr[i]._prev.coords.usrCoords, 278 "next", 279 arr[i]._next.coords.usrCoords + end 280 ); 281 } catch (e) { 282 console.log(i, arr[i].coords.usrCoords); 283 } 284 } 285 }, 286 287 _print_list: function (P) { 288 var cnt = 0, 289 alpha; 290 while (cnt < 100) { 291 if (P.data) { 292 alpha = P.data.alpha; 293 } else { 294 alpha = "-"; 295 } 296 console.log( 297 "\t", 298 P.coords.usrCoords, 299 "\n\t\tis:", 300 P.intersection, 301 "end:", 302 P._end, 303 alpha, 304 "\n\t\t-:", 305 P._prev.coords.usrCoords, 306 "\n\t\t+:", 307 P._next.coords.usrCoords, 308 "\n\t\tn:", 309 P.intersection ? P.neighbour.coords.usrCoords : "-" 310 ); 311 if (P._end) { 312 break; 313 } 314 P = P._next; 315 cnt++; 316 } 317 }, 318 319 _noOverlap: function (p1, p2, q1, q2) { 320 var k, 321 eps = Math.sqrt(Mat.eps), 322 minp, 323 maxp, 324 minq, 325 maxq, 326 no_overlap = false; 327 328 for (k = 0; k < 3; k++) { 329 minp = Math.min(p1[k], p2[k]); 330 maxp = Math.max(p1[k], p2[k]); 331 minq = Math.min(q1[k], q2[k]); 332 maxq = Math.max(q1[k], q2[k]); 333 if (maxp < minq - eps || minp > maxq + eps) { 334 no_overlap = true; 335 break; 336 } 337 } 338 return no_overlap; 339 }, 340 341 /** 342 * Find all intersections between two paths. 343 * @private 344 * @param {Array} S Subject path 345 * @param {Array} C Clip path 346 * @param {JXG.Board} board JSXGraph board object. It is needed to convert between 347 * user coordinates and screen coordinates. 348 * @return {Array} Array containing two arrays. The first array contains the intersection vertices 349 * of the subject path and the second array contains the intersection vertices of the clip path. 350 * @see JXG.Math.Clip.Vertex 351 */ 352 findIntersections: function (S, C, board) { 353 var res = [], eps = Mat.eps * 100, 354 i, j, crds, 355 S_le = S.length, 356 C_le = C.length, 357 Si, Si1, Cj, Cj1, d1, d2, 358 alpha, type, IS, IC, 359 S_intersect = [], 360 C_intersect = [], 361 S_crossings = [], 362 C_crossings = [], 363 hasMultCompsS = false, 364 hasMultCompsC = false, 365 DEBUG = false; 366 367 for (j = 0; j < C_le; j++) { 368 C_crossings.push([]); 369 } 370 371 // Run through the subject path. 372 for (i = 0; i < S_le; i++) { 373 S_crossings.push([]); 374 375 // Test if S[i] or its successor is a path separator. 376 // If yes, we know that the path consists of multiple components. 377 // We immediately jump to the next segment. 378 if (this._isSeparator(S[i]) || this._isSeparator(S[(i + 1) % S_le])) { 379 hasMultCompsS = true; 380 continue; 381 } 382 383 // If the path consists of multiple components then there is 384 // no path-closing segment between the last node and the first 385 // node. In this case we can leave the loop now. 386 if (hasMultCompsS && i === S_le - 1) { 387 break; 388 } 389 390 Si = S[i].coords.usrCoords; 391 Si1 = S[(i + 1) % S_le].coords.usrCoords; 392 // Run through the clip path. 393 for (j = 0; j < C_le; j++) { 394 // Test if C[j] or its successor is a path separator. 395 // If yes, we know that the path consists of multiple components. 396 // We immediately jump to the next segment. 397 if (this._isSeparator(C[j]) || this._isSeparator(C[(j + 1) % C_le])) { 398 hasMultCompsC = true; 399 continue; 400 } 401 402 // If the path consists of multiple components then there is 403 // no path-closing segment between the last node and the first 404 // node. In this case we can leave the loop now. 405 if (hasMultCompsC && j === C_le - 1) { 406 break; 407 } 408 409 // Test if bounding boxes of the two curve segments overlap 410 // If not, the expensive intersection test can be skipped. 411 Cj = C[j].coords.usrCoords; 412 Cj1 = C[(j + 1) % C_le].coords.usrCoords; 413 414 if (this._noOverlap(Si, Si1, Cj, Cj1)) { 415 continue; 416 } 417 418 // Intersection test 419 res = Geometry.meetSegmentSegment(Si, Si1, Cj, Cj1); 420 421 d1 = Geometry.distance(Si, Si1, 3); 422 d2 = Geometry.distance(Cj, Cj1, 3); 423 424 // Found an intersection point 425 if ( 426 // "Regular" intersection 427 (res[1] * d1 > -eps && 428 res[1] < 1 - eps / d1 && 429 res[2] * d2 > -eps && 430 res[2] < 1 - eps / d2) || 431 // Collinear segments 432 (res[1] === Infinity && res[2] === Infinity && Mat.norm(res[0], 3) < eps) 433 ) { 434 crds = new Coords(Const.COORDS_BY_USER, res[0], board); 435 type = "X"; 436 437 // Handle degenerated cases 438 if (Math.abs(res[1]) * d1 < eps || Math.abs(res[2]) * d2 < eps) { 439 // Crossing / bouncing at vertex or 440 // end of delayed crossing / bouncing 441 type = "T"; 442 if (Math.abs(res[1]) * d1 < eps) { 443 res[1] = 0; 444 } 445 if (Math.abs(res[2]) * d2 < eps) { 446 res[2] = 0; 447 } 448 if (res[1] === 0) { 449 crds = new Coords(Const.COORDS_BY_USER, Si, board); 450 } else { 451 crds = new Coords(Const.COORDS_BY_USER, Cj, board); 452 } 453 454 if (DEBUG) { 455 console.log( 456 "Degenerate case I", 457 res[1], 458 res[2], 459 crds.usrCoords, 460 "type", 461 type 462 ); 463 } 464 } else if ( 465 res[1] === Infinity && 466 res[2] === Infinity && 467 Mat.norm(res[0], 3) < eps 468 ) { 469 // console.log(C_intersect); 470 471 // Collinear segments 472 // Here, there might be two intersection points to be added 473 474 alpha = this._inbetween(Si, Cj, Cj1); 475 if (DEBUG) { 476 // console.log("alpha Si", alpha, Si); 477 // console.log(j, Cj) 478 // console.log((j + 1) % C_le, Cj1) 479 } 480 if (alpha >= 0 && alpha < 1) { 481 type = "T"; 482 crds = new Coords(Const.COORDS_BY_USER, Si, board); 483 res[1] = 0; 484 res[2] = alpha; 485 IS = new this.Vertex(crds, i, res[1], S, "S", type); 486 IC = new this.Vertex(crds, j, res[2], C, "C", type); 487 IS.neighbour = IC; 488 IC.neighbour = IS; 489 S_crossings[i].push(IS); 490 C_crossings[j].push(IC); 491 if (DEBUG) { 492 console.log( 493 "Degenerate case II", 494 res[1], 495 res[2], 496 crds.usrCoords, 497 "type T" 498 ); 499 } 500 } 501 alpha = this._inbetween(Cj, Si, Si1); 502 if (DEBUG) { 503 // console.log("alpha Cj", alpha, Si, Geometry.distance(Si, Cj, 3)); 504 } 505 if (Geometry.distance(Si, Cj, 3) > eps && alpha >= 0 && alpha < 1) { 506 type = "T"; 507 crds = new Coords(Const.COORDS_BY_USER, Cj, board); 508 res[1] = alpha; 509 res[2] = 0; 510 IS = new this.Vertex(crds, i, res[1], S, "S", type); 511 IC = new this.Vertex(crds, j, res[2], C, "C", type); 512 IS.neighbour = IC; 513 IC.neighbour = IS; 514 S_crossings[i].push(IS); 515 C_crossings[j].push(IC); 516 if (DEBUG) { 517 console.log( 518 "Degenerate case III", 519 res[1], 520 res[2], 521 crds.usrCoords, 522 "type T" 523 ); 524 } 525 } 526 continue; 527 } 528 if (DEBUG) { 529 console.log("IS", i, j, crds.usrCoords, type); 530 } 531 532 IS = new this.Vertex(crds, i, res[1], S, "S", type); 533 IC = new this.Vertex(crds, j, res[2], C, "C", type); 534 IS.neighbour = IC; 535 IC.neighbour = IS; 536 537 S_crossings[i].push(IS); 538 C_crossings[j].push(IC); 539 } 540 } 541 } 542 543 // For both paths, sort their intersection points 544 S_intersect = this.sortIntersections(S_crossings); 545 546 if (DEBUG) { 547 console.log(">>>>>> Intersections "); 548 console.log("S_intersect"); 549 this._print_array(S_intersect); 550 console.log("----------"); 551 } 552 for (i = 0; i < S_intersect.length; i++) { 553 S_intersect[i].data.idx = i; 554 S_intersect[i].neighbour.data.idx = i; 555 } 556 C_intersect = this.sortIntersections(C_crossings); 557 558 if (DEBUG) { 559 console.log("C_intersect"); 560 this._print_array(C_intersect); 561 console.log("<<<<<< Phase 1 done"); 562 } 563 return [S_intersect, C_intersect]; 564 }, 565 566 /** 567 * It is testedd if the point q lies to the left or right 568 * of the poylgonal chain [p1, p2, p3]. 569 * @param {Array} q User coords array 570 * @param {Array} p1 User coords array 571 * @param {Array} p2 User coords array 572 * @param {Array} p3 User coords array 573 * @returns string 'left' or 'right' 574 * @private 575 */ 576 _getPosition: function (q, p1, p2, p3) { 577 var s1 = Geometry.det3p(q, p1, p2), 578 s2 = Geometry.det3p(q, p2, p3), 579 s3 = Geometry.det3p(p1, p2, p3); 580 581 // Left turn 582 if (s3 >= 0) { 583 if (s1 >= 0 && s2 >= 0) { 584 return "left"; 585 } 586 return "right"; 587 } 588 // Right turn 589 if (s1 >= 0 || s2 >= 0) { 590 return "left"; 591 } 592 return "right"; 593 }, 594 595 /** 596 * Determine the delayed status of degenerated intersection points. 597 * It is of the form 598 * ['on|left|right', 'on|left|right'] 599 * <p> 600 * If all four determinants are zero, we add random noise to the point. 601 * 602 * @param {JXG.Math.Clip.Vertex} P Start of path 603 * @private 604 * @see JXG.Math.Clip.markEntryExit 605 * @see JXG.Math.Clip._handleIntersectionChains 606 */ 607 _classifyDegenerateIntersections: function (P) { 608 var Pp, Pm, Qp, Qm, Q, 609 side, cnt, tmp, det, 610 oppositeDir, 611 s1, s2, s3, s4, 612 endless = true, 613 DEBUG = false; 614 615 if (DEBUG) { 616 console.log( 617 "\n-------------- _classifyDegenerateIntersections()", 618 Type.exists(P.data) ? P.data.pathname : " " 619 ); 620 } 621 det = Geometry.det3p; 622 cnt = 0; 623 P._tours = 0; 624 while (endless) { 625 if (DEBUG) { 626 console.log("Inspect P:", P.coords.usrCoords, P.data ? P.data.type : " "); 627 } 628 if (P.intersection && P.data.type === "T") { 629 // Handle the degenerate cases 630 // Decide if they are (delayed) bouncing or crossing intersections 631 Pp = P._next.coords.usrCoords; // P+ 632 Pm = P._prev.coords.usrCoords; // P- 633 634 // If the intersection point is degenerated and 635 // equal to the start and end of one component, 636 // then there will be two adjacent points with 637 // the same coordinate. 638 // In that case, we proceed to the next node. 639 if (Geometry.distance(P.coords.usrCoords, Pp, 3) < Mat.eps) { 640 Pp = P._next._next.coords.usrCoords; 641 } 642 if (Geometry.distance(P.coords.usrCoords, Pm, 3) < Mat.eps) { 643 Pm = P._prev._prev.coords.usrCoords; 644 } 645 646 Q = P.neighbour; 647 Qm = Q._prev.coords.usrCoords; // Q- 648 Qp = Q._next.coords.usrCoords; // Q+ 649 if (Geometry.distance(Q.coords.usrCoords, Qp, 3) < Mat.eps) { 650 Qp = Q._next._next.coords.usrCoords; 651 } 652 if (Geometry.distance(Q.coords.usrCoords, Qm, 3) < Mat.eps) { 653 Qm = Q._prev._prev.coords.usrCoords; 654 } 655 656 if (DEBUG) { 657 console.log("P chain:", Pm, P.coords.usrCoords, Pp); 658 console.log("Q chain:", Qm, P.neighbour.coords.usrCoords, Qp); 659 console.log("Pm", this._getPosition(Pm, Qm, Q.coords.usrCoords, Qp)); 660 console.log("Pp", this._getPosition(Pp, Qm, Q.coords.usrCoords, Qp)); 661 } 662 663 s1 = det(P.coords.usrCoords, Pm, Qm); 664 s2 = det(P.coords.usrCoords, Pp, Qp); 665 s3 = det(P.coords.usrCoords, Pm, Qp); 666 s4 = det(P.coords.usrCoords, Pp, Qm); 667 668 if (s1 === 0 && s2 === 0 && s3 === 0 && s4 === 0) { 669 P.coords.usrCoords[1] *= 1 + Math.random() * Mat.eps; 670 P.coords.usrCoords[2] *= 1 + Math.random() * Mat.eps; 671 Q.coords.usrCoords[1] = P.coords.usrCoords[1]; 672 Q.coords.usrCoords[2] = P.coords.usrCoords[2]; 673 s1 = det(P.coords.usrCoords, Pm, Qm); 674 s2 = det(P.coords.usrCoords, Pp, Qp); 675 s3 = det(P.coords.usrCoords, Pm, Qp); 676 s4 = det(P.coords.usrCoords, Pp, Qm); 677 if (DEBUG) { 678 console.log("Random shift", P.coords.usrCoords); 679 console.log(s1, s2, s3, s4, s2 === 0); 680 console.log( 681 this._getPosition(Pm, Qm, Q.coords.usrCoords, Qp), 682 this._getPosition(Pp, Qm, Q.coords.usrCoords, Qp) 683 ); 684 } 685 } 686 oppositeDir = false; 687 if (s1 === 0) { 688 // Q-, Q=P, P- on straight line 689 if (Geometry.affineRatio(P.coords.usrCoords, Pm, Qm) < 0) { 690 oppositeDir = true; 691 } 692 } else if (s2 === 0) { 693 if (Geometry.affineRatio(P.coords.usrCoords, Pp, Qp) < 0) { 694 oppositeDir = true; 695 } 696 } else if (s3 === 0) { 697 if (Geometry.affineRatio(P.coords.usrCoords, Pm, Qp) > 0) { 698 oppositeDir = true; 699 } 700 } else if (s4 === 0) { 701 if (Geometry.affineRatio(P.coords.usrCoords, Pp, Qm) > 0) { 702 oppositeDir = true; 703 } 704 } 705 if (oppositeDir) { 706 // Swap Qm and Qp 707 // Then Qm Q Qp has the same direction as Pm P Pp 708 tmp = Qm; 709 Qm = Qp; 710 Qp = tmp; 711 tmp = s1; 712 s1 = s3; 713 s3 = tmp; 714 tmp = s2; 715 s2 = s4; 716 s4 = tmp; 717 } 718 719 if (DEBUG) { 720 console.log(s1, s2, s3, s4, oppositeDir); 721 } 722 723 if (!Type.exists(P.delayedStatus)) { 724 P.delayedStatus = []; 725 } 726 727 if (s1 === 0 && s2 === 0) { 728 // Line [P-,P] equals [Q-,Q] and line [P,P+] equals [Q,Q+] 729 // Interior of delayed crossing / bouncing 730 P.delayedStatus = ["on", "on"]; 731 } else if (s1 === 0) { 732 // P- on line [Q-,Q], P+ not on line [Q,Q+] 733 // Begin / end of delayed crossing / bouncing 734 side = this._getPosition(Pp, Qm, Q.coords.usrCoords, Qp); 735 P.delayedStatus = ["on", side]; 736 } else if (s2 === 0) { 737 // P+ on line [Q,Q+], P- not on line [Q-,Q] 738 // Begin / end of delayed crossing / bouncing 739 side = this._getPosition(Pm, Qm, Q.coords.usrCoords, Qp); 740 P.delayedStatus = [side, "on"]; 741 } else { 742 // Neither P+ on line [Q,Q+], nor P- on line [Q-,Q] 743 // No delayed crossing / bouncing 744 if (P.delayedStatus.length === 0) { 745 if ( 746 this._getPosition(Pm, Qm, Q.coords.usrCoords, Qp) !== 747 this._getPosition(Pp, Qm, Q.coords.usrCoords, Qp) 748 ) { 749 P.data.type = "X"; 750 } else { 751 P.data.type = "B"; 752 } 753 } 754 } 755 756 if (DEBUG) { 757 console.log( 758 ">>>> P:", 759 P.coords.usrCoords, 760 "delayedStatus:", 761 P.delayedStatus.toString(), 762 P.data ? P.data.type : " ", 763 "\n---" 764 ); 765 } 766 } 767 768 if (Type.exists(P._tours)) { 769 P._tours++; 770 } 771 772 if (P._tours > 3 || P._end || cnt > 1000) { 773 // Jump out if either 774 // - we reached the end 775 // - there are more than 1000 intersection points 776 // - P._tours > 3: We went already 4 times through this path. 777 if (cnt > 1000) { 778 console.log("Clipping: _classifyDegenerateIntersections exit"); 779 } 780 if (Type.exists(P._tours)) { 781 delete P._tours; 782 } 783 break; 784 } 785 if (P.intersection) { 786 cnt++; 787 } 788 P = P._next; 789 } 790 if (DEBUG) { 791 console.log("------------------------"); 792 } 793 }, 794 795 /** 796 * At this point the degenerated intersections have been classified. 797 * Now we decide if the intersection chains of the given path 798 * ultimatively cross the other path or bounce. 799 * 800 * @param {JXG.Math.Clip.Vertex} P Start of path 801 * 802 * @see JXG.Math.Clip.markEntryExit 803 * @see JXG.Math.Clip._classifyDegenerateIntersections 804 * @private 805 */ 806 _handleIntersectionChains: function (P) { 807 var cnt = 0, 808 start_status = "Null", 809 P_start, 810 endless = true, 811 intersection_chain = false, 812 wait_for_exit = false, 813 DEBUG = false; 814 815 if (DEBUG) { 816 console.log( 817 "\n-------------- _handleIntersectionChains()", 818 Type.exists(P.data) ? P.data.pathname : " " 819 ); 820 } 821 while (endless) { 822 if (P.intersection === true) { 823 if (DEBUG) { 824 if (P.data.type === "T") { 825 console.log( 826 "Degenerate point", 827 P.coords.usrCoords, 828 P.data.type, 829 P.data.type === "T" ? P.delayedStatus : " " 830 ); 831 } else { 832 console.log("Intersection point", P.coords.usrCoords, P.data.type); 833 } 834 } 835 if (P.data.type === "T") { 836 if (P.delayedStatus[0] !== "on" && P.delayedStatus[1] === "on") { 837 // First point of intersection chain 838 intersection_chain = true; 839 P_start = P; 840 start_status = P.delayedStatus[0]; 841 } else if ( 842 intersection_chain && 843 P.delayedStatus[0] === "on" && 844 P.delayedStatus[1] === "on" 845 ) { 846 // Interior of intersection chain 847 P.data.type = "B"; 848 if (DEBUG) { 849 console.log("Interior", P.coords.usrCoords); 850 } 851 } else if ( 852 intersection_chain && 853 P.delayedStatus[0] === "on" && 854 P.delayedStatus[1] !== "on" 855 ) { 856 // Last point of intersection chain 857 intersection_chain = false; 858 if (start_status === P.delayedStatus[1]) { 859 // Intersection chain is delayed bouncing 860 P_start.data.type = "DB"; 861 P.data.type = "DB"; 862 if (DEBUG) { 863 console.log( 864 "Chain: delayed bouncing", 865 P_start.coords.usrCoords, 866 "...", 867 P.coords.usrCoords 868 ); 869 } 870 } else { 871 // Intersection chain is delayed crossing 872 P_start.data.type = "DX"; 873 P.data.type = "DX"; 874 if (DEBUG) { 875 console.log( 876 "Chain: delayed crossing", 877 P_start.coords.usrCoords, 878 "...", 879 P.coords.usrCoords 880 ); 881 } 882 } 883 } 884 } 885 cnt++; 886 } 887 if (P._end) { 888 wait_for_exit = true; 889 } 890 if (wait_for_exit && !intersection_chain) { 891 break; 892 } 893 if (cnt > 1000) { 894 console.log( 895 "Warning: _handleIntersectionChains: intersection chain reached maximum numbers of iterations" 896 ); 897 break; 898 } 899 P = P._next; 900 } 901 }, 902 903 /** 904 * Handle the case that all vertices of one path are contained 905 * in the other path. In this case we search for a midpoint of an edge 906 * which is not contained in the other path and add it to the path. 907 * It will be used as starting point for the entry/exit algorithm. 908 * 909 * @private 910 * @param {Array} S Subject path 911 * @param {Array} C Clip path 912 * @param {JXG.board} board JSXGraph board object. It is needed to convert between 913 * user coordinates and screen coordinates. 914 */ 915 _handleFullyDegenerateCase: function (S, C, board) { 916 var P, Q, l, M, crds, 917 q1, q2, node, i, j, 918 leP, leQ, is_on_Q, 919 tmp, is_fully_degenerated, 920 arr = [S, C]; 921 922 for (l = 0; l < 2; l++) { 923 P = arr[l]; 924 leP = P.length; 925 for (i = 0, is_fully_degenerated = true; i < leP; i++) { 926 if (!P[i].intersection) { 927 is_fully_degenerated = false; 928 break; 929 } 930 } 931 932 if (is_fully_degenerated) { 933 // All nodes of P are also on the other path. 934 Q = arr[(l + 1) % 2]; 935 leQ = Q.length; 936 937 // We search for a midpoint of one edge of P which is not the other path and 938 // we add that midpoint to P. 939 for (i = 0; i < leP; i++) { 940 q1 = P[i].coords.usrCoords; 941 q2 = P[i]._next.coords.usrCoords; 942 943 // M is the midpoint 944 M = [(q1[0] + q2[0]) * 0.5, (q1[1] + q2[1]) * 0.5, (q1[2] + q2[2]) * 0.5]; 945 946 // Test if M is on path Q. If this is not the case, 947 // we take M as additional point of P. 948 for (j = 0, is_on_Q = false; j < leQ; j++) { 949 if ( 950 Math.abs( 951 Geometry.det3p( 952 Q[j].coords.usrCoords, 953 Q[(j + 1) % leQ].coords.usrCoords, 954 M 955 ) 956 ) < Mat.eps 957 ) { 958 is_on_Q = true; 959 break; 960 } 961 } 962 if (!is_on_Q) { 963 // The midpoint is added to the doubly-linked list. 964 crds = new Coords(Const.COORDS_BY_USER, M, board); 965 node = { 966 pos: i, 967 intersection: false, 968 coords: crds, 969 elementClass: Const.OBJECT_CLASS_POINT 970 }; 971 972 tmp = P[i]._next; 973 P[i]._next = node; 974 node._prev = P[i]; 975 node._next = tmp; 976 tmp._prev = node; 977 978 if (P[i]._end) { 979 P[i]._end = false; 980 node._end = true; 981 } 982 983 break; 984 } 985 } 986 } 987 } 988 }, 989 990 _getStatus: function (P, path) { 991 var status; 992 while (P.intersection) { 993 if (P._end) { 994 break; 995 } 996 P = P._next; 997 } 998 if (Geometry.windingNumber(P.coords.usrCoords, path) === 0) { 999 // Outside 1000 status = "entry"; 1001 // console.log(P.coords.usrCoords, ' is outside') 1002 } else { 1003 // Inside 1004 status = "exit"; 1005 // console.log(P.coords.usrCoords, ' is inside') 1006 } 1007 1008 return [P, status]; 1009 }, 1010 1011 /** 1012 * Mark the intersection vertices of path1 as entry points or as exit points 1013 * in respect to path2. 1014 * <p> 1015 * This is the simple algorithm as in 1016 * Greiner, Günther; Kai Hormann (1998). "Efficient clipping of arbitrary polygons". 1017 * ACM Transactions on Graphics. 17 (2): 71–83 1018 * <p> 1019 * The algorithm handles also "delayed crossings" from 1020 * Erich, L. Foster, and Kai Hormann, Kai, and Romeo Traaian Popa (2019), 1021 * "Clipping simple polygons with degenerate intersections", Computers & Graphics:X, 2. 1022 * and - as an additional improvement - 1023 * handles self intersections of delayed crossings (A.W. 2021). 1024 * 1025 * @private 1026 * @param {Array} path1 First path 1027 * @param {Array} path2 Second path 1028 */ 1029 markEntryExit: function (path1, path2, starters) { 1030 var status, P, cnt, res, 1031 i, len, start, 1032 endless = true, 1033 chain_start = null, 1034 intersection_chain = 0, 1035 DEBUG = false; 1036 1037 len = starters.length; 1038 for (i = 0; i < len; i++) { 1039 start = starters[i]; 1040 if (DEBUG) { 1041 console.log( 1042 "\n;;;;;;;;;; Labelling phase", 1043 Type.exists(path1[start].data) ? path1[start].data.pathname : " ", 1044 path1[start].coords.usrCoords 1045 ); 1046 } 1047 this._classifyDegenerateIntersections(path1[start]); 1048 this._handleIntersectionChains(path1[start]); 1049 if (DEBUG) { 1050 console.log("\n---- back to markEntryExit"); 1051 } 1052 1053 // Decide if the first point of the component is inside or outside 1054 // of the other path. 1055 res = this._getStatus(path1[start], path2); 1056 P = res[0]; 1057 status = res[1]; 1058 if (DEBUG) { 1059 console.log("Start node:", P.coords.usrCoords, status); 1060 } 1061 1062 P._starter = true; 1063 1064 // Greiner-Hormann entry/exit algorithm 1065 // with additional handling of delayed crossing / bouncing 1066 cnt = 0; 1067 chain_start = null; 1068 intersection_chain = 0; 1069 1070 while (endless) { 1071 if (P.intersection === true) { 1072 if (P.data.type === "X" && intersection_chain === 1) { 1073 // While we are in an intersection chain, i.e. a delayed crossing, 1074 // we stumble on a crossing intersection. 1075 // Probably, the other path is self intersecting. 1076 // We end the intersection chain here and 1077 // mark this event by setting intersection_chain = 2. 1078 chain_start.entry_exit = status; 1079 if (status === "exit") { 1080 chain_start.data.type = "X"; 1081 } 1082 intersection_chain = 2; 1083 } 1084 1085 if (P.data.type === "X" || P.data.type === "DB") { 1086 P.entry_exit = status; 1087 status = status === "entry" ? "exit" : "entry"; 1088 if (DEBUG) { 1089 console.log("mark:", P.coords.usrCoords, P.data.type, P.entry_exit); 1090 } 1091 } 1092 1093 if (P.data.type === "DX") { 1094 if (intersection_chain === 0) { 1095 // Start of intersection chain. 1096 // No active intersection chain yet, 1097 // i.e. we did not pass a the first node of a delayed crossing. 1098 chain_start = P; 1099 intersection_chain = 1; 1100 if (DEBUG) { 1101 console.log( 1102 "Start intersection chain:", 1103 P.coords.usrCoords, 1104 P.data.type, 1105 status 1106 ); 1107 } 1108 } else if (intersection_chain === 1) { 1109 // Active intersection chain (intersection_chain===1)! 1110 // End of delayed crossing chain reached 1111 P.entry_exit = status; 1112 chain_start.entry_exit = status; 1113 if (status === "exit") { 1114 chain_start.data.type = "X"; 1115 } else { 1116 P.data.type = "X"; 1117 } 1118 status = status === "entry" ? "exit" : "entry"; 1119 1120 if (DEBUG) { 1121 console.log( 1122 "mark':", 1123 chain_start.coords.usrCoords, 1124 chain_start.data.type, 1125 chain_start.entry_exit 1126 ); 1127 console.log( 1128 "mark:", 1129 P.coords.usrCoords, 1130 P.data.type, 1131 P.entry_exit 1132 ); 1133 } 1134 chain_start = null; 1135 intersection_chain = 0; 1136 } else if (intersection_chain === 2) { 1137 // The delayed crossing had been interrupted by a crossing intersection. 1138 // Now we treat the end of the delayed crossing as regular crossing. 1139 P.entry_exit = status; 1140 P.data.type = "X"; 1141 status = status === "entry" ? "exit" : "entry"; 1142 chain_start = null; 1143 intersection_chain = 0; 1144 } 1145 } 1146 } 1147 1148 P = P._next; 1149 if (Type.exists(P._starter) || cnt > 10000) { 1150 break; 1151 } 1152 1153 cnt++; 1154 } 1155 } 1156 }, 1157 1158 /** 1159 * 1160 * @private 1161 * @param {Array} P 1162 * @param {Boolean} isBackward 1163 * @returns {Boolean} True, if the node is an intersection and is of type 'X' 1164 */ 1165 _stayOnPath: function (P, status) { 1166 var stay = true; 1167 1168 if (P.intersection && P.data.type !== "B") { 1169 stay = status === P.entry_exit; 1170 } 1171 return stay; 1172 }, 1173 1174 /** 1175 * Add a point to the clipping path and returns if the algorithms 1176 * arrived at an intersection point which has already been visited. 1177 * In this case, true is returned. 1178 * 1179 * @param {Array} path Resulting path 1180 * @param {JXG.Math.Clip.Vertex} vertex Point to be added 1181 * @param {Boolean} DEBUG debug output to console.log 1182 * @returns {Boolean} true: point has been visited before, false otherwise 1183 * @private 1184 */ 1185 _addVertex: function (path, vertex, DEBUG) { 1186 if (!isNaN(vertex.coords.usrCoords[1]) && !isNaN(vertex.coords.usrCoords[2])) { 1187 path.push(vertex); 1188 } 1189 if (vertex.intersection && vertex.data.done) { 1190 if (DEBUG) { 1191 console.log( 1192 "Add last intersection point", 1193 vertex.coords.usrCoords, 1194 "on", 1195 vertex.data.pathname, 1196 vertex.entry_exit, 1197 vertex.data.type 1198 ); 1199 } 1200 return true; 1201 } 1202 if (vertex.intersection) { 1203 vertex.data.done = true; 1204 1205 if (DEBUG) { 1206 console.log( 1207 "Add intersection point", 1208 vertex.coords.usrCoords, 1209 "on", 1210 vertex.data.pathname, 1211 vertex.entry_exit, 1212 vertex.data.type 1213 ); 1214 } 1215 } 1216 return false; 1217 }, 1218 1219 /** 1220 * Tracing phase of the Greiner-Hormann algorithm, see 1221 * Greiner, Günther; Kai Hormann (1998). 1222 * "Efficient clipping of arbitrary polygons". ACM Transactions on Graphics. 17 (2): 71–83 1223 * 1224 * Boolean operations on polygons are distinguished: 'intersection', 'union', 'difference'. 1225 * 1226 * @private 1227 * @param {Array} S Subject path 1228 * @param {Array} S_intersect Array containing the intersection vertices of the subject path 1229 * @param {String} clip_type contains the Boolean operation: 'intersection', 'union', or 'difference' 1230 * @return {Array} Array consisting of two arrays containing the x-coordinates and the y-coordintaes of 1231 * the resulting path. 1232 */ 1233 tracing: function (S, S_intersect, clip_type) { 1234 var P, status, current, start, 1235 cnt = 0, 1236 maxCnt = 10000, 1237 S_idx = 0, 1238 path = [], 1239 done = false, 1240 DEBUG = false; 1241 1242 if (DEBUG) { 1243 console.log("\n------ Start Phase 3"); 1244 } 1245 1246 // reverse = (clip_type === 'difference' || clip_type === 'union') ? true : false; 1247 while (S_idx < S_intersect.length && cnt < maxCnt) { 1248 // Take the first intersection node of the subject path 1249 // which is not yet included as start point. 1250 current = S_intersect[S_idx]; 1251 if ( 1252 current.data.done || 1253 current.data.type !== "X" /*|| !this._isCrossing(current, reverse)*/ 1254 ) { 1255 S_idx++; 1256 continue; 1257 } 1258 1259 if (DEBUG) { 1260 console.log( 1261 "\nStart", 1262 current.data.pathname, 1263 current.coords.usrCoords, 1264 current.data.type, 1265 current.entry_exit, 1266 S_idx 1267 ); 1268 } 1269 if (path.length > 0) { 1270 // Add a new path 1271 path.push([NaN, NaN]); 1272 } 1273 1274 // Start now the tracing with that node of the subject path 1275 start = current.data.idx; 1276 P = S; 1277 1278 done = this._addVertex(path, current, DEBUG); 1279 status = current.entry_exit; 1280 do { 1281 if (done) { 1282 break; 1283 } 1284 // 1285 // Decide if we follow the current path forward or backward. 1286 // for example, in case the clipping is of type "intersection" 1287 // and the current intersection node is of type entry, we go forward. 1288 // 1289 if ( 1290 (clip_type === "intersection" && current.entry_exit === "entry") || 1291 (clip_type === "union" && current.entry_exit === "exit") || 1292 (clip_type === "difference" && 1293 (P === S) === (current.entry_exit === "exit")) 1294 ) { 1295 if (DEBUG) { 1296 console.log("Go forward on", current.data.pathname, current.entry_exit); 1297 } 1298 1299 // 1300 // Take the next nodes and add them to the path 1301 // as long as they are not intersection nodes of type 'X'. 1302 // 1303 do { 1304 current = current._next; 1305 done = this._addVertex(path, current, DEBUG); 1306 if (done) { 1307 break; 1308 } 1309 } while (this._stayOnPath(current, status)); 1310 cnt++; 1311 } else { 1312 if (DEBUG) { 1313 console.log("Go backward on", current.data.pathname); 1314 } 1315 // 1316 // Here, we go backward: 1317 // Take the previous nodes and add them to the path 1318 // as long as they are not intersection nodes of type 'X'. 1319 // 1320 do { 1321 current = current._prev; 1322 done = this._addVertex(path, current, DEBUG); 1323 if (done) { 1324 break; 1325 } 1326 } while (this._stayOnPath(current, status)); 1327 cnt++; 1328 } 1329 1330 if (done) { 1331 break; 1332 } 1333 1334 if (!current.neighbour) { 1335 console.log( 1336 "Tracing: emergency break - no neighbour!!!!!!!!!!!!!!!!!", 1337 cnt 1338 ); 1339 return [[0], [0]]; 1340 } 1341 // 1342 // We stopped the forward or backward loop, because we've 1343 // arrived at a crossing intersection node, i.e. we have to 1344 // switch to the other path now. 1345 if (DEBUG) { 1346 console.log( 1347 "Switch from", 1348 current.coords.usrCoords, 1349 current.data.pathname, 1350 "to", 1351 current.neighbour.coords.usrCoords, 1352 "on", 1353 current.neighbour.data.pathname 1354 ); 1355 } 1356 current = current.neighbour; 1357 if (current.data.done) { 1358 break; 1359 } 1360 current.data.done = true; 1361 status = current.entry_exit; 1362 1363 // if (current.data.done) { 1364 // // We arrived at an intersection node which is already 1365 // // added to the clipping path. 1366 // // We add it again to close the clipping path and jump out of the 1367 // // loop. 1368 // path.push(current); 1369 // if (DEBUG) { 1370 // console.log("Push last", current.coords.usrCoords); 1371 // } 1372 // break; 1373 // } 1374 P = current.data.path; 1375 1376 // Polygon closed: 1377 // if (DEBUG) { 1378 // console.log("End of loop:", "start=", start, "idx=", current.data.idx); 1379 // } 1380 // } while (!(current.data.pathname === 'S' && current.data.idx === start) && cnt < maxCnt); 1381 } while (current.data.idx !== start && cnt < maxCnt); 1382 1383 if (cnt >= maxCnt) { 1384 console.log("Tracing: stopping an infinite loop!", cnt); 1385 } 1386 1387 S_idx++; 1388 } 1389 return this._getCoordsArrays(path, false); 1390 }, 1391 1392 /** 1393 * Handle path clipping if one of the two paths is empty. 1394 * @private 1395 * @param {Array} S First path, array of JXG.Coords 1396 * @param {Array} C Second path, array of JXG.Coords 1397 * @param {String} clip_type Type of Boolean operation: 'intersection', 'union', 'differrence'. 1398 * @return {Boolean} true, if one of the input paths is empty, false otherwise. 1399 */ 1400 isEmptyCase: function (S, C, clip_type) { 1401 if (clip_type === "intersection" && (S.length === 0 || C.length === 0)) { 1402 return true; 1403 } 1404 if (clip_type === "union" && S.length === 0 && C.length === 0) { 1405 return true; 1406 } 1407 if (clip_type === "difference" && S.length === 0) { 1408 return true; 1409 } 1410 1411 return false; 1412 }, 1413 1414 _getCoordsArrays: function (path, doClose) { 1415 var pathX = [], 1416 pathY = [], 1417 i, 1418 le = path.length; 1419 1420 for (i = 0; i < le; i++) { 1421 if (path[i].coords) { 1422 pathX.push(path[i].coords.usrCoords[1]); 1423 pathY.push(path[i].coords.usrCoords[2]); 1424 } else { 1425 pathX.push(path[i][0]); 1426 pathY.push(path[i][1]); 1427 } 1428 } 1429 if (doClose && le > 0) { 1430 if (path[0].coords) { 1431 pathX.push(path[0].coords.usrCoords[1]); 1432 pathY.push(path[0].coords.usrCoords[2]); 1433 } else { 1434 pathX.push(path[0][0]); 1435 pathY.push(path[0][1]); 1436 } 1437 } 1438 1439 return [pathX, pathY]; 1440 }, 1441 1442 /** 1443 * Handle cases when there are no intersection points of the two paths. This is the case if the 1444 * paths are disjoint or one is contained in the other. 1445 * @private 1446 * @param {Array} S First path, array of JXG.Coords 1447 * @param {Array} C Second path, array of JXG.Coords 1448 * @param {String} clip_type Type of Boolean operation: 'intersection', 'union', 'differrence'. 1449 * @return {Array} Array consisting of two arrays containing the x-coordinates and the y-coordinates of 1450 * the resulting path. 1451 */ 1452 handleEmptyIntersection: function (S, C, clip_type) { 1453 var P, 1454 Q, 1455 doClose = false, 1456 path = []; 1457 1458 // Handle trivial cases 1459 if (S.length === 0) { 1460 if (clip_type === "union") { 1461 // S cup C = C 1462 path = C; 1463 } else { 1464 // S cap C = S \ C = {} 1465 path = []; 1466 } 1467 return this._getCoordsArrays(path, true); 1468 } 1469 if (C.length === 0) { 1470 if (clip_type === "intersection") { 1471 // S cap C = {} 1472 path = []; 1473 } else { 1474 // S cup C = S \ C = S 1475 path = S; 1476 } 1477 return this._getCoordsArrays(path, true); 1478 } 1479 1480 // From now on, both paths have non-zero length. 1481 // The two paths have no crossing intersections, 1482 // but there might be bouncing intersections. 1483 1484 // First, we find -- if possible -- on each path a point which is not an intersection point. 1485 if (S.length > 0) { 1486 P = S[0]; 1487 while (P.intersection) { 1488 P = P._next; 1489 if (P._end) { 1490 break; 1491 } 1492 } 1493 } 1494 if (C.length > 0) { 1495 Q = C[0]; 1496 while (Q.intersection) { 1497 Q = Q._next; 1498 if (Q._end) { 1499 break; 1500 } 1501 } 1502 } 1503 1504 // Test if one curve is contained by the other 1505 if (Geometry.windingNumber(P.coords.usrCoords, C) === 0) { 1506 // P is outside of C: 1507 // Either S is disjoint from C or C is inside of S 1508 if (Geometry.windingNumber(Q.coords.usrCoords, S) !== 0) { 1509 // C is inside of S, i.e. C subset of S 1510 1511 if (clip_type === "union") { 1512 Type.concat(path, S); 1513 path.push(S[0]); 1514 } else if (clip_type === "difference") { 1515 Type.concat(path, S); 1516 path.push(S[0]); 1517 if (Geometry.signedPolygon(S) * Geometry.signedPolygon(C) > 0) { 1518 // Pathes have same orientation, we have to revert one. 1519 path.reverse(); 1520 } 1521 path.push([NaN, NaN]); 1522 } 1523 if (clip_type === "difference" || clip_type === "intersection") { 1524 Type.concat(path, C); 1525 path.push(C[0]); 1526 doClose = false; 1527 } 1528 } else { 1529 // The curves are disjoint 1530 if (clip_type === "difference") { 1531 Type.concat(path, S); 1532 doClose = true; 1533 } else if (clip_type === "union") { 1534 Type.concat(path, S); 1535 path.push(S[0]); 1536 path.push([NaN, NaN]); 1537 Type.concat(path, C); 1538 path.push(C[0]); 1539 } 1540 } 1541 } else { 1542 // S inside of C, i.e. S subset of C 1543 if (clip_type === "intersection") { 1544 Type.concat(path, S); 1545 doClose = true; 1546 } else if (clip_type === "union") { 1547 Type.concat(path, C); 1548 path.push(C[0]); 1549 } 1550 1551 // 'difference': path is empty 1552 } 1553 1554 return this._getCoordsArrays(path, doClose); 1555 }, 1556 1557 /** 1558 * Count intersection points of type 'X'. 1559 * @param {JXG.Mat.Clip.Vertex} intersections 1560 * @returns Number 1561 * @private 1562 */ 1563 _countCrossingIntersections: function (intersections) { 1564 var i, 1565 le = intersections.length, 1566 sum = 0; 1567 1568 for (i = 0; i < le; i++) { 1569 if (intersections[i].data.type === "X") { 1570 sum++; 1571 } 1572 } 1573 return sum; 1574 }, 1575 1576 /** 1577 * Create path from all sorts of input elements and convert it 1578 * to a suitable input path for greinerHormann(). 1579 * 1580 * @private 1581 * @param {Object} obj Maybe curve, arc, sector, circle, polygon, array of points, array of JXG.Coords, 1582 * array of coordinate pairs. 1583 * @param {JXG.Board} board JSXGraph board object. It is needed to convert between 1584 * user coordinates and screen coordinates. 1585 * @returns {Array} Array of JXG.Coords elements containing a path. 1586 * @see JXG.Math.Clip.greinerHormann 1587 */ 1588 _getPath: function (obj, board) { 1589 var i, len, r, 1590 rad, angle, alpha, steps, 1591 S = []; 1592 1593 // Collect all points into path array S 1594 if ( 1595 obj.elementClass === Const.OBJECT_CLASS_CURVE && 1596 (obj.type === Const.OBJECT_TYPE_ARC || obj.type === Const.OBJECT_TYPE_SECTOR) 1597 ) { 1598 angle = Geometry.rad(obj.radiuspoint, obj.center, obj.anglepoint); 1599 steps = Math.floor((angle * 180) / Math.PI); 1600 r = obj.Radius(); 1601 rad = angle / steps; 1602 alpha = Math.atan2( 1603 obj.radiuspoint.coords.usrCoords[2] - obj.center.coords.usrCoords[2], 1604 obj.radiuspoint.coords.usrCoords[1] - obj.center.coords.usrCoords[1] 1605 ); 1606 1607 if (obj.type === Const.OBJECT_TYPE_SECTOR) { 1608 this._addToList(S, obj.center.coords, 0); 1609 } 1610 for (i = 0; i <= steps; i++) { 1611 this._addToList( 1612 S, 1613 new Coords( 1614 Const.COORDS_BY_USER, 1615 [ 1616 obj.center.coords.usrCoords[0], 1617 obj.center.coords.usrCoords[1] + Math.cos(i * rad + alpha) * r, 1618 obj.center.coords.usrCoords[2] + Math.sin(i * rad + alpha) * r 1619 ], 1620 board 1621 ), 1622 i + 1 1623 ); 1624 } 1625 if (obj.type === Const.OBJECT_TYPE_SECTOR) { 1626 this._addToList(S, obj.center.coords, steps + 2); 1627 } 1628 } else if (obj.elementClass === Const.OBJECT_CLASS_CURVE && Type.exists(obj.points)) { 1629 len = obj.numberPoints; 1630 for (i = 0; i < len; i++) { 1631 this._addToList(S, obj.points[i], i); 1632 } 1633 } else if (obj.type === Const.OBJECT_TYPE_POLYGON) { 1634 for (i = 0; i < obj.vertices.length; i++) { 1635 this._addToList(S, obj.vertices[i].coords, i); 1636 } 1637 } else if (obj.elementClass === Const.OBJECT_CLASS_CIRCLE) { 1638 steps = 359; 1639 r = obj.Radius(); 1640 rad = (2 * Math.PI) / steps; 1641 for (i = 0; i <= steps; i++) { 1642 this._addToList( 1643 S, 1644 new Coords( 1645 Const.COORDS_BY_USER, 1646 [ 1647 obj.center.coords.usrCoords[0], 1648 obj.center.coords.usrCoords[1] + Math.cos(i * rad) * r, 1649 obj.center.coords.usrCoords[2] + Math.sin(i * rad) * r 1650 ], 1651 board 1652 ), 1653 i 1654 ); 1655 } 1656 } else if (Type.isArray(obj)) { 1657 len = obj.length; 1658 for (i = 0; i < len; i++) { 1659 if (Type.exists(obj[i].coords)) { 1660 // Point type 1661 this._addToList(S, obj[i].coords, i); 1662 } else if (Type.isArray(obj[i])) { 1663 // Coordinate pair 1664 this._addToList(S, new Coords(Const.COORDS_BY_USER, obj[i], board), i); 1665 } else if (Type.exists(obj[i].usrCoords)) { 1666 // JXG.Coordinates 1667 this._addToList(S, obj[i], i); 1668 } 1669 } 1670 } 1671 1672 return S; 1673 }, 1674 1675 /** 1676 * Determine the intersection, union or difference of two closed paths. 1677 * <p> 1678 * This is an implementation of the Greiner-Hormann algorithm, see 1679 * Günther Greiner and Kai Hormann (1998). 1680 * "Efficient clipping of arbitrary polygons". ACM Transactions on Graphics. 17 (2): 71–83. 1681 * and 1682 * Erich, L. Foster, and Kai Hormann, Kai, and Romeo Traaian Popa (2019), 1683 * "Clipping simple polygons with degenerate intersections", Computers & Graphics:X, 2. 1684 * <p> 1685 * It is assumed that the pathes are closed, whereby it does not matter if the last point indeed 1686 * equals the first point. In contrast to the original Greiner-Hormann algorithm, 1687 * this algorithm can cope with many degenerate cases. A degenerate case is a vertext of one path 1688 * which is contained in the other path. 1689 * <p> 1690 * 1691 * <p>Problematic are: 1692 * <ul> 1693 * <li>degenerate cases where one path additionally has self-intersections 1694 * <li>differences with one path having self-intersections. 1695 * </ul> 1696 * 1697 * @param {JXG.Circle|JXG.Curve|JXG.Polygon} subject First closed path, usually called 'subject'. 1698 * Maybe curve, arc, sector, circle, polygon, array of points, array of JXG.Coords, 1699 * array of coordinate pairs. 1700 * @param {JXG.Circle|JXG.Curve|JXG.Polygon} clip Second closed path, usually called 'clip'. 1701 * Maybe curve, arc, sector, circle, polygon, array of points, array of JXG.Coords, 1702 * array of coordinate pairs. 1703 * @param {String} clip_type Determines the type of boolean operation on the two paths. 1704 * Possible values are 'intersection', 'union', or 'difference'. 1705 * @param {JXG.Board} board JSXGraph board object. It is needed to convert between 1706 * user coordinates and screen coordinates. 1707 * @return {Array} Array consisting of two arrays containing the x-coordinates and the y-coordinates of 1708 * the resulting path. 1709 * 1710 * @see JXG.Math.Clip.intersection 1711 * @see JXG.Math.Clip.union 1712 * @see JXG.Math.Clip.difference 1713 * 1714 * @example 1715 * var curve1 = board.create('curve', [ 1716 * [-3, 3, 0, -3], 1717 * [3, 3, 0, 3] 1718 * ], 1719 * {strokeColor: 'black'}); 1720 * 1721 * var curve2 = board.create('curve', [ 1722 * [-4, 4, 0, -4], 1723 * [2, 2, 4, 2] 1724 * ], 1725 * {strokeColor: 'blue'}); 1726 * 1727 * var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.6}); 1728 * clip_path.updateDataArray = function() { 1729 * var a = JXG.Math.Clip.greinerHormann(curve2, curve1, 'intersection', this.board); 1730 * 1731 * this.dataX = a[0]; 1732 * this.dataY = a[1]; 1733 * }; 1734 * 1735 * board.update(); 1736 * 1737 * </pre><div id="JXG9d2a6acf-a43b-4035-8f8a-9b1bee580210" class="jxgbox" style="width: 300px; height: 300px;"></div> 1738 * <script type="text/javascript"> 1739 * (function() { 1740 * var board = JXG.JSXGraph.initBoard('JXG9d2a6acf-a43b-4035-8f8a-9b1bee580210', 1741 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1742 * 1743 * var curve1 = board.create('curve', [ 1744 * [-3, 3, 0, -3], 1745 * [3, 3, 0, 3] 1746 * ], 1747 * {strokeColor: 'black'}); 1748 * 1749 * var curve2 = board.create('curve', [ 1750 * [-4, 4, 0, -4], 1751 * [2, 2, 4, 2] 1752 * ], 1753 * {strokeColor: 'blue'}); 1754 * 1755 * var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.6}); 1756 * clip_path.updateDataArray = function() { 1757 * var a = JXG.Math.Clip.greinerHormann(curve2, curve1, 'intersection', this.board); 1758 * 1759 * this.dataX = a[0]; 1760 * this.dataY = a[1]; 1761 * }; 1762 * 1763 * board.update(); 1764 * 1765 * })(); 1766 * 1767 * </script><pre> 1768 * 1769 * @example 1770 * var curve1 = board.create('curve', [ 1771 * [-3, 3, 0, -3], 1772 * [3, 3, 0, 3] 1773 * ], 1774 * {strokeColor: 'black', fillColor: 'none', fillOpacity: 0.8}); 1775 * 1776 * var curve2 = board.create('polygon', [[3, 4], [-4, 0], [-4, 4]], 1777 * {strokeColor: 'blue', fillColor: 'none'}); 1778 * 1779 * var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.6}); 1780 * clip_path.updateDataArray = function() { 1781 * var a = JXG.Math.Clip.greinerHormann(curve1, curve2, 'union', this.board); 1782 * this.dataX = a[0]; 1783 * this.dataY = a[1]; 1784 * }; 1785 * 1786 * board.update(); 1787 * 1788 * </pre><div id="JXG6075c918-4d57-4b72-b600-6597a6a4f44e" class="jxgbox" style="width: 300px; height: 300px;"></div> 1789 * <script type="text/javascript"> 1790 * (function() { 1791 * var board = JXG.JSXGraph.initBoard('JXG6075c918-4d57-4b72-b600-6597a6a4f44e', 1792 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1793 * var curve1 = board.create('curve', [ 1794 * [-3, 3, 0, -3], 1795 * [3, 3, 0, 3] 1796 * ], 1797 * {strokeColor: 'black', fillColor: 'none', fillOpacity: 0.8}); 1798 * 1799 * var curve2 = board.create('polygon', [[3, 4], [-4, 0], [-4, 4]], 1800 * {strokeColor: 'blue', fillColor: 'none'}); 1801 * 1802 * 1803 * var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.6}); 1804 * clip_path.updateDataArray = function() { 1805 * var a = JXG.Math.Clip.greinerHormann(curve1, curve2, 'union', this.board); 1806 * this.dataX = a[0]; 1807 * this.dataY = a[1]; 1808 * }; 1809 * 1810 * board.update(); 1811 * 1812 * })(); 1813 * 1814 * </script><pre> 1815 * 1816 * @example 1817 * var curve1 = board.create('curve', [ 1818 * [-4, 4, 0, -4], 1819 * [4, 4, -2, 4] 1820 * ], 1821 * {strokeColor: 'black', fillColor: 'none', fillOpacity: 0.8}); 1822 * 1823 * var curve2 = board.create('circle', [[0, 0], [0, -2]], 1824 * {strokeColor: 'blue', strokeWidth: 1, fillColor: 'red', fixed: true, fillOpacity: 0.3, 1825 * center: {visible: true, size: 5}, point2: {size: 5}}); 1826 * 1827 * var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.6}); 1828 * clip_path.updateDataArray = function() { 1829 * var a = JXG.Math.Clip.greinerHormann(curve1, curve2, 'difference', this.board); 1830 * 1831 * this.dataX = a[0]; 1832 * this.dataY = a[1]; 1833 * }; 1834 * 1835 * board.update(); 1836 * 1837 * </pre><div id="JXG46b3316b-5ab9-4928-9473-ccb476ca4185" class="jxgbox" style="width: 300px; height: 300px;"></div> 1838 * <script type="text/javascript"> 1839 * (function() { 1840 * var board = JXG.JSXGraph.initBoard('JXG46b3316b-5ab9-4928-9473-ccb476ca4185', 1841 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1842 * var curve1 = board.create('curve', [ 1843 * [-4, 4, 0, -4], 1844 * [4, 4, -2, 4] 1845 * ], 1846 * {strokeColor: 'black', fillColor: 'none', fillOpacity: 0.8}); 1847 * 1848 * var curve2 = board.create('circle', [[0, 0], [0, -2]], 1849 * {strokeColor: 'blue', strokeWidth: 1, fillColor: 'red', fixed: true, fillOpacity: 0.3, 1850 * center: {visible: true, size: 5}, point2: {size: 5}}); 1851 * 1852 * 1853 * var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.6}); 1854 * clip_path.updateDataArray = function() { 1855 * var a = JXG.Math.Clip.greinerHormann(curve1, curve2, 'difference', this.board); 1856 * 1857 * this.dataX = a[0]; 1858 * this.dataY = a[1]; 1859 * }; 1860 * 1861 * board.update(); 1862 * 1863 * })(); 1864 * 1865 * </script><pre> 1866 * 1867 * @example 1868 * var clip_path = board.create('curve', [[], []], {strokeWidth: 1, fillColor: 'yellow', fillOpacity: 0.6}); 1869 * clip_path.updateDataArray = function() { 1870 * var bbox = this.board.getBoundingBox(), 1871 * canvas, triangle; 1872 * 1873 * canvas = [[bbox[0], bbox[1]], // ul 1874 * [bbox[0], bbox[3]], // ll 1875 * [bbox[2], bbox[3]], // lr 1876 * [bbox[2], bbox[1]], // ur 1877 * [bbox[0], bbox[1]]] // ul 1878 * triangle = [[-1,1], [1,1], [0,-1], [-1,1]]; 1879 * 1880 * var a = JXG.Math.Clip.greinerHormann(canvas, triangle, 'difference', this.board); 1881 * this.dataX = a[0]; 1882 * this.dataY = a[1]; 1883 * }; 1884 * 1885 * </pre><div id="JXGe94da07a-2a01-4498-ad62-f71a327f8e25" class="jxgbox" style="width: 300px; height: 300px;"></div> 1886 * <script type="text/javascript"> 1887 * (function() { 1888 * var board = JXG.JSXGraph.initBoard('JXGe94da07a-2a01-4498-ad62-f71a327f8e25', 1889 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1890 * var clip_path = board.create('curve', [[], []], {strokeWidth: 1, fillColor: 'yellow', fillOpacity: 0.6}); 1891 * clip_path.updateDataArray = function() { 1892 * var bbox = this.board.getBoundingBox(), 1893 * canvas, triangle; 1894 * 1895 * canvas = [[bbox[0], bbox[1]], // ul 1896 * [bbox[0], bbox[3]], // ll 1897 * [bbox[2], bbox[3]], // lr 1898 * [bbox[2], bbox[1]], // ur 1899 * [bbox[0], bbox[1]]] // ul 1900 * triangle = [[-1,1], [1,1], [0,-1], [-1,1]]; 1901 * 1902 * var a = JXG.Math.Clip.greinerHormann(canvas, triangle, 'difference', this.board); 1903 * this.dataX = a[0]; 1904 * this.dataY = a[1]; 1905 * }; 1906 * 1907 * })(); 1908 * 1909 * </script><pre> 1910 * 1911 */ 1912 greinerHormann: function (subject, clip, clip_type, board) { 1913 //}, 1914 // subject_first_point_type, clip_first_point_type) { 1915 1916 var len, 1917 S = [], 1918 C = [], 1919 S_intersect = [], 1920 // C_intersect = [], 1921 S_starters, 1922 C_starters, 1923 res = [], 1924 DEBUG = false; 1925 1926 if (DEBUG) { 1927 console.log("\n------------ GREINER-HORMANN --------------"); 1928 } 1929 // Collect all subject points into subject array S 1930 S = this._getPath(subject, board); 1931 len = S.length; 1932 if ( 1933 len > 0 && 1934 Geometry.distance(S[0].coords.usrCoords, S[len - 1].coords.usrCoords, 3) < Mat.eps 1935 ) { 1936 S.pop(); 1937 } 1938 1939 // Collect all points into clip array C 1940 C = this._getPath(clip, board); 1941 len = C.length; 1942 if ( 1943 len > 0 && 1944 Geometry.distance(C[0].coords.usrCoords, C[len - 1].coords.usrCoords, 3) < 1945 Mat.eps * Mat.eps 1946 ) { 1947 C.pop(); 1948 } 1949 1950 // Handle cases where at least one of the paths is empty 1951 if (this.isEmptyCase(S, C, clip_type)) { 1952 return [[], []]; 1953 } 1954 1955 // Add pointers for doubly linked lists 1956 S_starters = this.makeDoublyLinkedList(S); 1957 C_starters = this.makeDoublyLinkedList(C); 1958 1959 if (DEBUG) { 1960 this._print_array(S); 1961 console.log("Components:", S_starters); 1962 this._print_array(C); 1963 console.log("Components:", C_starters); 1964 } 1965 1966 res = this.findIntersections(S, C, board); 1967 S_intersect = res[0]; 1968 1969 this._handleFullyDegenerateCase(S, C, board); 1970 1971 // Phase 2: mark intersection points as entry or exit points 1972 this.markEntryExit(S, C, S_starters); 1973 1974 // if (S[0].coords.distance(Const.COORDS_BY_USER, C[0].coords) === 0) { 1975 // // Randomly disturb the first point of the second path 1976 // // if both paths start at the same point. 1977 // C[0].usrCoords[1] *= 1 + Math.random() * 0.0001 - 0.00005; 1978 // C[0].usrCoords[2] *= 1 + Math.random() * 0.0001 - 0.00005; 1979 // } 1980 this.markEntryExit(C, S, C_starters); 1981 1982 // Handle cases without intersections 1983 if (this._countCrossingIntersections(S_intersect) === 0) { 1984 return this.handleEmptyIntersection(S, C, clip_type); 1985 } 1986 1987 // Phase 3: tracing 1988 return this.tracing(S, S_intersect, clip_type); 1989 }, 1990 1991 /** 1992 * Union of two closed paths. The paths could be JSXGraph elements circle, curve, or polygon. 1993 * Computed by the Greiner-Hormann algorithm. 1994 * 1995 * @param {JXG.Circle|JXG.Curve|JXG.Polygon} subject First closed path. 1996 * @param {JXG.Circle|JXG.Curve|JXG.Polygon} clip Second closed path. 1997 * @param {JXG.Board} board JSXGraph board object. It is needed to convert between 1998 * user coordinates and screen coordinates. 1999 * @return {Array} Array consisting of two arrays containing the x-coordinates and the y-coordinates of 2000 * the resulting path. 2001 * 2002 * @see JXG.Math.Clip.greinerHormann 2003 * @see JXG.Math.Clip.intersection 2004 * @see JXG.Math.Clip.difference 2005 * 2006 * @example 2007 * var curve1 = board.create('curve', [ 2008 * [-3, 3, 0, -3], 2009 * [3, 3, 0, 3] 2010 * ], 2011 * {strokeColor: 'black'}); 2012 * 2013 * var curve2 = board.create('polygon', [[3, 4], [-4, 0], [-4, 4]], 2014 * {strokeColor: 'blue', fillColor: 'none'}); 2015 * 2016 * var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.3}); 2017 * clip_path.updateDataArray = function() { 2018 * var a = JXG.Math.Clip.union(curve1, curve2, this.board); 2019 * this.dataX = a[0]; 2020 * this.dataY = a[1]; 2021 * }; 2022 * 2023 * board.update(); 2024 * 2025 * </pre><div id="JXG7c5204aa-3824-4464-819c-80df7bf1d917" class="jxgbox" style="width: 300px; height: 300px;"></div> 2026 * <script type="text/javascript"> 2027 * (function() { 2028 * var board = JXG.JSXGraph.initBoard('JXG7c5204aa-3824-4464-819c-80df7bf1d917', 2029 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 2030 * var curve1 = board.create('curve', [ 2031 * [-3, 3, 0, -3], 2032 * [3, 3, 0, 3] 2033 * ], 2034 * {strokeColor: 'black'}); 2035 * 2036 * var curve2 = board.create('polygon', [[3, 4], [-4, 0], [-4, 4]], 2037 * {strokeColor: 'blue', fillColor: 'none'}); 2038 * 2039 * 2040 * var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.3}); 2041 * clip_path.updateDataArray = function() { 2042 * var a = JXG.Math.Clip.union(curve1, curve2, this.board); 2043 * this.dataX = a[0]; 2044 * this.dataY = a[1]; 2045 * }; 2046 * 2047 * board.update(); 2048 * 2049 * })(); 2050 * 2051 * </script><pre> 2052 * 2053 */ 2054 union: function (path1, path2, board) { 2055 return this.greinerHormann(path1, path2, "union", board); 2056 }, 2057 2058 /** 2059 * Intersection of two closed paths. The paths could be JSXGraph elements circle, curve, or polygon. 2060 * Computed by the Greiner-Hormann algorithm. 2061 * 2062 * @param {JXG.Circle|JXG.Curve|JXG.Polygon} subject First closed path. 2063 * @param {JXG.Circle|JXG.Curve|JXG.Polygon} clip Second closed path. 2064 * @param {JXG.Board} board JSXGraph board object. It is needed to convert between 2065 * user coordinates and screen coordinates. 2066 * @return {Array} Array consisting of two arrays containing the x-coordinates and the y-coordinates of 2067 * the resulting path. 2068 * 2069 * @see JXG.Math.Clip.greinerHormann 2070 * @see JXG.Math.Clip.union 2071 * @see JXG.Math.Clip.difference 2072 * 2073 * @example 2074 * var p = []; 2075 * p.push(board.create('point', [0, -5])); 2076 * p.push(board.create('point', [-5, 0])); 2077 * p.push(board.create('point', [-3, 3])); 2078 * 2079 * var curve1 = board.create('ellipse', p, 2080 * {strokeColor: 'black'}); 2081 * 2082 * var curve2 = board.create('curve', [function(phi){return 4 * Math.cos(2*phi); }, 2083 * [0, 0], 2084 * 0, 2 * Math.PI], 2085 * {curveType:'polar', strokeColor: 'blue', strokewidth:1}); 2086 * 2087 * var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.3}); 2088 * clip_path.updateDataArray = function() { 2089 * var a = JXG.Math.Clip.intersection(curve2, curve1, this.board); 2090 * 2091 * this.dataX = a[0]; 2092 * this.dataY = a[1]; 2093 * }; 2094 * 2095 * board.update(); 2096 * 2097 * </pre><div id="JXG7ad547eb-7b6c-4a1a-a4d4-4ed298fc7998" class="jxgbox" style="width: 300px; height: 300px;"></div> 2098 * <script type="text/javascript"> 2099 * (function() { 2100 * var board = JXG.JSXGraph.initBoard('JXG7ad547eb-7b6c-4a1a-a4d4-4ed298fc7998', 2101 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 2102 * var p = []; 2103 * p.push(board.create('point', [0, -5])); 2104 * p.push(board.create('point', [-5, 0])); 2105 * p.push(board.create('point', [-3, 3])); 2106 * 2107 * var curve1 = board.create('ellipse', p, 2108 * {strokeColor: 'black'}); 2109 * 2110 * var curve2 = board.create('curve', [function(phi){return 4 * Math.cos(2*phi); }, 2111 * [0, 0], 2112 * 0, 2 * Math.PI], 2113 * {curveType:'polar', strokeColor: 'blue', strokewidth:1}); 2114 * 2115 * 2116 * var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.3}); 2117 * clip_path.updateDataArray = function() { 2118 * var a = JXG.Math.Clip.intersection(curve2, curve1, this.board); 2119 * 2120 * this.dataX = a[0]; 2121 * this.dataY = a[1]; 2122 * }; 2123 * 2124 * board.update(); 2125 * 2126 * })(); 2127 * 2128 * </script><pre> 2129 * 2130 * 2131 */ 2132 intersection: function (path1, path2, board) { 2133 return this.greinerHormann(path1, path2, "intersection", board); 2134 }, 2135 2136 /** 2137 * Difference of two closed paths, i.e. path1 minus path2. 2138 * The paths could be JSXGraph elements circle, curve, or polygon. 2139 * Computed by the Greiner-Hormann algorithm. 2140 * 2141 * @param {JXG.Circle|JXG.Curve|JXG.Polygon} subject First closed path. 2142 * @param {JXG.Circle|JXG.Curve|JXG.Polygon} clip Second closed path. 2143 * @param {JXG.Board} board JSXGraph board object. It is needed to convert between 2144 * user coordinates and screen coordinates. 2145 * @return {Array} Array consisting of two arrays containing the x-coordinates and the y-coordinates of 2146 * the resulting path. 2147 * 2148 * @see JXG.Math.Clip.greinerHormann 2149 * @see JXG.Math.Clip.intersection 2150 * @see JXG.Math.Clip.union 2151 * 2152 * @example 2153 * var curve1 = board.create('polygon', [[-4, 4], [4, 4], [0, -1]], 2154 * {strokeColor: 'blue', fillColor: 'none'}); 2155 * 2156 * var curve2 = board.create('curve', [ 2157 * [-1, 1, 0, -1], 2158 * [1, 1, 3, 1] 2159 * ], 2160 * {strokeColor: 'black', fillColor: 'none', fillOpacity: 0.8}); 2161 * 2162 * var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.3}); 2163 * clip_path.updateDataArray = function() { 2164 * var a = JXG.Math.Clip.difference(curve1, curve2, this.board); 2165 * this.dataX = a[0]; 2166 * this.dataY = a[1]; 2167 * }; 2168 * 2169 * board.update(); 2170 * 2171 * </pre><div id="JXGc5ce6bb3-146c-457f-a48b-6b9081fb68a3" class="jxgbox" style="width: 300px; height: 300px;"></div> 2172 * <script type="text/javascript"> 2173 * (function() { 2174 * var board = JXG.JSXGraph.initBoard('JXGc5ce6bb3-146c-457f-a48b-6b9081fb68a3', 2175 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 2176 * var curve1 = board.create('polygon', [[-4, 4], [4, 4], [0, -1]], 2177 * {strokeColor: 'blue', fillColor: 'none'}); 2178 * 2179 * var curve2 = board.create('curve', [ 2180 * [-1, 1, 0, -1], 2181 * [1, 1, 3, 1] 2182 * ], 2183 * {strokeColor: 'black', fillColor: 'none', fillOpacity: 0.8}); 2184 * 2185 * 2186 * var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.3}); 2187 * clip_path.updateDataArray = function() { 2188 * var a = JXG.Math.Clip.difference(curve1, curve2, this.board); 2189 * this.dataX = a[0]; 2190 * this.dataY = a[1]; 2191 * }; 2192 * 2193 * board.update(); 2194 * 2195 * })(); 2196 * 2197 * </script><pre> 2198 * 2199 */ 2200 difference: function (path1, path2, board) { 2201 return this.greinerHormann(path1, path2, "difference", board); 2202 } 2203 }; 2204 2205 // JXG.extend(Mat.Clip, /** @lends JXG.Math.Clip */ {}); 2206 2207 export default Mat.Clip; 2208