1 /* 2 Copyright 2008-2018 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/> 29 and <http://opensource.org/licenses/MIT/>. 30 */ 31 32 33 /*global JXG:true, define: true*/ 34 /*jslint nomen: true, plusplus: true*/ 35 36 /* depends: 37 jxg 38 base/constants 39 base/coords 40 math/statistics 41 utils/type 42 base/element 43 elements: 44 segment 45 transform 46 */ 47 48 define([ 49 'jxg', 'base/constants', 'base/coords', 'math/statistics', 'math/geometry', 'utils/type', 'base/element', 'base/line', 'base/transformation' 50 ], function (JXG, Const, Coords, Statistics, Geometry, Type, GeometryElement, Line, Transform) { 51 52 "use strict"; 53 54 /** 55 * Creates a new instance of JXG.Polygon. 56 * @class Polygon stores all style and functional properties that are required 57 * to draw and to interactact with a polygon. 58 * @param {JXG.Board} board Reference to the board the polygon is to be drawn on. 59 * @param {Array} vertices Unique identifiers for the points defining the polygon. 60 * Last point must be first point. Otherwise, the first point will be added at the list. 61 * @param {Object} attributes An object which contains properties as given in {@link JXG.Options.elements} 62 * and {@link JXG.Options.polygon}. 63 * @constructor 64 * @extends JXG.GeometryElement 65 */ 66 67 JXG.Polygon = function (board, vertices, attributes) { 68 this.constructor(board, attributes, Const.OBJECT_TYPE_POLYGON, Const.OBJECT_CLASS_AREA); 69 70 var i, l, len, j, 71 attr_line = Type.copyAttributes(attributes, board.options, 'polygon', 'borders'); 72 73 this.withLines = attributes.withlines; 74 this.attr_line = attr_line; 75 76 /** 77 * References to the points defining the polygon. The last vertex is the same as the first vertex. 78 * @type Array 79 */ 80 this.vertices = []; 81 for (i = 0; i < vertices.length; i++) { 82 this.vertices[i] = this.board.select(vertices[i]); 83 } 84 85 // Close the polygon 86 if (this.vertices.length > 0 && this.vertices[this.vertices.length - 1].id !== this.vertices[0].id) { 87 this.vertices.push(this.vertices[0]); 88 } 89 90 /** 91 * References to the border lines of the polygon. 92 * @type Array 93 */ 94 this.borders = []; 95 96 if (this.withLines) { 97 len = this.vertices.length - 1; 98 for (j = 0; j < len; j++) { 99 // This sets the "correct" labels for the first triangle of a construction. 100 i = (j + 1) % len; 101 attr_line.id = attr_line.ids && attr_line.ids[i]; 102 attr_line.name = attr_line.names && attr_line.names[i]; 103 attr_line.strokecolor = (Type.isArray(attr_line.colors) && attr_line.colors[i % attr_line.colors.length]) || 104 attr_line.strokecolor; 105 attr_line.visible = Type.exists(attributes.borders.visible) ? attributes.borders.visible : attributes.visible; 106 107 if (attr_line.strokecolor === false) { 108 attr_line.strokecolor = 'none'; 109 } 110 111 l = board.create('segment', [this.vertices[i], this.vertices[i + 1]], attr_line); 112 l.dump = false; 113 this.borders[i] = l; 114 l.parentPolygon = this; 115 } 116 } 117 this.inherits.push(this.vertices, this.borders); 118 119 // Register polygon at board 120 // This needs to be done BEFORE the points get this polygon added in their descendants list 121 this.id = this.board.setId(this, 'Py'); 122 123 // Add polygon as child to defining points 124 for (i = 0; i < this.vertices.length - 1; i++) { 125 this.board.select(this.vertices[i]).addChild(this); 126 } 127 128 this.board.renderer.drawPolygon(this); 129 this.board.finalizeAdding(this); 130 131 this.createGradient(); 132 this.elType = 'polygon'; 133 134 // create label 135 this.createLabel(); 136 137 this.methodMap = JXG.deepCopy(this.methodMap, { 138 borders: 'borders', 139 vertices: 'vertices', 140 A: 'Area', 141 Area: 'Area', 142 Perimeter: 'Perimeter', 143 L: 'Perimeter', 144 Length: 'Perimeter', 145 boundingBox: 'boundingBox', 146 bounds: 'bounds', 147 addPoints: 'addPoints', 148 insertPoints: 'insertPoints', 149 removePoints: 'removePoints' 150 }); 151 }; 152 153 JXG.Polygon.prototype = new GeometryElement(); 154 155 JXG.extend(JXG.Polygon.prototype, /** @lends JXG.Polygon.prototype */ { 156 /** 157 * Checks whether (x,y) is near the polygon. 158 * @param {Number} x Coordinate in x direction, screen coordinates. 159 * @param {Number} y Coordinate in y direction, screen coordinates. 160 * @returns {Boolean} Returns true, if (x,y) is inside or at the boundary the polygon, otherwise false. 161 */ 162 hasPoint: function (x, y) { 163 164 var i, j, len, c = false; 165 166 if (Type.evaluate(this.visProp.hasinnerpoints)) { 167 // All points of the polygon trigger hasPoint: inner and boundary points 168 len = this.vertices.length; 169 // See http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html 170 // for a reference of Jordan method 171 for (i = 0, j = len - 2; i < len - 1; j = i++) { 172 if (((this.vertices[i].coords.scrCoords[2] > y) !== (this.vertices[j].coords.scrCoords[2] > y)) && 173 (x < (this.vertices[j].coords.scrCoords[1] - this.vertices[i].coords.scrCoords[1]) * (y - this.vertices[i].coords.scrCoords[2]) / 174 (this.vertices[j].coords.scrCoords[2] - this.vertices[i].coords.scrCoords[2]) + this.vertices[i].coords.scrCoords[1])) { 175 c = !c; 176 } 177 } 178 if (c) { 179 return true; 180 } 181 } 182 183 // Only boundary points trigger hasPoint 184 // We additionally test the boundary also in case hasInnerPoints. 185 // Since even if the above test has failed, the strokewidth may be large and (x, y) may 186 // be inside of hasPoint() of a vertices. 187 len = this.borders.length; 188 for (i = 0; i < len; i++) { 189 if (this.borders[i].hasPoint(x, y)) { 190 c = true; 191 break; 192 } 193 } 194 195 return c; 196 }, 197 198 /** 199 * Uses the boards renderer to update the polygon. 200 */ 201 updateRenderer: function () { 202 var i, len; // wasReal, 203 204 205 if (!this.needsUpdate) { 206 return this; 207 } 208 209 if (this.visPropCalc.visible) { 210 // wasReal = this.isReal; 211 212 len = this.vertices.length; 213 this.isReal = true; 214 for (i = 0; i < len; ++i) { 215 if (!this.vertices[i].isReal) { 216 this.isReal = false; 217 break; 218 } 219 } 220 221 if (//wasReal && 222 !this.isReal) { 223 this.updateVisibility(false); 224 } 225 } 226 227 if (this.visPropCalc.visible) { 228 this.board.renderer.updatePolygon(this); 229 } 230 231 /* Update the label if visible. */ 232 if (this.hasLabel && this.visPropCalc.visible && this.label && 233 this.label.visPropCalc.visible && this.isReal) { 234 235 this.label.update(); 236 this.board.renderer.updateText(this.label); 237 } 238 239 // Update rendNode display 240 this.setDisplayRendNode(); 241 // if (this.visPropCalc.visible !== this.visPropOld.visible) { 242 // this.board.renderer.display(this, this.visPropCalc.visible); 243 // this.visPropOld.visible = this.visPropCalc.visible; 244 // 245 // if (this.hasLabel) { 246 // this.board.renderer.display(this.label, this.label.visPropCalc.visible); 247 // } 248 // } 249 250 this.needsUpdate = false; 251 return this; 252 }, 253 254 /** 255 * return TextAnchor 256 */ 257 getTextAnchor: function () { 258 var a, b, x, y, i; 259 260 if (this.vertices.length === 0) { 261 return new Coords(Const.COORDS_BY_USER, [1, 0, 0], this.board); 262 } 263 264 a = this.vertices[0].X(); 265 b = this.vertices[0].Y(); 266 x = a; 267 y = b; 268 for (i = 0; i < this.vertices.length; i++) { 269 if (this.vertices[i].X() < a) { 270 a = this.vertices[i].X(); 271 } 272 273 if (this.vertices[i].X() > x) { 274 x = this.vertices[i].X(); 275 } 276 277 if (this.vertices[i].Y() > b) { 278 b = this.vertices[i].Y(); 279 } 280 281 if (this.vertices[i].Y() < y) { 282 y = this.vertices[i].Y(); 283 } 284 } 285 286 return new Coords(Const.COORDS_BY_USER, [(a + x) * 0.5, (b + y) * 0.5], this.board); 287 }, 288 289 getLabelAnchor: JXG.shortcut(JXG.Polygon.prototype, 'getTextAnchor'), 290 291 // documented in geometry element 292 cloneToBackground: function () { 293 var copy = {}, er; 294 295 copy.id = this.id + 'T' + this.numTraces; 296 this.numTraces++; 297 copy.vertices = this.vertices; 298 copy.visProp = Type.deepCopy(this.visProp, this.visProp.traceattributes, true); 299 copy.visProp.layer = this.board.options.layer.trace; 300 copy.board = this.board; 301 Type.clearVisPropOld(copy); 302 303 er = this.board.renderer.enhancedRendering; 304 this.board.renderer.enhancedRendering = true; 305 this.board.renderer.drawPolygon(copy); 306 this.board.renderer.enhancedRendering = er; 307 this.traces[copy.id] = copy.rendNode; 308 309 return this; 310 }, 311 312 /** 313 * Hide the polygon including its border lines. It will still exist but not visible on the board. 314 * @param {Boolean} [borderless=false] If set to true, the polygon is treated as a polygon without 315 * borders, i.e. the borders will not be hidden. 316 */ 317 hideElement: function (borderless) { 318 var i; 319 320 JXG.deprecated('Element.hideElement()', 'Element.setDisplayRendNode()'); 321 322 this.visPropCalc.visible = false; 323 this.board.renderer.display(this, false); 324 325 if (!borderless) { 326 for (i = 0; i < this.borders.length; i++) { 327 this.borders[i].hideElement(); 328 } 329 } 330 331 if (this.hasLabel && Type.exists(this.label)) { 332 this.label.hiddenByParent = true; 333 if (this.label.visPropCalc.visible) { 334 this.label.hideElement(); 335 } 336 } 337 }, 338 339 /** 340 * Make the element visible. 341 * @param {Boolean} [borderless=false] If set to true, the polygon is treated as a polygon without 342 * borders, i.e. the borders will not be shown. 343 */ 344 showElement: function (borderless) { 345 var i; 346 347 JXG.deprecated('Element.showElement()', 'Element.setDisplayRendNode()'); 348 349 this.visPropCalc.visible = true; 350 this.board.renderer.display(this, true); 351 352 if (!borderless) { 353 for (i = 0; i < this.borders.length; i++) { 354 this.borders[i].showElement().updateRenderer(); 355 } 356 } 357 358 if (Type.exists(this.label) && this.hasLabel && this.label.hiddenByParent) { 359 this.label.hiddenByParent = false; 360 if (!this.label.visPropCalc.visible) { 361 this.label.showElement().updateRenderer(); 362 } 363 } 364 return this; 365 }, 366 367 /** 368 * Area of (not self-intersecting) polygon 369 * @returns {Number} Area of (not self-intersecting) polygon 370 */ 371 Area: function () { 372 return Math.abs(Geometry.signedPolygon(this.vertices, true)); 373 }, 374 375 /** 376 * Perimeter of polygon. 377 * @returns {Number} Perimeter of polygon in user units. 378 * 379 * @example 380 * var p = [[0.0, 2.0], [2.0, 1.0], [4.0, 6.0], [1.0, 3.0]]; 381 * 382 * var pol = board.create('polygon', p, {hasInnerPoints: true}); 383 * var t = board.create('text', [5, 5, function() { return pol.Perimeter(); }]); 384 * </pre><div class="jxgbox" id="b10b734d-89fc-4b9d-b4a7-e3f0c1c6bf77" style="width: 400px; height: 400px;"></div> 385 * <script type="text/javascript"> 386 * (function () { 387 * var board = JXG.JSXGraph.initBoard('b10b734d-89fc-4b9d-b4a7-e3f0c1c6bf77', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 388 * p = [[0.0, 2.0], [2.0, 1.0], [4.0, 6.0], [1.0, 4.0]], 389 * cc1 = board.create('polygon', p, {hasInnerPoints: true}), 390 * t = board.create('text', [5, 5, function() { return cc1.Perimeter(); }]); 391 * })(); 392 * </script><pre> 393 * 394 */ 395 Perimeter: function() { 396 var i, 397 len = this.vertices.length, 398 val = 0.0; 399 400 for (i = 1; i < len; ++i) { 401 val += this.vertices[i].Dist(this.vertices[i - 1]); 402 } 403 404 return val; 405 }, 406 407 /** 408 * Bounding box of a polygon. The bounding box is an array of four numbers: the first two numbers 409 * determine the upper left corner, the last two number determine the lower right corner of the bounding box. 410 * 411 * The width and height of a polygon can then determined like this: 412 * @example 413 * var box = polygon.boundingBox(); 414 * var width = box[2] - box[0]; 415 * var height = box[1] - box[3]; 416 * 417 * @returns {Array} Array containing four numbers: [minX, maxY, maxX, minY] 418 */ 419 boundingBox: function () { 420 var box = [0, 0, 0, 0], i, v, 421 le = this.vertices.length - 1; 422 423 if (le === 0) { 424 return box; 425 } 426 box[0] = this.vertices[0].X(); 427 box[2] = box[0]; 428 box[1] = this.vertices[0].Y(); 429 box[3] = box[1]; 430 431 for (i = 1; i < le; ++i) { 432 v = this.vertices[i].X(); 433 if (v < box[0]) { 434 box[0] = v; 435 } else if (v > box[2]) { 436 box[2] = v; 437 } 438 439 v = this.vertices[i].Y(); 440 if (v > box[1]) { 441 box[1] = v; 442 } else if (v < box[3]) { 443 box[3] = v; 444 } 445 } 446 447 return box; 448 }, 449 450 // already documented in GeometryElement 451 bounds: function () { 452 return this.boundingBox(); 453 }, 454 455 /** 456 * This method removes the SVG or VML nodes of the lines and the filled area from the renderer, to remove 457 * the object completely you should use {@link JXG.Board#removeObject}. 458 */ 459 remove: function () { 460 var i; 461 462 for (i = 0; i < this.borders.length; i++) { 463 this.board.removeObject(this.borders[i]); 464 } 465 466 GeometryElement.prototype.remove.call(this); 467 }, 468 469 /** 470 * Finds the index to a given point reference. 471 * @param {JXG.Point} p Reference to an element of type {@link JXG.Point} 472 */ 473 findPoint: function (p) { 474 var i; 475 476 if (!Type.isPoint(p)) { 477 return -1; 478 } 479 480 for (i = 0; i < this.vertices.length; i++) { 481 if (this.vertices[i].id === p.id) { 482 return i; 483 } 484 } 485 486 return -1; 487 }, 488 489 /** 490 * Add more points to the polygon. The new points will be inserted at the end. 491 * @param {JXG.Point} p Arbitrary number of points 492 * @returns {JXG.Polygon} Reference to the polygon 493 */ 494 addPoints: function (p) { 495 var args = Array.prototype.slice.call(arguments); 496 497 return this.insertPoints.apply(this, [this.vertices.length - 2].concat(args)); 498 }, 499 500 /** 501 * Adds more points to the vertex list of the polygon, starting with index <tt><i</tt> 502 * @param {Number} idx The position where the new vertices are inserted, starting with 0. 503 * @param {JXG.Point} p Arbitrary number of points to insert. 504 * @returns {JXG.Polygon} Reference to the polygon object 505 */ 506 insertPoints: function (idx, p) { 507 var i, npoints = [], tmp; 508 509 if (arguments.length === 0) { 510 return this; 511 } 512 513 514 if (idx < 0 || idx > this.vertices.length - 2) { 515 return this; 516 } 517 518 for (i = 1; i < arguments.length; i++) { 519 if (Type.isPoint(arguments[i])) { 520 npoints.push(arguments[i]); 521 } 522 } 523 524 tmp = this.vertices.slice(0, idx + 1).concat(npoints); 525 this.vertices = tmp.concat(this.vertices.slice(idx + 1)); 526 527 if (this.withLines) { 528 tmp = this.borders.slice(0, idx); 529 this.board.removeObject(this.borders[idx]); 530 531 for (i = 0; i < npoints.length; i++) { 532 tmp.push(this.board.create('segment', [this.vertices[idx + i], this.vertices[idx + i + 1]], this.attr_line)); 533 } 534 535 tmp.push(this.board.create('segment', [this.vertices[idx + npoints.length], this.vertices[idx + npoints.length + 1]], this.attr_line)); 536 this.borders = tmp.concat(this.borders.slice(idx + 1)); 537 } 538 539 this.board.update(); 540 541 return this; 542 }, 543 544 /** 545 * Removes given set of vertices from the polygon 546 * @param {JXG.Point} p Arbitrary number of vertices as {@link JXG.Point} elements or index numbers 547 * @returns {JXG.Polygon} Reference to the polygon 548 */ 549 removePoints: function (p) { 550 var i, j, idx, nvertices = [], nborders = [], 551 nidx = [], partition = []; 552 553 // partition: 554 // in order to keep the borders which could be recycled, we have to partition 555 // the set of removed points. I.e. if the points 1, 2, 5, 6, 7, 10 are removed, 556 // the partitions are 557 // 1-2, 5-7, 10-10 558 // this gives us the borders, that can be removed and the borders we have to create. 559 560 561 // remove the last vertex which is identical to the first 562 this.vertices = this.vertices.slice(0, this.vertices.length - 1); 563 564 // collect all valid parameters as indices in nidx 565 for (i = 0; i < arguments.length; i++) { 566 idx = arguments[i]; 567 if (Type.isPoint(idx)) { 568 idx = this.findPoint(idx); 569 } 570 571 if (Type.isNumber(idx) && idx > -1 && idx < this.vertices.length && Type.indexOf(nidx, idx) === -1) { 572 nidx.push(idx); 573 } 574 } 575 576 // remove the polygon from each removed point's children 577 for (i = 0; i < nidx.length; i++) { 578 this.vertices[nidx[i]].removeChild(this); 579 } 580 581 // sort the elements to be eliminated 582 nidx = nidx.sort(); 583 nvertices = this.vertices.slice(); 584 nborders = this.borders.slice(); 585 586 // initialize the partition 587 if (this.withLines) { 588 partition.push([nidx[nidx.length - 1]]); 589 } 590 591 // run through all existing vertices and copy all remaining ones to nvertices 592 // compute the partition 593 for (i = nidx.length - 1; i > -1; i--) { 594 nvertices[nidx[i]] = -1; 595 596 if (this.withLines && (nidx[i] - 1 > nidx[i - 1])) { 597 partition[partition.length - 1][1] = nidx[i]; 598 partition.push([nidx[i - 1]]); 599 } 600 } 601 602 // finalize the partition computation 603 if (this.withLines) { 604 partition[partition.length - 1][1] = nidx[0]; 605 } 606 607 // update vertices 608 this.vertices = []; 609 for (i = 0; i < nvertices.length; i++) { 610 if (Type.isPoint(nvertices[i])) { 611 this.vertices.push(nvertices[i]); 612 } 613 } 614 if (this.vertices[this.vertices.length - 1].id !== this.vertices[0].id) { 615 this.vertices.push(this.vertices[0]); 616 } 617 618 // delete obsolete and create missing borders 619 if (this.withLines) { 620 for (i = 0; i < partition.length; i++) { 621 for (j = partition[i][1] - 1; j < partition[i][0] + 1; j++) { 622 // special cases 623 if (j < 0) { 624 // first vertex is removed, so the last border has to be removed, too 625 j = 0; 626 this.board.removeObject(this.borders[nborders.length - 1]); 627 nborders[nborders.length - 1] = -1; 628 } else if (j > nborders.length - 1) { 629 j = nborders.length - 1; 630 } 631 632 this.board.removeObject(this.borders[j]); 633 nborders[j] = -1; 634 } 635 636 // only create the new segment if it's not the closing border. the closing border is getting a special treatment at the end 637 // the if clause is newer than the min/max calls inside createSegment; i'm sure this makes the min/max calls obsolete, but 638 // just to be sure... 639 if (partition[i][1] !== 0 && partition[i][0] !== nvertices.length - 1) { 640 nborders[partition[i][0] - 1] = this.board.create('segment', [nvertices[Math.max(partition[i][1] - 1, 0)], nvertices[Math.min(partition[i][0] + 1, this.vertices.length - 1)]], this.attr_line); 641 } 642 } 643 644 this.borders = []; 645 for (i = 0; i < nborders.length; i++) { 646 if (nborders[i] !== -1) { 647 this.borders.push(nborders[i]); 648 } 649 } 650 651 // if the first and/or the last vertex is removed, the closing border is created at the end. 652 if (partition[0][1] === this.vertices.length - 1 || partition[partition.length - 1][1] === 0) { 653 this.borders.push(this.board.create('segment', [this.vertices[0], this.vertices[this.vertices.length - 2]], this.attr_line)); 654 } 655 } 656 657 this.board.update(); 658 659 return this; 660 }, 661 662 // documented in element.js 663 getParents: function () { 664 this.setParents(this.vertices); 665 return this.parents; 666 }, 667 668 getAttributes: function () { 669 var attr = GeometryElement.prototype.getAttributes.call(this), i; 670 671 if (this.withLines) { 672 attr.lines = attr.lines || {}; 673 attr.lines.ids = []; 674 attr.lines.colors = []; 675 676 for (i = 0; i < this.borders.length; i++) { 677 attr.lines.ids.push(this.borders[i].id); 678 attr.lines.colors.push(this.borders[i].visProp.strokecolor); 679 } 680 } 681 682 return attr; 683 }, 684 685 snapToGrid: function () { 686 var i, force; 687 688 if (Type.evaluate(this.visProp.snaptogrid)) { 689 force = true; 690 } else { 691 force = false; 692 } 693 694 for (i = 0; i < this.vertices.length; i++) { 695 this.vertices[i].handleSnapToGrid(force, true); 696 } 697 698 }, 699 700 /** 701 * Moves the polygon by the difference of two coordinates. 702 * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}. 703 * @param {Array} coords coordinates in screen/user units 704 * @param {Array} oldcoords previous coordinates in screen/user units 705 * @returns {JXG.Polygon} this element 706 */ 707 setPositionDirectly: function (method, coords, oldcoords) { 708 var dc, t, i, len, 709 c = new Coords(method, coords, this.board), 710 oldc = new Coords(method, oldcoords, this.board); 711 712 len = this.vertices.length - 1; 713 for (i = 0; i < len; i++) { 714 if (!this.vertices[i].draggable()) { 715 return this; 716 } 717 } 718 719 dc = Statistics.subtract(c.usrCoords, oldc.usrCoords); 720 t = this.board.create('transform', dc.slice(1), {type: 'translate'}); 721 t.applyOnce(this.vertices.slice(0, -1)); 722 723 return this; 724 }, 725 726 /** 727 * Algorithm by Sutherland and Hodgman to compute the intersection of two convex polygons. 728 * The polygon itself is the clipping polygon, it expects as parameter a polygon to be clipped. 729 * See <a href="https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm">wikipedia entry</a>. 730 * Called by {@link JXG.Polygon#intersect}. 731 * 732 * @private 733 * 734 * @param {JXG.Polygon} polygon Polygon which will be clipped. 735 * 736 * @returns {Array} of (normalized homogeneous user) coordinates (i.e. [z, x, y], where z==1 in most cases, 737 * representing the vertices of the intersection polygon. 738 * 739 */ 740 sutherlandHodgman: function(polygon) { 741 // First the two polygons are sorted counter clockwise 742 var clip = JXG.Math.Geometry.sortVertices(this.vertices), // "this" is the clipping polygon 743 subject = JXG.Math.Geometry.sortVertices(polygon.vertices), // "polygon" is the subject polygon 744 745 lenClip = clip.length - 1, 746 lenSubject = subject.length - 1, 747 lenIn, 748 749 outputList = [], 750 inputList, i, j, S, E, cross, 751 752 753 // Determines if the point c3 is right of the line through c1 and c2. 754 // Since the polygons are sorted counter clockwise, "right of" and therefore >= is needed here 755 isInside = function(c1, c2, c3) { 756 return ((c2[1] - c1[1]) * (c3[2] - c1[2]) - (c2[2] - c1[2]) * (c3[1] - c1[1])) >= 0; 757 }; 758 759 for (i = 0; i < lenSubject; i++) { 760 outputList.push(subject[i]); 761 } 762 763 for (i = 0; i < lenClip; i++) { 764 inputList = outputList.slice(0); 765 lenIn = inputList.length; 766 outputList = []; 767 768 S = inputList[lenIn - 1]; 769 770 for (j = 0; j < lenIn; j++) { 771 E = inputList[j]; 772 if (isInside(clip[i], clip[i + 1], E)) { 773 if (!isInside(clip[i], clip[i + 1], S)) { 774 cross = JXG.Math.Geometry.meetSegmentSegment(S, E, clip[i], clip[i + 1]); 775 cross[0][1] /= cross[0][0]; 776 cross[0][2] /= cross[0][0]; 777 cross[0][0] = 1; 778 outputList.push(cross[0]); 779 } 780 outputList.push(E); 781 } else if (isInside(clip[i], clip[i + 1], S)) { 782 cross = JXG.Math.Geometry.meetSegmentSegment(S, E, clip[i], clip[i + 1]); 783 cross[0][1] /= cross[0][0]; 784 cross[0][2] /= cross[0][0]; 785 cross[0][0] = 1; 786 outputList.push(cross[0]); 787 } 788 S = E; 789 } 790 } 791 792 return outputList; 793 }, 794 795 /** 796 * Generic method for the intersection of this polygon with another polygon. 797 * The parent object is the clipping polygon, it expects as parameter a polygon to be clipped. 798 * Both polygons have to be convex. 799 * Calls {@link JXG.Polygon#sutherlandHodgman}. 800 * 801 * @param {JXG.Polygon} polygon Polygon which will be clipped. 802 * 803 * @returns {Array} of (normalized homogeneous user) coordinates (i.e. [z, x, y], where z==1 in most cases, 804 * representing the vertices of the intersection polygon. 805 * 806 * @example 807 * // Static intersection of two polygons pol1 and pol2 808 * var pol1 = board.create('polygon', [[-2, 3], [-4, -3], [2, 0], [4, 4]], { 809 * name:'pol1', withLabel: true, 810 * fillColor: 'yellow' 811 * }); 812 * var pol2 = board.create('polygon', [[-2, -3], [-4, 1], [0, 4], [5, 1]], { 813 * name:'pol2', withLabel: true 814 * }); 815 * 816 * // Static version: 817 * // the intersection polygon does not adapt to changes of pol1 or pol2. 818 * var pol3 = board.create('polygon', pol1.intersect(pol2), {fillColor: 'blue'}); 819 * </pre><div class="jxgbox" id="d1fe5ea9-309f-494a-af07-ee3d033acb7c" style="width: 300px; height: 300px;"></div> 820 * <script type="text/javascript"> 821 * (function() { 822 * var board = JXG.JSXGraph.initBoard('d1fe5ea9-309f-494a-af07-ee3d033acb7c', {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 823 * // Intersect two polygons pol1 and pol2 824 * var pol1 = board.create('polygon', [[-2, 3], [-4, -3], [2, 0], [4, 4]], { 825 * name:'pol1', withLabel: true, 826 * fillColor: 'yellow' 827 * }); 828 * var pol2 = board.create('polygon', [[-2, -3], [-4, 1], [0, 4], [5, 1]], { 829 * name:'pol2', withLabel: true 830 * }); 831 * 832 * // Static version: the intersection polygon does not adapt to changes of pol1 or pol2. 833 * var pol3 = board.create('polygon', pol1.intersect(pol2), {fillColor: 'blue'}); 834 * })(); 835 * </script><pre> 836 * 837 * @example 838 * // Dynamic intersection of two polygons pol1 and pol2 839 * var pol1 = board.create('polygon', [[-2, 3], [-4, -3], [2, 0], [4, 4]], { 840 * name:'pol1', withLabel: true, 841 * fillColor: 'yellow' 842 * }); 843 * var pol2 = board.create('polygon', [[-2, -3], [-4, 1], [0, 4], [5, 1]], { 844 * name:'pol2', withLabel: true 845 * }); 846 * 847 * // Dynamic version: 848 * // the intersection polygon does adapt to changes of pol1 or pol2. 849 * // For this a curve element is used. 850 * var curve = board.create('curve', [[],[]], {fillColor: 'blue', fillOpacity: 0.4}); 851 * curve.updateDataArray = function() { 852 * var mat = JXG.Math.transpose(pol1.intersect(pol2)); 853 * 854 * if (mat.length == 3) { 855 * this.dataX = mat[1]; 856 * this.dataY = mat[2]; 857 * } else { 858 * this.dataX = []; 859 * this.dataY = []; 860 * } 861 * }; 862 * board.update(); 863 * </pre><div class="jxgbox" id="f870d516-ca1a-4140-8fe3-5d64fb42e5f2" style="width: 300px; height: 300px;"></div> 864 * <script type="text/javascript"> 865 * (function() { 866 * var board = JXG.JSXGraph.initBoard('f870d516-ca1a-4140-8fe3-5d64fb42e5f2', {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 867 * // Intersect two polygons pol1 and pol2 868 * var pol1 = board.create('polygon', [[-2, 3], [-4, -3], [2, 0], [4, 4]], { 869 * name:'pol1', withLabel: true, 870 * fillColor: 'yellow' 871 * }); 872 * var pol2 = board.create('polygon', [[-2, -3], [-4, 1], [0, 4], [5, 1]], { 873 * name:'pol2', withLabel: true 874 * }); 875 * 876 * // Dynamic version: 877 * // the intersection polygon does adapt to changes of pol1 or pol2. 878 * // For this a curve element is used. 879 * var curve = board.create('curve', [[],[]], {fillColor: 'blue', fillOpacity: 0.4}); 880 * curve.updateDataArray = function() { 881 * var mat = JXG.Math.transpose(pol1.intersect(pol2)); 882 * 883 * if (mat.length == 3) { 884 * this.dataX = mat[1]; 885 * this.dataY = mat[2]; 886 * } else { 887 * this.dataX = []; 888 * this.dataY = []; 889 * } 890 * }; 891 * board.update(); 892 * })(); 893 * </script><pre> 894 * 895 */ 896 intersect: function(polygon) { 897 return this.sutherlandHodgman(polygon); 898 } 899 900 901 }); 902 903 904 /** 905 * @class A polygon is an area enclosed by a set of border lines which are determined by 906 * <ul> 907 * <li> a list of points or 908 * <li> a list of coordinate arrays or 909 * <li> a function returning a list of coordinate arrays. 910 * </ul> 911 * Each two consecutive points of the list define a line. 912 * @pseudo 913 * @constructor 914 * @name Polygon 915 * @type Polygon 916 * @augments JXG.Polygon 917 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 918 * @param {Array} vertices The polygon's vertices. If the first and the last vertex don't match the first one will be 919 * added to the array by the creator. 920 * 921 * Additionally, a polygon can be created by providing a polygon and a transformation (or an array of transformations). 922 * The result is a polygon which is the transformation of the supplied polygon. 923 * 924 * @example 925 * var p1 = board.create('point', [0.0, 2.0]); 926 * var p2 = board.create('point', [2.0, 1.0]); 927 * var p3 = board.create('point', [4.0, 6.0]); 928 * var p4 = board.create('point', [1.0, 4.0]); 929 * 930 * var pol = board.create('polygon', [p1, p2, p3, p4]); 931 * </pre><div class="jxgbox" id="682069e9-9e2c-4f63-9b73-e26f8a2b2bb1" style="width: 400px; height: 400px;"></div> 932 * <script type="text/javascript"> 933 * (function () { 934 * var board = JXG.JSXGraph.initBoard('682069e9-9e2c-4f63-9b73-e26f8a2b2bb1', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 935 * p1 = board.create('point', [0.0, 2.0]), 936 * p2 = board.create('point', [2.0, 1.0]), 937 * p3 = board.create('point', [4.0, 6.0]), 938 * p4 = board.create('point', [1.0, 4.0]), 939 * cc1 = board.create('polygon', [p1, p2, p3, p4]); 940 * })(); 941 * </script><pre> 942 * 943 * @example 944 * var p = [[0.0, 2.0], [2.0, 1.0], [4.0, 6.0], [1.0, 3.0]]; 945 * 946 * var pol = board.create('polygon', p, {hasInnerPoints: true}); 947 * </pre><div class="jxgbox" id="9f9a5946-112a-4768-99ca-f30792bcdefb" style="width: 400px; height: 400px;"></div> 948 * <script type="text/javascript"> 949 * (function () { 950 * var board = JXG.JSXGraph.initBoard('9f9a5946-112a-4768-99ca-f30792bcdefb', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 951 * p = [[0.0, 2.0], [2.0, 1.0], [4.0, 6.0], [1.0, 4.0]], 952 * cc1 = board.create('polygon', p, {hasInnerPoints: true}); 953 * })(); 954 * </script><pre> 955 * 956 * @example 957 * var f1 = function() { return [0.0, 2.0]; }, 958 * f2 = function() { return [2.0, 1.0]; }, 959 * f3 = function() { return [4.0, 6.0]; }, 960 * f4 = function() { return [1.0, 4.0]; }, 961 * cc1 = board.create('polygon', [f1, f2, f3, f4]); 962 * 963 * </pre><div class="jxgbox" id="ceb09915-b783-44db-adff-7877ae3534c8" style="width: 400px; height: 400px;"></div> 964 * <script type="text/javascript"> 965 * (function () { 966 * var board = JXG.JSXGraph.initBoard('ceb09915-b783-44db-adff-7877ae3534c8', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 967 * f1 = function() { return [0.0, 2.0]; }, 968 * f2 = function() { return [2.0, 1.0]; }, 969 * f3 = function() { return [4.0, 6.0]; }, 970 * f4 = function() { return [1.0, 4.0]; }, 971 * cc1 = board.create('polygon', [f1, f2, f3, f4]); 972 * })(); 973 * </script><pre> 974 */ 975 JXG.createPolygon = function (board, parents, attributes) { 976 var el, i, le, obj, 977 points = [], 978 attr, p; 979 980 obj = board.select(parents[0]); 981 if (Type.isObject(obj) && obj.type === Const.OBJECT_TYPE_POLYGON && 982 Type.isTransformationOrArray(parents[1])) { 983 984 le = obj.vertices.length - 1; 985 attr = Type.copyAttributes(attributes, board.options, 'polygon', 'vertices'); 986 for (i = 0; i < le; i++) { 987 points.push(board.create('point', [obj.vertices[i], parents[1]], attr)); 988 } 989 990 } else { 991 points = Type.providePoints(board, parents, attributes, 'polygon', ['vertices']); 992 if (points === false) { 993 throw new Error("JSXGraph: Can't create polygon with parent types other than 'point' and 'coordinate arrays' or a function returning an array of coordinates. Alternatively, a polygon and a transformation can be supplied"); 994 } 995 } 996 997 attr = Type.copyAttributes(attributes, board.options, 'polygon'); 998 el = new JXG.Polygon(board, points, attr); 999 el.isDraggable = true; 1000 1001 return el; 1002 }; 1003 1004 /** 1005 * @class Constructs a regular polygon. It needs two points which define the base line and the number of vertices. 1006 * @pseudo 1007 * @description Constructs a regular polygon. It needs two points which define the base line and the number of vertices, or a set of points. 1008 * @constructor 1009 * @name RegularPolygon 1010 * @type Polygon 1011 * @augments Polygon 1012 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1013 * @param {JXG.Point_JXG.Point_Number} p1,p2,n The constructed regular polygon has n vertices and the base line defined by p1 and p2. 1014 * @example 1015 * var p1 = board.create('point', [0.0, 2.0]); 1016 * var p2 = board.create('point', [2.0, 1.0]); 1017 * 1018 * var pol = board.create('regularpolygon', [p1, p2, 5]); 1019 * </pre><div class="jxgbox" id="682069e9-9e2c-4f63-9b73-e26f8a2b2bb1" style="width: 400px; height: 400px;"></div> 1020 * <script type="text/javascript"> 1021 * (function () { 1022 * var board = JXG.JSXGraph.initBoard('682069e9-9e2c-4f63-9b73-e26f8a2b2bb1', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 1023 * p1 = board.create('point', [0.0, 2.0]), 1024 * p2 = board.create('point', [2.0, 1.0]), 1025 * cc1 = board.create('regularpolygon', [p1, p2, 5]); 1026 * })(); 1027 * </script><pre> 1028 * @example 1029 * var p1 = board.create('point', [0.0, 2.0]); 1030 * var p2 = board.create('point', [4.0,4.0]); 1031 * var p3 = board.create('point', [2.0,0.0]); 1032 * 1033 * var pol = board.create('regularpolygon', [p1, p2, p3]); 1034 * </pre><div class="jxgbox" id="096a78b3-bd50-4bac-b958-3be5e7df17ed" style="width: 400px; height: 400px;"></div> 1035 * <script type="text/javascript"> 1036 * (function () { 1037 * var board = JXG.JSXGraph.initBoard('096a78b3-bd50-4bac-b958-3be5e7df17ed', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 1038 * p1 = board.create('point', [0.0, 2.0]), 1039 * p2 = board.create('point', [4.0, 4.0]), 1040 * p3 = board.create('point', [2.0,0.0]), 1041 * cc1 = board.create('regularpolygon', [p1, p2, p3]); 1042 * })(); 1043 * </script><pre> 1044 * 1045 * @example 1046 * // Line of reflection 1047 * var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'}); 1048 * var reflect = board.create('transform', [li], {type: 'reflect'}); 1049 * var pol1 = board.create('polygon', [[-3,-2], [-1,-4], [-2,-0.5]]); 1050 * var pol2 = board.create('polygon', [pol1, reflect]); 1051 * 1052 * </pre><div id="58fc3078-d8d1-11e7-93b3-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 1053 * <script type="text/javascript"> 1054 * (function() { 1055 * var board = JXG.JSXGraph.initBoard('58fc3078-d8d1-11e7-93b3-901b0e1b8723', 1056 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1057 * var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'}); 1058 * var reflect = board.create('transform', [li], {type: 'reflect'}); 1059 * var pol1 = board.create('polygon', [[-3,-2], [-1,-4], [-2,-0.5]]); 1060 * var pol2 = board.create('polygon', [pol1, reflect]); 1061 * 1062 * })(); 1063 * 1064 * </script><pre> 1065 * 1066 */ 1067 JXG.createRegularPolygon = function (board, parents, attributes) { 1068 var el, i, n, 1069 p = [], rot, c, len, pointsExist, attr; 1070 1071 len = parents.length; 1072 n = parents[len - 1]; 1073 1074 if (Type.isNumber(n) && (parents.length !== 3 || n < 3)) { 1075 throw new Error("JSXGraph: A regular polygon needs two point types and a number > 2 as input."); 1076 } 1077 1078 if (Type.isNumber(board.select(n))) { // Regular polygon given by 2 points and a number 1079 len--; 1080 pointsExist = false; 1081 } else { // Regular polygon given by n points 1082 n = len; 1083 pointsExist = true; 1084 } 1085 1086 p = Type.providePoints(board, parents.slice(0, len), attributes, 'regularpolygon', ['vertices']); 1087 if (p === false) { 1088 throw new Error("JSXGraph: Can't create regular polygon with parent types other than 'point' and 'coordinate arrays' or a function returning an array of coordinates"); 1089 } 1090 1091 attr = Type.copyAttributes(attributes, board.options, 'regularpolygon', 'vertices'); 1092 for (i = 2; i < n; i++) { 1093 rot = board.create('transform', [Math.PI * (2 - (n - 2) / n), p[i - 1]], {type: 'rotate'}); 1094 if (pointsExist) { 1095 p[i].addTransform(p[i - 2], rot); 1096 p[i].fullUpdate(); 1097 } else { 1098 if (Type.isArray(attr.ids) && attr.ids.length >= n - 2) { 1099 attr.id = attr.ids[i - 2]; 1100 } 1101 p[i] = board.create('point', [p[i - 2], rot], attr); 1102 p[i].type = Const.OBJECT_TYPE_CAS; 1103 1104 // The next two lines of code are needed to make regular polgonmes draggable 1105 // The new helper points are set to be draggable. 1106 p[i].isDraggable = true; 1107 p[i].visProp.fixed = false; 1108 } 1109 } 1110 1111 attr = Type.copyAttributes(attributes, board.options, 'regularpolygon'); 1112 el = board.create('polygon', p, attr); 1113 el.elType = 'regularpolygon'; 1114 1115 return el; 1116 }; 1117 1118 JXG.registerElement('polygon', JXG.createPolygon); 1119 JXG.registerElement('regularpolygon', JXG.createRegularPolygon); 1120 1121 return { 1122 Polygon: JXG.Polygon, 1123 createPolygon: JXG.createPolygon, 1124 createRegularPolygon: JXG.createRegularPolygon 1125 }; 1126 }); 1127