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/math 41 math/numerics 42 utils/type 43 */ 44 45 /** 46 * @fileoverview This file contains the Math.Geometry namespace for calculating algebraic/geometric 47 * stuff like intersection points, angles, midpoint, and so on. 48 */ 49 50 define([ 51 'jxg', 'base/constants', 'base/coords', 'math/math', 'math/numerics', 'utils/type', 'utils/expect' 52 ], function (JXG, Const, Coords, Mat, Numerics, Type, Expect) { 53 54 "use strict"; 55 56 /** 57 * Math.Geometry namespace definition 58 * @name JXG.Math.Geometry 59 * @namespace 60 */ 61 Mat.Geometry = {}; 62 63 // the splitting is necessary due to the shortcut for the circumcircleMidpoint method to circumcenter. 64 65 JXG.extend(Mat.Geometry, /** @lends JXG.Math.Geometry */ { 66 /* ***************************************/ 67 /* *** GENERAL GEOMETRIC CALCULATIONS ****/ 68 /* ***************************************/ 69 70 /** 71 * Calculates the angle defined by the points A, B, C. 72 * @param {JXG.Point,Array} A A point or [x,y] array. 73 * @param {JXG.Point,Array} B Another point or [x,y] array. 74 * @param {JXG.Point,Array} C A circle - no, of course the third point or [x,y] array. 75 * @deprecated Use {@link JXG.Math.Geometry.rad} instead. 76 * @see #rad 77 * @see #trueAngle 78 * @returns {Number} The angle in radian measure. 79 */ 80 angle: function (A, B, C) { 81 var u, v, s, t, 82 a = [], 83 b = [], 84 c = []; 85 86 JXG.deprecated('Geometry.angle()', 'Geometry.rad()'); 87 if (A.coords) { 88 a[0] = A.coords.usrCoords[1]; 89 a[1] = A.coords.usrCoords[2]; 90 } else { 91 a[0] = A[0]; 92 a[1] = A[1]; 93 } 94 95 if (B.coords) { 96 b[0] = B.coords.usrCoords[1]; 97 b[1] = B.coords.usrCoords[2]; 98 } else { 99 b[0] = B[0]; 100 b[1] = B[1]; 101 } 102 103 if (C.coords) { 104 c[0] = C.coords.usrCoords[1]; 105 c[1] = C.coords.usrCoords[2]; 106 } else { 107 c[0] = C[0]; 108 c[1] = C[1]; 109 } 110 111 u = a[0] - b[0]; 112 v = a[1] - b[1]; 113 s = c[0] - b[0]; 114 t = c[1] - b[1]; 115 116 return Math.atan2(u * t - v * s, u * s + v * t); 117 }, 118 119 /** 120 * Calculates the angle defined by the three points A, B, C if you're going from A to C around B counterclockwise. 121 * @param {JXG.Point,Array} A Point or [x,y] array 122 * @param {JXG.Point,Array} B Point or [x,y] array 123 * @param {JXG.Point,Array} C Point or [x,y] array 124 * @see #rad 125 * @returns {Number} The angle in degrees. 126 */ 127 trueAngle: function (A, B, C) { 128 return this.rad(A, B, C) * 57.295779513082323; // *180.0/Math.PI; 129 }, 130 131 /** 132 * Calculates the internal angle defined by the three points A, B, C if you're going from A to C around B counterclockwise. 133 * @param {JXG.Point,Array} A Point or [x,y] array 134 * @param {JXG.Point,Array} B Point or [x,y] array 135 * @param {JXG.Point,Array} C Point or [x,y] array 136 * @see #trueAngle 137 * @returns {Number} Angle in radians. 138 */ 139 rad: function (A, B, C) { 140 var ax, ay, bx, by, cx, cy, phi; 141 142 if (A.coords) { 143 ax = A.coords.usrCoords[1]; 144 ay = A.coords.usrCoords[2]; 145 } else { 146 ax = A[0]; 147 ay = A[1]; 148 } 149 150 if (B.coords) { 151 bx = B.coords.usrCoords[1]; 152 by = B.coords.usrCoords[2]; 153 } else { 154 bx = B[0]; 155 by = B[1]; 156 } 157 158 if (C.coords) { 159 cx = C.coords.usrCoords[1]; 160 cy = C.coords.usrCoords[2]; 161 } else { 162 cx = C[0]; 163 cy = C[1]; 164 } 165 166 phi = Math.atan2(cy - by, cx - bx) - Math.atan2(ay - by, ax - bx); 167 168 if (phi < 0) { 169 phi += 6.2831853071795862; 170 } 171 172 return phi; 173 }, 174 175 /** 176 * Calculates a point on the bisection line between the three points A, B, C. 177 * As a result, the bisection line is defined by two points: 178 * Parameter B and the point with the coordinates calculated in this function. 179 * Does not work for ideal points. 180 * @param {JXG.Point} A Point 181 * @param {JXG.Point} B Point 182 * @param {JXG.Point} C Point 183 * @param [board=A.board] Reference to the board 184 * @returns {JXG.Coords} Coordinates of the second point defining the bisection. 185 */ 186 angleBisector: function (A, B, C, board) { 187 var phiA, phiC, phi, 188 Ac = A.coords.usrCoords, 189 Bc = B.coords.usrCoords, 190 Cc = C.coords.usrCoords, 191 x, y; 192 193 if (!Type.exists(board)) { 194 board = A.board; 195 } 196 197 // Parallel lines 198 if (Bc[0] === 0) { 199 return new Coords(Const.COORDS_BY_USER, 200 [1, (Ac[1] + Cc[1]) * 0.5, (Ac[2] + Cc[2]) * 0.5], board); 201 } 202 203 // Non-parallel lines 204 x = Ac[1] - Bc[1]; 205 y = Ac[2] - Bc[2]; 206 phiA = Math.atan2(y, x); 207 208 x = Cc[1] - Bc[1]; 209 y = Cc[2] - Bc[2]; 210 phiC = Math.atan2(y, x); 211 212 phi = (phiA + phiC) * 0.5; 213 214 if (phiA > phiC) { 215 phi += Math.PI; 216 } 217 218 x = Math.cos(phi) + Bc[1]; 219 y = Math.sin(phi) + Bc[2]; 220 221 return new Coords(Const.COORDS_BY_USER, [1, x, y], board); 222 }, 223 224 // /** 225 // * Calculates a point on the m-section line between the three points A, B, C. 226 // * As a result, the m-section line is defined by two points: 227 // * Parameter B and the point with the coordinates calculated in this function. 228 // * The m-section generalizes the bisector to any real number. 229 // * For example, the trisectors of an angle are simply the 1/3-sector and the 2/3-sector. 230 // * Does not work for ideal points. 231 // * @param {JXG.Point} A Point 232 // * @param {JXG.Point} B Point 233 // * @param {JXG.Point} C Point 234 // * @param {Number} m Number 235 // * @param [board=A.board] Reference to the board 236 // * @returns {JXG.Coords} Coordinates of the second point defining the bisection. 237 // */ 238 // angleMsector: function (A, B, C, m, board) { 239 // var phiA, phiC, phi, 240 // Ac = A.coords.usrCoords, 241 // Bc = B.coords.usrCoords, 242 // Cc = C.coords.usrCoords, 243 // x, y; 244 245 // if (!Type.exists(board)) { 246 // board = A.board; 247 // } 248 249 // // Parallel lines 250 // if (Bc[0] === 0) { 251 // return new Coords(Const.COORDS_BY_USER, 252 // [1, (Ac[1] + Cc[1]) * m, (Ac[2] + Cc[2]) * m], board); 253 // } 254 255 // // Non-parallel lines 256 // x = Ac[1] - Bc[1]; 257 // y = Ac[2] - Bc[2]; 258 // phiA = Math.atan2(y, x); 259 260 // x = Cc[1] - Bc[1]; 261 // y = Cc[2] - Bc[2]; 262 // phiC = Math.atan2(y, x); 263 264 // phi = phiA + ((phiC - phiA) * m); 265 266 // if (phiA - phiC > Math.PI) { 267 // phi += 2*m*Math.PI; 268 // } 269 270 // x = Math.cos(phi) + Bc[1]; 271 // y = Math.sin(phi) + Bc[2]; 272 273 // return new Coords(Const.COORDS_BY_USER, [1, x, y], board); 274 // }, 275 276 /** 277 * Reflects the point along the line. 278 * @param {JXG.Line} line Axis of reflection. 279 * @param {JXG.Point} point Point to reflect. 280 * @param [board=point.board] Reference to the board 281 * @returns {JXG.Coords} Coordinates of the reflected point. 282 */ 283 reflection: function (line, point, board) { 284 // (v,w) defines the slope of the line 285 var x0, y0, x1, y1, v, w, mu, 286 pc = point.coords.usrCoords, 287 p1c = line.point1.coords.usrCoords, 288 p2c = line.point2.coords.usrCoords; 289 290 if (!Type.exists(board)) { 291 board = point.board; 292 } 293 294 v = p2c[1] - p1c[1]; 295 w = p2c[2] - p1c[2]; 296 297 x0 = pc[1] - p1c[1]; 298 y0 = pc[2] - p1c[2]; 299 300 mu = (v * y0 - w * x0) / (v * v + w * w); 301 302 // point + mu*(-y,x) is the perpendicular foot 303 x1 = pc[1] + 2 * mu * w; 304 y1 = pc[2] - 2 * mu * v; 305 306 return new Coords(Const.COORDS_BY_USER, [x1, y1], board); 307 }, 308 309 /** 310 * Computes the new position of a point which is rotated 311 * around a second point (called rotpoint) by the angle phi. 312 * @param {JXG.Point} rotpoint Center of the rotation 313 * @param {JXG.Point} point point to be rotated 314 * @param {Number} phi rotation angle in arc length 315 * @param {JXG.Board} [board=point.board] Reference to the board 316 * @returns {JXG.Coords} Coordinates of the new position. 317 */ 318 rotation: function (rotpoint, point, phi, board) { 319 var x0, y0, c, s, x1, y1, 320 pc = point.coords.usrCoords, 321 rotpc = rotpoint.coords.usrCoords; 322 323 if (!Type.exists(board)) { 324 board = point.board; 325 } 326 327 x0 = pc[1] - rotpc[1]; 328 y0 = pc[2] - rotpc[2]; 329 330 c = Math.cos(phi); 331 s = Math.sin(phi); 332 333 x1 = x0 * c - y0 * s + rotpc[1]; 334 y1 = x0 * s + y0 * c + rotpc[2]; 335 336 return new Coords(Const.COORDS_BY_USER, [x1, y1], board); 337 }, 338 339 /** 340 * Calculates the coordinates of a point on the perpendicular to the given line through 341 * the given point. 342 * @param {JXG.Line} line A line. 343 * @param {JXG.Point} point Point which is projected to the line. 344 * @param {JXG.Board} [board=point.board] Reference to the board 345 * @returns {Array} Array of length two containing coordinates of a point on the perpendicular to the given line 346 * through the given point and boolean flag "change". 347 */ 348 perpendicular: function (line, point, board) { 349 var x, y, change, 350 c, z, 351 A = line.point1.coords.usrCoords, 352 B = line.point2.coords.usrCoords, 353 C = point.coords.usrCoords; 354 355 if (!Type.exists(board)) { 356 board = point.board; 357 } 358 359 // special case: point is the first point of the line 360 if (point === line.point1) { 361 x = A[1] + B[2] - A[2]; 362 y = A[2] - B[1] + A[1]; 363 z = A[0] * B[0]; 364 365 if (Math.abs(z) < Mat.eps) { 366 x = B[2]; 367 y = -B[1]; 368 } 369 c = [z, x, y]; 370 change = true; 371 372 // special case: point is the second point of the line 373 } else if (point === line.point2) { 374 x = B[1] + A[2] - B[2]; 375 y = B[2] - A[1] + B[1]; 376 z = A[0] * B[0]; 377 378 if (Math.abs(z) < Mat.eps) { 379 x = A[2]; 380 y = -A[1]; 381 } 382 c = [z, x, y]; 383 change = false; 384 385 // special case: point lies somewhere else on the line 386 } else if (Math.abs(Mat.innerProduct(C, line.stdform, 3)) < Mat.eps) { 387 x = C[1] + B[2] - C[2]; 388 y = C[2] - B[1] + C[1]; 389 z = B[0]; 390 391 if (Math.abs(z) < Mat.eps) { 392 x = B[2]; 393 y = -B[1]; 394 } 395 change = true; 396 397 if (Math.abs(z) > Mat.eps && Math.abs(x - C[1]) < Mat.eps && Math.abs(y - C[2]) < Mat.eps) { 398 x = C[1] + A[2] - C[2]; 399 y = C[2] - A[1] + C[1]; 400 change = false; 401 } 402 c = [z, x, y]; 403 404 // general case: point does not lie on the line 405 // -> calculate the foot of the dropped perpendicular 406 } else { 407 c = [0, line.stdform[1], line.stdform[2]]; 408 c = Mat.crossProduct(c, C); // perpendicuar to line 409 c = Mat.crossProduct(c, line.stdform); // intersection of line and perpendicular 410 change = true; 411 } 412 413 return [new Coords(Const.COORDS_BY_USER, c, board), change]; 414 }, 415 416 /** 417 * @deprecated Please use {@link JXG.Math.Geometry.circumcenter} instead. 418 */ 419 circumcenterMidpoint: function () { 420 JXG.deprecated('Geometry.circumcenterMidpoint()', 'Geometry.circumcenter()'); 421 this.circumcenter.apply(this, arguments); 422 }, 423 424 /** 425 * Calculates the center of the circumcircle of the three given points. 426 * @param {JXG.Point} point1 Point 427 * @param {JXG.Point} point2 Point 428 * @param {JXG.Point} point3 Point 429 * @param {JXG.Board} [board=point1.board] Reference to the board 430 * @returns {JXG.Coords} Coordinates of the center of the circumcircle of the given points. 431 */ 432 circumcenter: function (point1, point2, point3, board) { 433 var u, v, m1, m2, 434 A = point1.coords.usrCoords, 435 B = point2.coords.usrCoords, 436 C = point3.coords.usrCoords; 437 438 if (!Type.exists(board)) { 439 board = point1.board; 440 } 441 442 u = [B[0] - A[0], -B[2] + A[2], B[1] - A[1]]; 443 v = [(A[0] + B[0]) * 0.5, (A[1] + B[1]) * 0.5, (A[2] + B[2]) * 0.5]; 444 m1 = Mat.crossProduct(u, v); 445 446 u = [C[0] - B[0], -C[2] + B[2], C[1] - B[1]]; 447 v = [(B[0] + C[0]) * 0.5, (B[1] + C[1]) * 0.5, (B[2] + C[2]) * 0.5]; 448 m2 = Mat.crossProduct(u, v); 449 450 return new Coords(Const.COORDS_BY_USER, Mat.crossProduct(m1, m2), board); 451 }, 452 453 /** 454 * Calculates the euclidean norm for two given arrays of the same length. 455 * @param {Array} array1 Array of Number 456 * @param {Array} array2 Array of Number 457 * @param {Number} [n] Length of the arrays. Default is the minimum length of the given arrays. 458 * @returns {Number} Euclidean distance of the given vectors. 459 */ 460 distance: function (array1, array2, n) { 461 var i, 462 sum = 0; 463 464 if (!n) { 465 n = Math.min(array1.length, array2.length); 466 } 467 468 for (i = 0; i < n; i++) { 469 sum += (array1[i] - array2[i]) * (array1[i] - array2[i]); 470 } 471 472 return Math.sqrt(sum); 473 }, 474 475 /** 476 * Calculates euclidean distance for two given arrays of the same length. 477 * If one of the arrays contains a zero in the first coordinate, and the euclidean distance 478 * is different from zero it is a point at infinity and we return Infinity. 479 * @param {Array} array1 Array containing elements of type number. 480 * @param {Array} array2 Array containing elements of type number. 481 * @param {Number} [n] Length of the arrays. Default is the minimum length of the given arrays. 482 * @returns {Number} Euclidean (affine) distance of the given vectors. 483 */ 484 affineDistance: function (array1, array2, n) { 485 var d; 486 487 d = this.distance(array1, array2, n); 488 489 if (d > Mat.eps && (Math.abs(array1[0]) < Mat.eps || Math.abs(array2[0]) < Mat.eps)) { 490 return Infinity; 491 } 492 493 return d; 494 }, 495 496 /** 497 * Sort vertices counter clockwise starting with the point with the lowest y coordinate. 498 * 499 * @param {Array} p An array containing {@link JXG.Point}, {@link JXG.Coords}, and/or arrays. 500 * 501 * @returns {Array} 502 */ 503 sortVertices: function (p) { 504 var i, ll, 505 ps = Expect.each(p, Expect.coordsArray), 506 N = ps.length; 507 508 // find the point with the lowest y value 509 for (i = 1; i < N; i++) { 510 if ((ps[i][2] < ps[0][2]) || 511 // if the current and the lowest point have the same y value, pick the one with 512 // the lowest x value. 513 (Math.abs(ps[i][2] - ps[0][2]) < Mat.eps && ps[i][1] < ps[0][1])) { 514 ps = Type.swap(ps, i, 0); 515 } 516 } 517 518 // sort ps in increasing order of the angle the points and the ll make with the x-axis 519 ll = ps.shift(); 520 ps.sort(function (a, b) { 521 // atan is monotonically increasing, as we are only interested in the sign of the difference 522 // evaluating atan is not necessary 523 var rad1 = Math.atan2(a[2] - ll[2], a[1] - ll[1]), 524 rad2 = Math.atan2(b[2] - ll[2], b[1] - ll[1]); 525 526 return rad1 - rad2; 527 }); 528 529 // put ll back into the array 530 ps.unshift(ll); 531 532 // put the last element also in the beginning 533 ps.unshift(ps[ps.length - 1]); 534 535 return ps; 536 }, 537 538 /** 539 * Signed triangle area of the three points given. 540 * 541 * @param {JXG.Point|JXG.Coords|Array} p1 542 * @param {JXG.Point|JXG.Coords|Array} p2 543 * @param {JXG.Point|JXG.Coords|Array} p3 544 * 545 * @returns {Number} 546 */ 547 signedTriangle: function (p1, p2, p3) { 548 var A = Expect.coordsArray(p1), 549 B = Expect.coordsArray(p2), 550 C = Expect.coordsArray(p3); 551 552 return 0.5 * ((B[1] - A[1]) * (C[2] - A[2]) - (B[2] - A[2]) * (C[1] - A[1])); 553 }, 554 555 /** 556 * Determine the signed area of a non-intersecting polygon. 557 * Surveyor's Formula 558 * 559 * @param {Array} p An array containing {@link JXG.Point}, {@link JXG.Coords}, and/or arrays. 560 * @param {Boolean} [sort=true] 561 * 562 * @returns {Number} 563 */ 564 signedPolygon: function (p, sort) { 565 var i, N, 566 A = 0, 567 ps = Expect.each(p, Expect.coordsArray); 568 569 if (sort === undefined) { 570 sort = true; 571 } 572 573 if (!sort) { 574 ps = this.sortVertices(ps); 575 } else { 576 // make sure the polygon is closed. If it is already closed this won't change the sum because the last 577 // summand will be 0. 578 ps.unshift(ps[ps.length - 1]); 579 } 580 581 N = ps.length; 582 583 for (i = 1; i < N; i++) { 584 A += ps[i - 1][1] * ps[i][2] - ps[i][1] * ps[i - 1][2]; 585 } 586 587 return 0.5 * A; 588 }, 589 590 /** 591 * Calculate the complex hull of a point cloud. 592 * 593 * @param {Array} points An array containing {@link JXG.Point}, {@link JXG.Coords}, and/or arrays. 594 * 595 * @returns {Array} 596 */ 597 GrahamScan: function (points) { 598 var i, 599 M = 1, 600 ps = Expect.each(points, Expect.coordsArray), 601 N = ps.length; 602 603 ps = this.sortVertices(ps); 604 N = ps.length; 605 606 for (i = 2; i < N; i++) { 607 while (this.signedTriangle(ps[M - 1], ps[M], ps[i]) <= 0) { 608 if (M > 1) { 609 M -= 1; 610 } else if (i === N - 1) { 611 break; 612 } else { 613 i += 1; 614 } 615 } 616 617 M += 1; 618 ps = Type.swap(ps, M, i); 619 } 620 621 return ps.slice(0, M); 622 }, 623 624 /** 625 * A line can be a segment, a straight, or a ray. so it is not always delimited by point1 and point2 626 * calcStraight determines the visual start point and end point of the line. A segment is only drawn 627 * from start to end point, a straight line is drawn until it meets the boards boundaries. 628 * @param {JXG.Line} el Reference to a line object, that needs calculation of start and end point. 629 * @param {JXG.Coords} point1 Coordinates of the point where line drawing begins. This value is calculated and 630 * set by this method. 631 * @param {JXG.Coords} point2 Coordinates of the point where line drawing ends. This value is calculated and set 632 * by this method. 633 * @param {Number} margin Optional margin, to avoid the display of the small sides of lines. 634 * @returns null 635 * @see Line 636 * @see JXG.Line 637 */ 638 calcStraight: function (el, point1, point2, margin) { 639 var takePoint1, takePoint2, intersection, intersect1, intersect2, straightFirst, straightLast, 640 c, p1, p2; 641 642 if (!Type.exists(margin)) { 643 // Enlarge the drawable region slightly. This hides the small sides 644 // of thick lines in most cases. 645 margin = 10; 646 } 647 648 straightFirst = Type.evaluate(el.visProp.straightfirst); 649 straightLast = Type.evaluate(el.visProp.straightlast); 650 651 // If one of the point is an ideal point in homogeneous coordinates 652 // drawing of line segments or rays are not possible. 653 if (Math.abs(point1.scrCoords[0]) < Mat.eps) { 654 straightFirst = true; 655 } 656 if (Math.abs(point2.scrCoords[0]) < Mat.eps) { 657 straightLast = true; 658 } 659 660 // Do nothing in case of line segments (inside or outside of the board) 661 if (!straightFirst && !straightLast) { 662 return; 663 } 664 665 // Compute the stdform of the line in screen coordinates. 666 c = []; 667 c[0] = el.stdform[0] - 668 el.stdform[1] * el.board.origin.scrCoords[1] / el.board.unitX + 669 el.stdform[2] * el.board.origin.scrCoords[2] / el.board.unitY; 670 c[1] = el.stdform[1] / el.board.unitX; 671 c[2] = -el.stdform[2] / el.board.unitY; 672 673 // p1=p2 674 if (isNaN(c[0] + c[1] + c[2])) { 675 return; 676 } 677 678 takePoint1 = false; 679 takePoint2 = false; 680 681 // Line starts at point1 and point1 is inside the board 682 takePoint1 = !straightFirst && 683 Math.abs(point1.usrCoords[0]) >= Mat.eps && 684 point1.scrCoords[1] >= 0.0 && point1.scrCoords[1] <= el.board.canvasWidth && 685 point1.scrCoords[2] >= 0.0 && point1.scrCoords[2] <= el.board.canvasHeight; 686 687 // Line ends at point2 and point2 is inside the board 688 takePoint2 = !straightLast && 689 Math.abs(point2.usrCoords[0]) >= Mat.eps && 690 point2.scrCoords[1] >= 0.0 && point2.scrCoords[1] <= el.board.canvasWidth && 691 point2.scrCoords[2] >= 0.0 && point2.scrCoords[2] <= el.board.canvasHeight; 692 693 // Intersect the line with the four borders of the board. 694 intersection = this.meetLineBoard(c, el.board, margin); 695 intersect1 = intersection[0]; 696 intersect2 = intersection[1]; 697 698 /** 699 * At this point we have four points: 700 * point1 and point2 are the first and the second defining point on the line, 701 * intersect1, intersect2 are the intersections of the line with border around the board. 702 */ 703 704 /* 705 * Here we handle rays where both defining points are outside of the board. 706 */ 707 // If both points are outside and the complete ray is outside we do nothing 708 if (!takePoint1 && !takePoint2) { 709 // Ray starting at point 1 710 if (!straightFirst && straightLast && 711 !this.isSameDirection(point1, point2, intersect1) && !this.isSameDirection(point1, point2, intersect2)) { 712 return; 713 } 714 715 // Ray starting at point 2 716 if (straightFirst && !straightLast && 717 !this.isSameDirection(point2, point1, intersect1) && !this.isSameDirection(point2, point1, intersect2)) { 718 return; 719 } 720 } 721 722 /* 723 * If at least one of the defining points is outside of the board 724 * we take intersect1 or intersect2 as one of the end points 725 * The order is also important for arrows of axes 726 */ 727 if (!takePoint1) { 728 if (!takePoint2) { 729 // Two border intersection points are used 730 if (this.isSameDir(point1, point2, intersect1, intersect2)) { 731 p1 = intersect1; 732 p2 = intersect2; 733 } else { 734 p2 = intersect1; 735 p1 = intersect2; 736 } 737 } else { 738 // One border intersection points is used 739 if (this.isSameDir(point1, point2, intersect1, intersect2)) { 740 p1 = intersect1; 741 } else { 742 p1 = intersect2; 743 } 744 } 745 } else { 746 if (!takePoint2) { 747 // One border intersection points is used 748 if (this.isSameDir(point1, point2, intersect1, intersect2)) { 749 p2 = intersect2; 750 } else { 751 p2 = intersect1; 752 } 753 } 754 } 755 756 if (p1) { 757 //point1.setCoordinates(Const.COORDS_BY_USER, p1.usrCoords.slice(1)); 758 point1.setCoordinates(Const.COORDS_BY_USER, p1.usrCoords); 759 } 760 761 if (p2) { 762 //point2.setCoordinates(Const.COORDS_BY_USER, p2.usrCoords.slice(1)); 763 point2.setCoordinates(Const.COORDS_BY_USER, p2.usrCoords); 764 } 765 }, 766 767 /** 768 * A line can be a segment, a straight, or a ray. so it is not always delimited by point1 and point2. 769 * 770 * This method adjusts the line's delimiting points taking into account its nature, the viewport defined 771 * by the board. 772 * 773 * A segment is delimited by start and end point, a straight line or ray is delimited until it meets the 774 * boards boundaries. However, if the line has infinite ticks, it will be delimited by the projection of 775 * the boards vertices onto itself. 776 * 777 * @param {JXG.Line} el Reference to a line object, that needs calculation of start and end point. 778 * @param {JXG.Coords} point1 Coordinates of the point where line drawing begins. This value is calculated and 779 * set by this method. 780 * @param {JXG.Coords} point2 Coordinates of the point where line drawing ends. This value is calculated and set 781 * by this method. 782 * @see Line 783 * @see JXG.Line 784 */ 785 calcLineDelimitingPoints: function (el, point1, point2) { 786 var distP1P2, boundingBox, lineSlope, 787 intersect1, intersect2, straightFirst, straightLast, 788 c, p1, p2, 789 takePoint1 = false, 790 takePoint2 = false; 791 792 straightFirst = Type.evaluate(el.visProp.straightfirst); 793 straightLast = Type.evaluate(el.visProp.straightlast); 794 795 // If one of the point is an ideal point in homogeneous coordinates 796 // drawing of line segments or rays are not possible. 797 if (Math.abs(point1.scrCoords[0]) < Mat.eps) { 798 straightFirst = true; 799 } 800 if (Math.abs(point2.scrCoords[0]) < Mat.eps) { 801 straightLast = true; 802 } 803 804 // Compute the stdform of the line in screen coordinates. 805 c = []; 806 c[0] = el.stdform[0] - 807 el.stdform[1] * el.board.origin.scrCoords[1] / el.board.unitX + 808 el.stdform[2] * el.board.origin.scrCoords[2] / el.board.unitY; 809 c[1] = el.stdform[1] / el.board.unitX; 810 c[2] = -el.stdform[2] / el.board.unitY; 811 812 // p1=p2 813 if (isNaN(c[0] + c[1] + c[2])) { 814 return; 815 } 816 817 takePoint1 = !straightFirst; 818 takePoint2 = !straightLast; 819 // Intersect the board vertices on the line to establish the available visual space for the infinite ticks 820 // Based on the slope of the line we can optimise and only project the two outer vertices 821 822 // boundingBox = [x1, y1, x2, y2] upper left, lower right vertices 823 boundingBox = el.board.getBoundingBox(); 824 lineSlope = el.getSlope(); 825 if (lineSlope >= 0) { 826 // project vertices (x2,y1) (x1, y2) 827 intersect1 = this.projectPointToLine({ coords: { usrCoords: [1, boundingBox[2], boundingBox[1]] } }, el, el.board); 828 intersect2 = this.projectPointToLine({ coords: { usrCoords: [1, boundingBox[0], boundingBox[3]] } }, el, el.board); 829 } else { 830 // project vertices (x1, y1) (x2, y2) 831 intersect1 = this.projectPointToLine({ coords: { usrCoords: [1, boundingBox[0], boundingBox[1]] } }, el, el.board); 832 intersect2 = this.projectPointToLine({ coords: { usrCoords: [1, boundingBox[2], boundingBox[3]] } }, el, el.board); 833 } 834 835 /** 836 * we have four points: 837 * point1 and point2 are the first and the second defining point on the line, 838 * intersect1, intersect2 are the intersections of the line with border around the board. 839 */ 840 841 /* 842 * Here we handle rays/segments where both defining points are outside of the board. 843 */ 844 if (!takePoint1 && !takePoint2) { 845 // Segment, if segment does not cross the board, do nothing 846 if (!straightFirst && !straightLast) { 847 distP1P2 = point1.distance(Const.COORDS_BY_USER, point2); 848 // if intersect1 not between point1 and point2 849 if (Math.abs(point1.distance(Const.COORDS_BY_USER, intersect1) + 850 intersect1.distance(Const.COORDS_BY_USER, point2) - distP1P2) > Mat.eps) { 851 return; 852 } 853 // if insersect2 not between point1 and point2 854 if (Math.abs(point1.distance(Const.COORDS_BY_USER, intersect2) + 855 intersect2.distance(Const.COORDS_BY_USER, point2) - distP1P2) > Mat.eps) { 856 return; 857 } 858 } 859 860 // If both points are outside and the complete ray is outside we do nothing 861 // Ray starting at point 1 862 if (!straightFirst && straightLast && 863 !this.isSameDirection(point1, point2, intersect1) && !this.isSameDirection(point1, point2, intersect2)) { 864 return; 865 } 866 867 // Ray starting at point 2 868 if (straightFirst && !straightLast && 869 !this.isSameDirection(point2, point1, intersect1) && !this.isSameDirection(point2, point1, intersect2)) { 870 return; 871 } 872 } 873 874 /* 875 * If at least one of the defining points is outside of the board 876 * we take intersect1 or intersect2 as one of the end points 877 * The order is also important for arrows of axes 878 */ 879 if (!takePoint1) { 880 if (!takePoint2) { 881 // Two border intersection points are used 882 if (this.isSameDir(point1, point2, intersect1, intersect2)) { 883 p1 = intersect1; 884 p2 = intersect2; 885 } else { 886 p2 = intersect1; 887 p1 = intersect2; 888 } 889 } else { 890 // One border intersection points is used 891 if (this.isSameDir(point1, point2, intersect1, intersect2)) { 892 p1 = intersect1; 893 } else { 894 p1 = intersect2; 895 } 896 } 897 } else { 898 if (!takePoint2) { 899 // One border intersection points is used 900 if (this.isSameDir(point1, point2, intersect1, intersect2)) { 901 p2 = intersect2; 902 } else { 903 p2 = intersect1; 904 } 905 } 906 } 907 908 if (p1) { 909 //point1.setCoordinates(Const.COORDS_BY_USER, p1.usrCoords.slice(1)); 910 point1.setCoordinates(Const.COORDS_BY_USER, p1.usrCoords); 911 } 912 913 if (p2) { 914 //point2.setCoordinates(Const.COORDS_BY_USER, p2.usrCoords.slice(1)); 915 point2.setCoordinates(Const.COORDS_BY_USER, p2.usrCoords); 916 } 917 }, 918 919 /** 920 * Calculates the visProp.position corresponding to a given angle. 921 * @param {number} angle angle in radians. Must be in range (-2pi,2pi). 922 */ 923 calcLabelQuadrant: function(angle) { 924 var q; 925 if (angle < 0) { 926 angle += 2*Math.PI; 927 } 928 q = Math.floor((angle+Math.PI/8)/(Math.PI/4))%8; 929 return ['rt','urt','top','ulft','lft','llft','lrt'][q]; 930 }, 931 932 /** 933 * The vectors <tt>p2-p1</tt> and <tt>i2-i1</tt> are supposed to be collinear. If their cosine is positive 934 * they point into the same direction otherwise they point in opposite direction. 935 * @param {JXG.Coords} p1 936 * @param {JXG.Coords} p2 937 * @param {JXG.Coords} i1 938 * @param {JXG.Coords} i2 939 * @returns {Boolean} True, if <tt>p2-p1</tt> and <tt>i2-i1</tt> point into the same direction 940 */ 941 isSameDir: function (p1, p2, i1, i2) { 942 var dpx = p2.usrCoords[1] - p1.usrCoords[1], 943 dpy = p2.usrCoords[2] - p1.usrCoords[2], 944 dix = i2.usrCoords[1] - i1.usrCoords[1], 945 diy = i2.usrCoords[2] - i1.usrCoords[2]; 946 947 if (Math.abs(p2.usrCoords[0]) < Mat.eps) { 948 dpx = p2.usrCoords[1]; 949 dpy = p2.usrCoords[2]; 950 } 951 952 if (Math.abs(p1.usrCoords[0]) < Mat.eps) { 953 dpx = -p1.usrCoords[1]; 954 dpy = -p1.usrCoords[2]; 955 } 956 957 return dpx * dix + dpy * diy >= 0; 958 }, 959 960 /** 961 * If you're looking from point "start" towards point "s" and can see the point "p", true is returned. Otherwise false. 962 * @param {JXG.Coords} start The point you're standing on. 963 * @param {JXG.Coords} p The point in which direction you're looking. 964 * @param {JXG.Coords} s The point that should be visible. 965 * @returns {Boolean} True, if from start the point p is in the same direction as s is, that means s-start = k*(p-start) with k>=0. 966 */ 967 isSameDirection: function (start, p, s) { 968 var dx, dy, sx, sy, r = false; 969 970 dx = p.usrCoords[1] - start.usrCoords[1]; 971 dy = p.usrCoords[2] - start.usrCoords[2]; 972 973 sx = s.usrCoords[1] - start.usrCoords[1]; 974 sy = s.usrCoords[2] - start.usrCoords[2]; 975 976 if (Math.abs(dx) < Mat.eps) { 977 dx = 0; 978 } 979 980 if (Math.abs(dy) < Mat.eps) { 981 dy = 0; 982 } 983 984 if (Math.abs(sx) < Mat.eps) { 985 sx = 0; 986 } 987 988 if (Math.abs(sy) < Mat.eps) { 989 sy = 0; 990 } 991 992 if (dx >= 0 && sx >= 0) { 993 r = (dy >= 0 && sy >= 0) || (dy <= 0 && sy <= 0); 994 } else if (dx <= 0 && sx <= 0) { 995 r = (dy >= 0 && sy >= 0) || (dy <= 0 && sy <= 0); 996 } 997 998 return r; 999 }, 1000 1001 /****************************************/ 1002 /**** INTERSECTIONS ****/ 1003 /****************************************/ 1004 1005 /** 1006 * Generate the function which computes the coordinates of the intersection point. 1007 * Primarily used in {@link JXG.Point#createIntersectionPoint}. 1008 * @param {JXG.Board} board object 1009 * @param {JXG.Line,JXG.Circle_JXG.Line,JXG.Circle_Number} el1,el2,i The result will be a intersection point on el1 and el2. 1010 * i determines the intersection point if two points are available: <ul> 1011 * <li>i==0: use the positive square root,</li> 1012 * <li>i==1: use the negative square root.</li></ul> 1013 * See further {@link JXG.Point#createIntersectionPoint}. 1014 * @param {Boolean} alwaysintersect. Flag that determines if segements and arc can have an outer intersection point 1015 * on their defining line or circle. 1016 * @returns {Function} Function returning a {@link JXG.Coords} object that determines 1017 * the intersection point. 1018 */ 1019 intersectionFunction: function (board, el1, el2, i, j, alwaysintersect) { 1020 var func, that = this; 1021 1022 if (el1.elementClass === Const.OBJECT_CLASS_CURVE && 1023 el2.elementClass === Const.OBJECT_CLASS_CURVE) { 1024 // curve - curve 1025 /** @ignore */ 1026 func = function () { 1027 return that.meetCurveCurve(el1, el2, i, j, el1.board); 1028 }; 1029 1030 } else if ((el1.elementClass === Const.OBJECT_CLASS_CURVE && el2.elementClass === Const.OBJECT_CLASS_LINE) || 1031 (el2.elementClass === Const.OBJECT_CLASS_CURVE && el1.elementClass === Const.OBJECT_CLASS_LINE)) { 1032 // curve - line (this includes intersections between conic sections and lines 1033 /** @ignore */ 1034 func = function () { 1035 return that.meetCurveLine(el1, el2, i, el1.board, alwaysintersect); 1036 }; 1037 1038 } else if (el1.elementClass === Const.OBJECT_CLASS_LINE && el2.elementClass === Const.OBJECT_CLASS_LINE) { 1039 // line - line, lines may also be segments. 1040 /** @ignore */ 1041 func = function () { 1042 var res, c, 1043 first1, first2, last1, last2; 1044 1045 first1 = first2 = Type.evaluate(el1.visProp.straightfirst); 1046 last1 = last2 = Type.evaluate(el1.visProp.straightlast); 1047 1048 /** 1049 * If one of the lines is a segment or ray and 1050 * the the intersection point shpould disappear if outside 1051 * of the segment or ray we call 1052 * meetSegmentSegment 1053 */ 1054 if (!Type.evaluate(alwaysintersect) && (!first1 || !last1 || !first2 || !last2)) { 1055 res = that.meetSegmentSegment( 1056 el1.point1.coords.usrCoords, 1057 el1.point2.coords.usrCoords, 1058 el2.point1.coords.usrCoords, 1059 el2.point2.coords.usrCoords, 1060 el1.board 1061 ); 1062 1063 if ((!first1 && res[1] < 0) || (!last1 && res[1] > 1) || 1064 (!first2 && res[2] < 0) || (!last2 && res[2] > 1)) { 1065 // Non-existent 1066 c = [0, NaN, NaN]; 1067 } else { 1068 c = res[0]; 1069 } 1070 1071 return (new Coords(Const.COORDS_BY_USER, c, el1.board)); 1072 } 1073 1074 return that.meet(el1.stdform, el2.stdform, i, el1.board); 1075 }; 1076 } else { 1077 // All other combinations of circles and lines 1078 /** @ignore */ 1079 func = function () { 1080 return that.meet(el1.stdform, el2.stdform, i, el1.board); 1081 }; 1082 } 1083 1084 return func; 1085 }, 1086 1087 /** 1088 * Computes the intersection of a pair of lines, circles or both. 1089 * It uses the internal data array stdform of these elements. 1090 * @param {Array} el1 stdform of the first element (line or circle) 1091 * @param {Array} el2 stdform of the second element (line or circle) 1092 * @param {Number} i Index of the intersection point that should be returned. 1093 * @param board Reference to the board. 1094 * @returns {JXG.Coords} Coordinates of one of the possible two or more intersection points. 1095 * Which point will be returned is determined by i. 1096 */ 1097 meet: function (el1, el2, i, board) { 1098 var result, 1099 eps = Mat.eps; 1100 1101 // line line 1102 if (Math.abs(el1[3]) < eps && Math.abs(el2[3]) < eps) { 1103 result = this.meetLineLine(el1, el2, i, board); 1104 // circle line 1105 } else if (Math.abs(el1[3]) >= eps && Math.abs(el2[3]) < eps) { 1106 result = this.meetLineCircle(el2, el1, i, board); 1107 // line circle 1108 } else if (Math.abs(el1[3]) < eps && Math.abs(el2[3]) >= eps) { 1109 result = this.meetLineCircle(el1, el2, i, board); 1110 // circle circle 1111 } else { 1112 result = this.meetCircleCircle(el1, el2, i, board); 1113 } 1114 1115 return result; 1116 }, 1117 1118 /** 1119 * Intersection of the line with the board 1120 * @param {Array} line stdform of the line in screen coordinates 1121 * @param {JXG.Board} board reference to a board. 1122 * @param {Number} margin optional margin, to avoid the display of the small sides of lines. 1123 * @returns {Array} [intersection coords 1, intersection coords 2] 1124 */ 1125 meetLineBoard: function (line, board, margin) { 1126 // Intersect the line with the four borders of the board. 1127 var s = [], intersect1, intersect2, i, j; 1128 1129 if (!Type.exists(margin)) { 1130 margin = 0; 1131 } 1132 1133 // top 1134 s[0] = Mat.crossProduct(line, [margin, 0, 1]); 1135 // left 1136 s[1] = Mat.crossProduct(line, [margin, 1, 0]); 1137 // bottom 1138 s[2] = Mat.crossProduct(line, [-margin - board.canvasHeight, 0, 1]); 1139 // right 1140 s[3] = Mat.crossProduct(line, [-margin - board.canvasWidth, 1, 0]); 1141 1142 // Normalize the intersections 1143 for (i = 0; i < 4; i++) { 1144 if (Math.abs(s[i][0]) > Mat.eps) { 1145 for (j = 2; j > 0; j--) { 1146 s[i][j] /= s[i][0]; 1147 } 1148 s[i][0] = 1.0; 1149 } 1150 } 1151 1152 // line is parallel to "left", take "top" and "bottom" 1153 if (Math.abs(s[1][0]) < Mat.eps) { 1154 intersect1 = s[0]; // top 1155 intersect2 = s[2]; // bottom 1156 // line is parallel to "top", take "left" and "right" 1157 } else if (Math.abs(s[0][0]) < Mat.eps) { 1158 intersect1 = s[1]; // left 1159 intersect2 = s[3]; // right 1160 // left intersection out of board (above) 1161 } else if (s[1][2] < 0) { 1162 intersect1 = s[0]; // top 1163 1164 // right intersection out of board (below) 1165 if (s[3][2] > board.canvasHeight) { 1166 intersect2 = s[2]; // bottom 1167 } else { 1168 intersect2 = s[3]; // right 1169 } 1170 // left intersection out of board (below) 1171 } else if (s[1][2] > board.canvasHeight) { 1172 intersect1 = s[2]; // bottom 1173 1174 // right intersection out of board (above) 1175 if (s[3][2] < 0) { 1176 intersect2 = s[0]; // top 1177 } else { 1178 intersect2 = s[3]; // right 1179 } 1180 } else { 1181 intersect1 = s[1]; // left 1182 1183 // right intersection out of board (above) 1184 if (s[3][2] < 0) { 1185 intersect2 = s[0]; // top 1186 // right intersection out of board (below) 1187 } else if (s[3][2] > board.canvasHeight) { 1188 intersect2 = s[2]; // bottom 1189 } else { 1190 intersect2 = s[3]; // right 1191 } 1192 } 1193 1194 intersect1 = new Coords(Const.COORDS_BY_SCREEN, intersect1.slice(1), board); 1195 intersect2 = new Coords(Const.COORDS_BY_SCREEN, intersect2.slice(1), board); 1196 return [intersect1, intersect2]; 1197 }, 1198 1199 /** 1200 * Intersection of two lines. 1201 * @param {Array} l1 stdform of the first line 1202 * @param {Array} l2 stdform of the second line 1203 * @param {number} i unused 1204 * @param {JXG.Board} board Reference to the board. 1205 * @returns {JXG.Coords} Coordinates of the intersection point. 1206 */ 1207 meetLineLine: function (l1, l2, i, board) { 1208 /* 1209 var s = Mat.crossProduct(l1, l2); 1210 1211 if (Math.abs(s[0]) > Mat.eps) { 1212 s[1] /= s[0]; 1213 s[2] /= s[0]; 1214 s[0] = 1.0; 1215 } 1216 */ 1217 var s = isNaN(l1[5] + l2[5]) ? [0, 0, 0] : Mat.crossProduct(l1, l2); 1218 return new Coords(Const.COORDS_BY_USER, s, board); 1219 }, 1220 1221 /** 1222 * Intersection of line and circle. 1223 * @param {Array} lin stdform of the line 1224 * @param {Array} circ stdform of the circle 1225 * @param {number} i number of the returned intersection point. 1226 * i==0: use the positive square root, 1227 * i==1: use the negative square root. 1228 * @param {JXG.Board} board Reference to a board. 1229 * @returns {JXG.Coords} Coordinates of the intersection point 1230 */ 1231 meetLineCircle: function (lin, circ, i, board) { 1232 var a, b, c, d, n, 1233 A, B, C, k, t; 1234 1235 // Radius is zero, return center of circle 1236 if (circ[4] < Mat.eps) { 1237 if (Math.abs(Mat.innerProduct([1, circ[6], circ[7]], lin, 3)) < Mat.eps) { 1238 return new Coords(Const.COORDS_BY_USER, circ.slice(6, 8), board); 1239 } 1240 1241 return new Coords(Const.COORDS_BY_USER, [NaN, NaN], board); 1242 } 1243 1244 c = circ[0]; 1245 b = circ.slice(1, 3); 1246 a = circ[3]; 1247 d = lin[0]; 1248 n = lin.slice(1, 3); 1249 1250 // Line is assumed to be normalized. Therefore, nn==1 and we can skip some operations: 1251 /* 1252 var nn = n[0]*n[0]+n[1]*n[1]; 1253 A = a*nn; 1254 B = (b[0]*n[1]-b[1]*n[0])*nn; 1255 C = a*d*d - (b[0]*n[0]+b[1]*n[1])*d + c*nn; 1256 */ 1257 A = a; 1258 B = (b[0] * n[1] - b[1] * n[0]); 1259 C = a * d * d - (b[0] * n[0] + b[1] * n[1]) * d + c; 1260 1261 k = B * B - 4 * A * C; 1262 if (k > -Mat.eps * Mat.eps) { 1263 k = Math.sqrt(Math.abs(k)); 1264 t = [(-B + k) / (2 * A), (-B - k) / (2 * A)]; 1265 1266 return ((i === 0) ? 1267 new Coords(Const.COORDS_BY_USER, [-t[0] * (-n[1]) - d * n[0], -t[0] * n[0] - d * n[1]], board) : 1268 new Coords(Const.COORDS_BY_USER, [-t[1] * (-n[1]) - d * n[0], -t[1] * n[0] - d * n[1]], board) 1269 ); 1270 } 1271 1272 return new Coords(Const.COORDS_BY_USER, [0, 0, 0], board); 1273 }, 1274 1275 /** 1276 * Intersection of two circles. 1277 * @param {Array} circ1 stdform of the first circle 1278 * @param {Array} circ2 stdform of the second circle 1279 * @param {number} i number of the returned intersection point. 1280 * i==0: use the positive square root, 1281 * i==1: use the negative square root. 1282 * @param {JXG.Board} board Reference to the board. 1283 * @returns {JXG.Coords} Coordinates of the intersection point 1284 */ 1285 meetCircleCircle: function (circ1, circ2, i, board) { 1286 var radicalAxis; 1287 1288 // Radius is zero, return center of circle, if on other circle 1289 if (circ1[4] < Mat.eps) { 1290 if (Math.abs(this.distance(circ1.slice(6, 2), circ2.slice(6, 8)) - circ2[4]) < Mat.eps) { 1291 return new Coords(Const.COORDS_BY_USER, circ1.slice(6, 8), board); 1292 } 1293 1294 return new Coords(Const.COORDS_BY_USER, [0, 0, 0], board); 1295 } 1296 1297 // Radius is zero, return center of circle, if on other circle 1298 if (circ2[4] < Mat.eps) { 1299 if (Math.abs(this.distance(circ2.slice(6, 2), circ1.slice(6, 8)) - circ1[4]) < Mat.eps) { 1300 return new Coords(Const.COORDS_BY_USER, circ2.slice(6, 8), board); 1301 } 1302 1303 return new Coords(Const.COORDS_BY_USER, [0, 0, 0], board); 1304 } 1305 1306 radicalAxis = [circ2[3] * circ1[0] - circ1[3] * circ2[0], 1307 circ2[3] * circ1[1] - circ1[3] * circ2[1], 1308 circ2[3] * circ1[2] - circ1[3] * circ2[2], 1309 0, 1, Infinity, Infinity, Infinity]; 1310 radicalAxis = Mat.normalize(radicalAxis); 1311 1312 return this.meetLineCircle(radicalAxis, circ1, i, board); 1313 }, 1314 1315 /** 1316 * Compute an intersection of the curves c1 and c2. 1317 * We want to find values t1, t2 such that 1318 * c1(t1) = c2(t2), i.e. (c1_x(t1)-c2_x(t2),c1_y(t1)-c2_y(t2)) = (0,0). 1319 * 1320 * Methods: segment-wise intersections (default) or generalized Newton method. 1321 * @param {JXG.Curve} c1 Curve, Line or Circle 1322 * @param {JXG.Curve} c2 Curve, Line or Circle 1323 * @param {Number} nr the nr-th intersection point will be returned. 1324 * @param {Number} t2ini not longer used. 1325 * @param {JXG.Board} [board=c1.board] Reference to a board object. 1326 * @param {String} [method='segment'] Intersection method, possible values are 'newton' and 'segment'. 1327 * @returns {JXG.Coords} intersection point 1328 */ 1329 meetCurveCurve: function (c1, c2, nr, t2ini, board, method) { 1330 var co; 1331 1332 if (Type.exists(method) && method === 'newton') { 1333 co = Numerics.generalizedNewton(c1, c2, nr, t2ini); 1334 } else { 1335 if (c1.bezierDegree === 3 && c2.bezierDegree === 3) { 1336 co = this.meetBezierCurveRedBlueSegments(c1, c2, nr); 1337 } else { 1338 co = this.meetCurveRedBlueSegments(c1, c2, nr); 1339 } 1340 } 1341 1342 return (new Coords(Const.COORDS_BY_USER, co, board)); 1343 }, 1344 1345 /** 1346 * Intersection of curve with line, 1347 * Order of input does not matter for el1 and el2. 1348 * From version 0.99.7 on this method calls 1349 * {@link JXG.Math.Geometry.meetCurveLineDiscrete}. 1350 * If higher precision is needed, {@link JXG.Math.Geometry.meetCurveLineContinuous} 1351 * has to be used. 1352 * 1353 * @param {JXG.Curve,JXG.Line} el1 Curve or Line 1354 * @param {JXG.Curve,JXG.Line} el2 Curve or Line 1355 * @param {Number} nr the nr-th intersection point will be returned. 1356 * @param {JXG.Board} [board=el1.board] Reference to a board object. 1357 * @param {Boolean} alwaysIntersect If false just the segment between the two defining points are tested for intersection 1358 * @returns {JXG.Coords} Intersection point. In case no intersection point is detected, 1359 * the ideal point [0,1,0] is returned. 1360 */ 1361 meetCurveLine: function (el1, el2, nr, board, alwaysIntersect) { 1362 var v = [0, NaN, NaN], cu, li; 1363 1364 if (!Type.exists(board)) { 1365 board = el1.board; 1366 } 1367 1368 if (el1.elementClass === Const.OBJECT_CLASS_CURVE) { 1369 cu = el1; 1370 li = el2; 1371 } else { 1372 cu = el2; 1373 li = el1; 1374 } 1375 1376 // if (Type.evaluate(cu.visProp.curvetype) === 'plot') { 1377 v = this.meetCurveLineDiscrete(cu, li, nr, board, !alwaysIntersect); 1378 // } else { 1379 // v = this.meetCurveLineContinuous(cu, li, nr, board); 1380 // } 1381 1382 return v; 1383 }, 1384 1385 /** 1386 * Intersection of line and curve, continuous case. 1387 * Finds the nr-the intersection point 1388 * Uses {@link JXG.Math.Geometry.meetCurveLineDiscrete} as a first approximation. 1389 * A more exact solution is then found with {@link JXG.Math.Numerics.root}. 1390 * 1391 * @param {JXG.Curve} cu Curve 1392 * @param {JXG.Line} li Line 1393 * @param {Number} nr Will return the nr-th intersection point. 1394 * @param {JXG.Board} board 1395 * 1396 */ 1397 meetCurveLineContinuous: function (cu, li, nr, board, testSegment) { 1398 var t, func0, func1, func0a, v, x, y, z, 1399 eps = Mat.eps, 1400 epsLow = Mat.eps, 1401 steps, delta, tnew, i, 1402 tmin, fmin, ft; 1403 1404 v = this.meetCurveLineDiscrete(cu, li, nr, board, testSegment); 1405 x = v.usrCoords[1]; 1406 y = v.usrCoords[2]; 1407 1408 func0 = function (t) { 1409 var c1, c2; 1410 1411 if (t > cu.maxX() || t < cu.minX()) { 1412 return Infinity; 1413 } 1414 c1 = x - cu.X(t); 1415 c2 = y - cu.Y(t); 1416 return c1 * c1 + c2 * c2; 1417 }; 1418 1419 func1 = function (t) { 1420 var v = li.stdform[0] + li.stdform[1] * cu.X(t) + li.stdform[2] * cu.Y(t); 1421 return v * v; 1422 }; 1423 1424 // Find t 1425 steps = 50; 1426 delta = (cu.maxX() - cu.minX()) / steps; 1427 tnew = cu.minX(); 1428 1429 fmin = 0.0001; //eps; 1430 tmin = NaN; 1431 for (i = 0; i < steps; i++) { 1432 t = Numerics.root(func0, [Math.max(tnew, cu.minX()), Math.min(tnew + delta, cu.maxX())]); 1433 ft = Math.abs(func0(t)); 1434 if (ft <= fmin) { 1435 fmin = ft; 1436 tmin = t; 1437 if (fmin < eps) { 1438 break; 1439 } 1440 } 1441 1442 tnew += delta; 1443 } 1444 t = tmin; 1445 // Compute "exact" t 1446 t = Numerics.root(func1, [Math.max(t - delta, cu.minX()), Math.min(t + delta, cu.maxX())]); 1447 1448 ft = func1(t); 1449 // Is the point on the line? 1450 if (isNaN(ft) || Math.abs(ft) > epsLow) { 1451 z = 0.0; //NaN; 1452 } else { 1453 z = 1.0; 1454 } 1455 1456 return (new Coords(Const.COORDS_BY_USER, [z, cu.X(t), cu.Y(t)], board)); 1457 }, 1458 1459 /** 1460 * Intersection of line and curve, continuous case. 1461 * Segments are treated as lines. Finding the nr-the intersection point 1462 * works for nr=0,1 only. 1463 * 1464 * @private 1465 * @deprecated 1466 * @param {JXG.Curve} cu Curve 1467 * @param {JXG.Line} li Line 1468 * @param {Number} nr Will return the nr-th intersection point. 1469 * @param {JXG.Board} board 1470 * 1471 * BUG: does not respect cu.minX() and cu.maxX() 1472 */ 1473 meetCurveLineContinuousOld: function (cu, li, nr, board) { 1474 var t, t2, i, func, z, 1475 tnew, steps, delta, tstart, tend, cux, cuy, 1476 eps = Mat.eps * 10; 1477 1478 JXG.deprecated('Geometry.meetCurveLineContinuousOld()', 'Geometry.meetCurveLineContinuous()'); 1479 func = function (t) { 1480 var v = li.stdform[0] + li.stdform[1] * cu.X(t) + li.stdform[2] * cu.Y(t); 1481 return v * v; 1482 }; 1483 1484 // Find some intersection point 1485 if (this.meetCurveLineContinuous.t1memo) { 1486 tstart = this.meetCurveLineContinuous.t1memo; 1487 t = Numerics.root(func, tstart); 1488 } else { 1489 tstart = cu.minX(); 1490 tend = cu.maxX(); 1491 t = Numerics.root(func, [tstart, tend]); 1492 } 1493 1494 this.meetCurveLineContinuous.t1memo = t; 1495 cux = cu.X(t); 1496 cuy = cu.Y(t); 1497 1498 // Find second intersection point 1499 if (nr === 1) { 1500 if (this.meetCurveLineContinuous.t2memo) { 1501 tstart = this.meetCurveLineContinuous.t2memo; 1502 } 1503 t2 = Numerics.root(func, tstart); 1504 1505 if (!(Math.abs(t2 - t) > 0.1 && Math.abs(cux - cu.X(t2)) > 0.1 && Math.abs(cuy - cu.Y(t2)) > 0.1)) { 1506 steps = 20; 1507 delta = (cu.maxX() - cu.minX()) / steps; 1508 tnew = cu.minX(); 1509 1510 for (i = 0; i < steps; i++) { 1511 t2 = Numerics.root(func, [tnew, tnew + delta]); 1512 1513 if (Math.abs(func(t2)) <= eps && Math.abs(t2 - t) > 0.1 && Math.abs(cux - cu.X(t2)) > 0.1 && Math.abs(cuy - cu.Y(t2)) > 0.1) { 1514 break; 1515 } 1516 1517 tnew += delta; 1518 } 1519 } 1520 t = t2; 1521 this.meetCurveLineContinuous.t2memo = t; 1522 } 1523 1524 // Is the point on the line? 1525 if (Math.abs(func(t)) > eps) { 1526 z = NaN; 1527 } else { 1528 z = 1.0; 1529 } 1530 1531 return (new Coords(Const.COORDS_BY_USER, [z, cu.X(t), cu.Y(t)], board)); 1532 }, 1533 1534 /** 1535 * Intersection of line and curve, discrete case. 1536 * Segments are treated as lines. 1537 * Finding the nr-th intersection point should work for all nr. 1538 * @param {JXG.Curve} cu 1539 * @param {JXG.Line} li 1540 * @param {Number} nr 1541 * @param {JXG.Board} board 1542 * @param {Boolean} testSegment Test if intersection has to be inside of the segment or somewhere on the 1543 * line defined by the segment 1544 * 1545 * @returns {JXG.Coords} Intersection point. In case no intersection point is detected, 1546 * the ideal point [0,1,0] is returned. 1547 */ 1548 meetCurveLineDiscrete: function (cu, li, nr, board, testSegment) { 1549 var i, j, 1550 p1, p2, p, q, 1551 lip1 = li.point1.coords.usrCoords, 1552 lip2 = li.point2.coords.usrCoords, 1553 d, res, 1554 cnt = 0, 1555 len = cu.numberPoints, 1556 ev_sf = Type.evaluate(li.visProp.straightfirst), 1557 ev_sl = Type.evaluate(li.visProp.straightlast); 1558 1559 // In case, no intersection will be found we will take this 1560 q = new Coords(Const.COORDS_BY_USER, [0, NaN, NaN], board); 1561 1562 if (lip1[0] === 0.0) { 1563 lip1 = [1, lip2[1] + li.stdform[2], lip2[2] - li.stdform[1]]; 1564 } else if (lip2[0] === 0.0) { 1565 lip2 = [1, lip1[1] + li.stdform[2], lip1[2] - li.stdform[1]]; 1566 } 1567 1568 p2 = cu.points[0].usrCoords; 1569 for (i = 1; i < len; i++) { 1570 p1 = p2.slice(0); 1571 p2 = cu.points[i].usrCoords; 1572 d = this.distance(p1, p2); 1573 1574 // The defining points are not identical 1575 if (d > Mat.eps) { 1576 if (cu.bezierDegree === 3) { 1577 res = this.meetBeziersegmentBeziersegment([ 1578 cu.points[i - 1].usrCoords.slice(1), 1579 cu.points[i].usrCoords.slice(1), 1580 cu.points[i + 1].usrCoords.slice(1), 1581 cu.points[i + 2].usrCoords.slice(1) 1582 ], [ 1583 lip1.slice(1), 1584 lip2.slice(1) 1585 ], testSegment); 1586 1587 i += 2; 1588 } else { 1589 res = [this.meetSegmentSegment(p1, p2, lip1, lip2)]; 1590 } 1591 1592 for (j = 0; j < res.length; j++) { 1593 p = res[j]; 1594 if (0 <= p[1] && p[1] <= 1) { 1595 if (cnt === nr) { 1596 /** 1597 * If the intersection point is not part of the segment, 1598 * this intersection point is set to non-existent. 1599 * This prevents jumping of the intersection points. 1600 * But it may be discussed if it is the desired behavior. 1601 */ 1602 if (testSegment && 1603 ((!ev_sf && p[2] < 0) || (!ev_sl && p[2] > 1))) { 1604 return q; // break; 1605 } 1606 1607 q = new Coords(Const.COORDS_BY_USER, p[0], board); 1608 return q; // break; 1609 } 1610 cnt += 1; 1611 } 1612 } 1613 } 1614 } 1615 1616 return q; 1617 }, 1618 1619 /** 1620 * Find the n-th intersection point of two curves named red (first parameter) and blue (second parameter). 1621 * We go through each segment of the red curve and search if there is an intersection with a segemnt of the blue curve. 1622 * This double loop, i.e. the outer loop runs along the red curve and the inner loop runs along the blue curve, defines 1623 * the n-th intersection point. The segments are either line segments or Bezier curves of degree 3. This depends on 1624 * the property bezierDegree of the curves. 1625 * <p> 1626 * This method works also for transformed curves, since only the already 1627 * transformed points are used. 1628 * 1629 * @param {JXG.Curve} red 1630 * @param {JXG.Curve} blue 1631 * @param {Number} nr 1632 */ 1633 meetCurveRedBlueSegments: function (red, blue, nr) { 1634 var i, j, 1635 red1, red2, blue1, blue2, m, 1636 minX, maxX, 1637 iFound = 0, 1638 lenBlue = blue.numberPoints, //points.length, 1639 lenRed = red.numberPoints; //points.length; 1640 1641 if (lenBlue <= 1 || lenRed <= 1) { 1642 return [0, NaN, NaN]; 1643 } 1644 1645 for (i = 1; i < lenRed; i++) { 1646 red1 = red.points[i - 1].usrCoords; 1647 red2 = red.points[i].usrCoords; 1648 minX = Math.min(red1[1], red2[1]); 1649 maxX = Math.max(red1[1], red2[1]); 1650 1651 blue2 = blue.points[0].usrCoords; 1652 for (j = 1; j < lenBlue; j++) { 1653 blue1 = blue2; 1654 blue2 = blue.points[j].usrCoords; 1655 1656 if (Math.min(blue1[1], blue2[1]) < maxX && Math.max(blue1[1], blue2[1]) > minX) { 1657 m = this.meetSegmentSegment(red1, red2, blue1, blue2); 1658 if (m[1] >= 0.0 && m[2] >= 0.0 && 1659 // The two segments meet in the interior or at the start points 1660 ((m[1] < 1.0 && m[2] < 1.0) || 1661 // One of the curve is intersected in the very last point 1662 (i === lenRed - 1 && m[1] === 1.0) || 1663 (j === lenBlue - 1 && m[2] === 1.0))) { 1664 if (iFound === nr) { 1665 return m[0]; 1666 } 1667 1668 iFound++; 1669 } 1670 } 1671 } 1672 } 1673 1674 return [0, NaN, NaN]; 1675 }, 1676 1677 /** 1678 * Intersection of two segments. 1679 * @param {Array} p1 First point of segment 1 using homogeneous coordinates [z,x,y] 1680 * @param {Array} p2 Second point of segment 1 using homogeneous coordinates [z,x,y] 1681 * @param {Array} q1 First point of segment 2 using homogeneous coordinates [z,x,y] 1682 * @param {Array} q2 Second point of segment 2 using homogeneous coordinates [z,x,y] 1683 * @returns {Array} [Intersection point, t, u] The first entry contains the homogeneous coordinates 1684 * of the intersection point. The second and third entry gives the position of the intersection between the 1685 * two defining points. For example, the second entry t is defined by: intersection point = t*p1 + (1-t)*p2. 1686 **/ 1687 meetSegmentSegment: function (p1, p2, q1, q2) { 1688 var t, u, diff, 1689 li1 = Mat.crossProduct(p1, p2), 1690 li2 = Mat.crossProduct(q1, q2), 1691 c = Mat.crossProduct(li1, li2), 1692 denom = c[0]; 1693 1694 if (Math.abs(denom) < Mat.eps) { 1695 return [c, Infinity, Infinity]; 1696 } 1697 1698 diff = [q1[1] - p1[1], q1[2] - p1[2]]; 1699 1700 // Because of speed issues, evalute the determinants directly 1701 t = (diff[0] * (q2[2] - q1[2]) - diff[1] * (q2[1] - q1[1])) / denom; 1702 u = (diff[0] * (p2[2] - p1[2]) - diff[1] * (p2[1] - p1[1])) / denom; 1703 1704 return [c, t, u]; 1705 }, 1706 1707 /****************************************/ 1708 /**** BEZIER CURVE ALGORITHMS ****/ 1709 /****************************************/ 1710 1711 /** 1712 * Splits a Bezier curve segment defined by four points into 1713 * two Bezier curve segments. Dissection point is t=1/2. 1714 * @param {Array} curve Array of four coordinate arrays of length 2 defining a 1715 * Bezier curve segment, i.e. [[x0,y0], [x1,y1], [x2,y2], [x3,y3]]. 1716 * @returns {Array} Array consisting of two coordinate arrays for Bezier curves. 1717 */ 1718 _bezierSplit: function (curve) { 1719 var p0, p1, p2, p00, p22, p000; 1720 1721 p0 = [(curve[0][0] + curve[1][0]) * 0.5, (curve[0][1] + curve[1][1]) * 0.5]; 1722 p1 = [(curve[1][0] + curve[2][0]) * 0.5, (curve[1][1] + curve[2][1]) * 0.5]; 1723 p2 = [(curve[2][0] + curve[3][0]) * 0.5, (curve[2][1] + curve[3][1]) * 0.5]; 1724 1725 p00 = [(p0[0] + p1[0]) * 0.5, (p0[1] + p1[1]) * 0.5]; 1726 p22 = [(p1[0] + p2[0]) * 0.5, (p1[1] + p2[1]) * 0.5]; 1727 1728 p000 = [(p00[0] + p22[0]) * 0.5, (p00[1] + p22[1]) * 0.5]; 1729 1730 return [[curve[0], p0, p00, p000], [p000, p22, p2, curve[3]]]; 1731 }, 1732 1733 /** 1734 * Computes the bounding box [minX, maxY, maxX, minY] of a Bezier curve segment 1735 * from its control points. 1736 * @param {Array} curve Array of four coordinate arrays of length 2 defining a 1737 * Bezier curve segment, i.e. [[x0,y0], [x1,y1], [x2,y2], [x3,y3]]. 1738 * @returns {Array} Bounding box [minX, maxY, maxX, minY] 1739 */ 1740 _bezierBbox: function (curve) { 1741 var bb = []; 1742 1743 if (curve.length === 4) { // bezierDegree == 3 1744 bb[0] = Math.min(curve[0][0], curve[1][0], curve[2][0], curve[3][0]); // minX 1745 bb[1] = Math.max(curve[0][1], curve[1][1], curve[2][1], curve[3][1]); // maxY 1746 bb[2] = Math.max(curve[0][0], curve[1][0], curve[2][0], curve[3][0]); // maxX 1747 bb[3] = Math.min(curve[0][1], curve[1][1], curve[2][1], curve[3][1]); // minY 1748 } else { // bezierDegree == 1 1749 bb[0] = Math.min(curve[0][0], curve[1][0]); // minX 1750 bb[1] = Math.max(curve[0][1], curve[1][1]); // maxY 1751 bb[2] = Math.max(curve[0][0], curve[1][0]); // maxX 1752 bb[3] = Math.min(curve[0][1], curve[1][1]); // minY 1753 } 1754 1755 return bb; 1756 }, 1757 1758 /** 1759 * Decide if two Bezier curve segments overlap by comparing their bounding boxes. 1760 * @param {Array} bb1 Bounding box of the first Bezier curve segment 1761 * @param {Array} bb2 Bounding box of the second Bezier curve segment 1762 * @returns {Boolean} true if the bounding boxes overlap, false otherwise. 1763 */ 1764 _bezierOverlap: function (bb1, bb2) { 1765 return bb1[2] >= bb2[0] && bb1[0] <= bb2[2] && bb1[1] >= bb2[3] && bb1[3] <= bb2[1]; 1766 }, 1767 1768 /** 1769 * Append list of intersection points to a list. 1770 * @private 1771 */ 1772 _bezierListConcat: function (L, Lnew, t1, t2) { 1773 var i, 1774 t2exists = Type.exists(t2), 1775 start = 0, 1776 len = Lnew.length, 1777 le = L.length; 1778 1779 if (le > 0 && len > 0 && 1780 ((L[le - 1][1] === 1 && Lnew[0][1] === 0) || 1781 (t2exists && L[le - 1][2] === 1 && Lnew[0][2] === 0))) { 1782 start = 1; 1783 } 1784 1785 for (i = start; i < len; i++) { 1786 if (t2exists) { 1787 Lnew[i][2] *= 0.5; 1788 Lnew[i][2] += t2; 1789 } 1790 1791 Lnew[i][1] *= 0.5; 1792 Lnew[i][1] += t1; 1793 1794 L.push(Lnew[i]); 1795 } 1796 }, 1797 1798 /** 1799 * Find intersections of two Bezier curve segments by recursive subdivision. 1800 * Below maxlevel determine intersections by intersection line segments. 1801 * @param {Array} red Array of four coordinate arrays of length 2 defining the first 1802 * Bezier curve segment, i.e. [[x0,y0], [x1,y1], [x2,y2], [x3,y3]]. 1803 * @param {Array} blue Array of four coordinate arrays of length 2 defining the second 1804 * Bezier curve segment, i.e. [[x0,y0], [x1,y1], [x2,y2], [x3,y3]]. 1805 * @param {Number} level Recursion level 1806 * @returns {Array} List of intersection points (up to nine). Each intersection point is an 1807 * array of length three (homogeneous coordinates) plus preimages. 1808 */ 1809 _bezierMeetSubdivision: function (red, blue, level) { 1810 var bbb, bbr, 1811 ar, b0, b1, r0, r1, m, 1812 p0, p1, q0, q1, 1813 L = [], 1814 maxLev = 5; // Maximum recursion level 1815 1816 bbr = this._bezierBbox(blue); 1817 bbb = this._bezierBbox(red); 1818 1819 if (!this._bezierOverlap(bbr, bbb)) { 1820 return []; 1821 } 1822 1823 if (level < maxLev) { 1824 ar = this._bezierSplit(red); 1825 r0 = ar[0]; 1826 r1 = ar[1]; 1827 1828 ar = this._bezierSplit(blue); 1829 b0 = ar[0]; 1830 b1 = ar[1]; 1831 1832 this._bezierListConcat(L, this._bezierMeetSubdivision(r0, b0, level + 1), 0.0, 0.0); 1833 this._bezierListConcat(L, this._bezierMeetSubdivision(r0, b1, level + 1), 0, 0.5); 1834 this._bezierListConcat(L, this._bezierMeetSubdivision(r1, b0, level + 1), 0.5, 0.0); 1835 this._bezierListConcat(L, this._bezierMeetSubdivision(r1, b1, level + 1), 0.5, 0.5); 1836 1837 return L; 1838 } 1839 1840 // Make homogeneous coordinates 1841 q0 = [1].concat(red[0]); 1842 q1 = [1].concat(red[3]); 1843 p0 = [1].concat(blue[0]); 1844 p1 = [1].concat(blue[3]); 1845 1846 m = this.meetSegmentSegment(q0, q1, p0, p1); 1847 1848 if (m[1] >= 0.0 && m[2] >= 0.0 && m[1] <= 1.0 && m[2] <= 1.0) { 1849 return [m]; 1850 } 1851 1852 return []; 1853 }, 1854 1855 /** 1856 * @param {Boolean} testSegment Test if intersection has to be inside of the segment or somewhere on the line defined by the segment 1857 */ 1858 _bezierLineMeetSubdivision: function (red, blue, level, testSegment) { 1859 var bbb, bbr, 1860 ar, r0, r1, m, 1861 p0, p1, q0, q1, 1862 L = [], 1863 maxLev = 5; // Maximum recursion level 1864 1865 bbb = this._bezierBbox(blue); 1866 bbr = this._bezierBbox(red); 1867 1868 if (testSegment && !this._bezierOverlap(bbr, bbb)) { 1869 return []; 1870 } 1871 1872 if (level < maxLev) { 1873 ar = this._bezierSplit(red); 1874 r0 = ar[0]; 1875 r1 = ar[1]; 1876 1877 this._bezierListConcat(L, this._bezierLineMeetSubdivision(r0, blue, level + 1), 0.0); 1878 this._bezierListConcat(L, this._bezierLineMeetSubdivision(r1, blue, level + 1), 0.5); 1879 1880 return L; 1881 } 1882 1883 // Make homogeneous coordinates 1884 q0 = [1].concat(red[0]); 1885 q1 = [1].concat(red[3]); 1886 p0 = [1].concat(blue[0]); 1887 p1 = [1].concat(blue[1]); 1888 1889 m = this.meetSegmentSegment(q0, q1, p0, p1); 1890 1891 if (m[1] >= 0.0 && m[1] <= 1.0) { 1892 if (!testSegment || (m[2] >= 0.0 && m[2] <= 1.0)) { 1893 return [m]; 1894 } 1895 } 1896 1897 return []; 1898 }, 1899 1900 /** 1901 * Find the nr-th intersection point of two Bezier curve segments. 1902 * @param {Array} red Array of four coordinate arrays of length 2 defining the first 1903 * Bezier curve segment, i.e. [[x0,y0], [x1,y1], [x2,y2], [x3,y3]]. 1904 * @param {Array} blue Array of four coordinate arrays of length 2 defining the second 1905 * Bezier curve segment, i.e. [[x0,y0], [x1,y1], [x2,y2], [x3,y3]]. 1906 * @param {Boolean} testSegment Test if intersection has to be inside of the segment or somewhere on the line defined by the segment 1907 * @returns {Array} Array containing the list of all intersection points as homogeneous coordinate arrays plus 1908 * preimages [x,y], t_1, t_2] of the two Bezier curve segments. 1909 * 1910 */ 1911 meetBeziersegmentBeziersegment: function (red, blue, testSegment) { 1912 var L, L2, i; 1913 1914 if (red.length === 4 && blue.length === 4) { 1915 L = this._bezierMeetSubdivision(red, blue, 0); 1916 } else { 1917 L = this._bezierLineMeetSubdivision(red, blue, 0, testSegment); 1918 } 1919 1920 L.sort(function (a, b) { 1921 return (a[1] - b[1]) * 10000000.0 + (a[2] - b[2]); 1922 }); 1923 1924 L2 = []; 1925 for (i = 0; i < L.length; i++) { 1926 // Only push entries different from their predecessor 1927 if (i === 0 || (L[i][1] !== L[i - 1][1] || L[i][2] !== L[i - 1][2])) { 1928 L2.push(L[i]); 1929 } 1930 } 1931 return L2; 1932 }, 1933 1934 /** 1935 * Find the nr-th intersection point of two Bezier curves, i.e. curves with bezierDegree == 3. 1936 * @param {JXG.Curve} red Curve with bezierDegree == 3 1937 * @param {JXG.Curve} blue Curve with bezierDegree == 3 1938 * @param {Number} nr The number of the intersection point which should be returned. 1939 * @returns {Array} The homogeneous coordinates of the nr-th intersection point. 1940 */ 1941 meetBezierCurveRedBlueSegments: function (red, blue, nr) { 1942 var p, i, j, 1943 redArr, blueArr, 1944 bbr, bbb, 1945 lenBlue = blue.numberPoints, //points.length, 1946 lenRed = red.numberPoints, // points.length, 1947 L = []; 1948 1949 if (lenBlue < 4 || lenRed < 4) { 1950 return [0, NaN, NaN]; 1951 } 1952 1953 for (i = 0; i < lenRed - 3; i += 3) { 1954 p = red.points; 1955 redArr = [ 1956 [p[i].usrCoords[1], p[i].usrCoords[2]], 1957 [p[i + 1].usrCoords[1], p[i + 1].usrCoords[2]], 1958 [p[i + 2].usrCoords[1], p[i + 2].usrCoords[2]], 1959 [p[i + 3].usrCoords[1], p[i + 3].usrCoords[2]] 1960 ]; 1961 1962 bbr = this._bezierBbox(redArr); 1963 1964 for (j = 0; j < lenBlue - 3; j += 3) { 1965 p = blue.points; 1966 blueArr = [ 1967 [p[j].usrCoords[1], p[j].usrCoords[2]], 1968 [p[j + 1].usrCoords[1], p[j + 1].usrCoords[2]], 1969 [p[j + 2].usrCoords[1], p[j + 2].usrCoords[2]], 1970 [p[j + 3].usrCoords[1], p[j + 3].usrCoords[2]] 1971 ]; 1972 1973 bbb = this._bezierBbox(blueArr); 1974 if (this._bezierOverlap(bbr, bbb)) { 1975 L = L.concat(this.meetBeziersegmentBeziersegment(redArr, blueArr)); 1976 if (L.length > nr) { 1977 return L[nr][0]; 1978 } 1979 } 1980 } 1981 } 1982 if (L.length > nr) { 1983 return L[nr][0]; 1984 } 1985 1986 return [0, NaN, NaN]; 1987 }, 1988 1989 bezierSegmentEval: function (t, curve) { 1990 var f, x, y, 1991 t1 = 1.0 - t; 1992 1993 x = 0; 1994 y = 0; 1995 1996 f = t1 * t1 * t1; 1997 x += f * curve[0][0]; 1998 y += f * curve[0][1]; 1999 2000 f = 3.0 * t * t1 * t1; 2001 x += f * curve[1][0]; 2002 y += f * curve[1][1]; 2003 2004 f = 3.0 * t * t * t1; 2005 x += f * curve[2][0]; 2006 y += f * curve[2][1]; 2007 2008 f = t * t * t; 2009 x += f * curve[3][0]; 2010 y += f * curve[3][1]; 2011 2012 return [1.0, x, y]; 2013 }, 2014 2015 /** 2016 * Generate the defining points of a 3rd degree bezier curve that approximates 2017 * a circle sector defined by three arrays A, B,C, each of length three. 2018 * The coordinate arrays are given in homogeneous coordinates. 2019 * @param {Array} A First point 2020 * @param {Array} B Second point (intersection point) 2021 * @param {Array} C Third point 2022 * @param {Boolean} withLegs Flag. If true the legs to the intersection point are part of the curve. 2023 * @param {Number} sgn Wither 1 or -1. Needed for minor and major arcs. In case of doubt, use 1. 2024 */ 2025 bezierArc: function (A, B, C, withLegs, sgn) { 2026 var p1, p2, p3, p4, 2027 r, phi, beta, 2028 PI2 = Math.PI * 0.5, 2029 x = B[1], 2030 y = B[2], 2031 z = B[0], 2032 dataX = [], dataY = [], 2033 co, si, ax, ay, bx, by, k, v, d, matrix; 2034 2035 r = this.distance(B, A); 2036 2037 // x,y, z is intersection point. Normalize it. 2038 x /= z; 2039 y /= z; 2040 2041 phi = this.rad(A.slice(1), B.slice(1), C.slice(1)); 2042 if (sgn === -1) { 2043 phi = 2 * Math.PI - phi; 2044 } 2045 2046 p1 = A; 2047 p1[1] /= p1[0]; 2048 p1[2] /= p1[0]; 2049 p1[0] /= p1[0]; 2050 2051 p4 = p1.slice(0); 2052 2053 if (withLegs) { 2054 dataX = [x, x + 0.333 * (p1[1] - x), x + 0.666 * (p1[1] - x), p1[1]]; 2055 dataY = [y, y + 0.333 * (p1[2] - y), y + 0.666 * (p1[2] - y), p1[2]]; 2056 } else { 2057 dataX = [p1[1]]; 2058 dataY = [p1[2]]; 2059 } 2060 2061 while (phi > Mat.eps) { 2062 if (phi > PI2) { 2063 beta = PI2; 2064 phi -= PI2; 2065 } else { 2066 beta = phi; 2067 phi = 0; 2068 } 2069 2070 co = Math.cos(sgn * beta); 2071 si = Math.sin(sgn * beta); 2072 2073 matrix = [ 2074 [1, 0, 0], 2075 [x * (1 - co) + y * si, co, -si], 2076 [y * (1 - co) - x * si, si, co] 2077 ]; 2078 v = Mat.matVecMult(matrix, p1); 2079 p4 = [v[0] / v[0], v[1] / v[0], v[2] / v[0]]; 2080 2081 ax = p1[1] - x; 2082 ay = p1[2] - y; 2083 bx = p4[1] - x; 2084 by = p4[2] - y; 2085 2086 d = Math.sqrt((ax + bx) * (ax + bx) + (ay + by) * (ay + by)); 2087 2088 if (Math.abs(by - ay) > Mat.eps) { 2089 k = (ax + bx) * (r / d - 0.5) / (by - ay) * 8 / 3; 2090 } else { 2091 k = (ay + by) * (r / d - 0.5) / (ax - bx) * 8 / 3; 2092 } 2093 2094 p2 = [1, p1[1] - k * ay, p1[2] + k * ax]; 2095 p3 = [1, p4[1] + k * by, p4[2] - k * bx]; 2096 2097 dataX = dataX.concat([p2[1], p3[1], p4[1]]); 2098 dataY = dataY.concat([p2[2], p3[2], p4[2]]); 2099 p1 = p4.slice(0); 2100 } 2101 2102 if (withLegs) { 2103 dataX = dataX.concat([ p4[1] + 0.333 * (x - p4[1]), p4[1] + 0.666 * (x - p4[1]), x]); 2104 dataY = dataY.concat([ p4[2] + 0.333 * (y - p4[2]), p4[2] + 0.666 * (y - p4[2]), y]); 2105 } 2106 2107 return [dataX, dataY]; 2108 }, 2109 2110 /****************************************/ 2111 /**** PROJECTIONS ****/ 2112 /****************************************/ 2113 2114 /** 2115 * Calculates the coordinates of the projection of a given point on a given circle. I.o.w. the 2116 * nearest one of the two intersection points of the line through the given point and the circles 2117 * center. 2118 * @param {JXG.Point,JXG.Coords} point Point to project or coords object to project. 2119 * @param {JXG.Circle} circle Circle on that the point is projected. 2120 * @param {JXG.Board} [board=point.board] Reference to the board 2121 * @returns {JXG.Coords} The coordinates of the projection of the given point on the given circle. 2122 */ 2123 projectPointToCircle: function (point, circle, board) { 2124 var dist, P, x, y, factor, 2125 M = circle.center.coords.usrCoords; 2126 2127 if (!Type.exists(board)) { 2128 board = point.board; 2129 } 2130 2131 // gave us a point 2132 if (Type.isPoint(point)) { 2133 dist = point.coords.distance(Const.COORDS_BY_USER, circle.center.coords); 2134 P = point.coords.usrCoords; 2135 // gave us coords 2136 } else { 2137 dist = point.distance(Const.COORDS_BY_USER, circle.center.coords); 2138 P = point.usrCoords; 2139 } 2140 2141 if (Math.abs(dist) < Mat.eps) { 2142 dist = Mat.eps; 2143 } 2144 2145 factor = circle.Radius() / dist; 2146 x = M[1] + factor * (P[1] - M[1]); 2147 y = M[2] + factor * (P[2] - M[2]); 2148 2149 return new Coords(Const.COORDS_BY_USER, [x, y], board); 2150 }, 2151 2152 /** 2153 * Calculates the coordinates of the orthogonal projection of a given point on a given line. I.o.w. the 2154 * intersection point of the given line and its perpendicular through the given point. 2155 * @param {JXG.Point} point Point to project. 2156 * @param {JXG.Line} line Line on that the point is projected. 2157 * @param {JXG.Board} [board=point.board] Reference to a board. 2158 * @returns {JXG.Coords} The coordinates of the projection of the given point on the given line. 2159 */ 2160 projectPointToLine: function (point, line, board) { 2161 // Homogeneous version 2162 var v = [0, line.stdform[1], line.stdform[2]]; 2163 2164 if (!Type.exists(board)) { 2165 board = point.board; 2166 } 2167 2168 v = Mat.crossProduct(v, point.coords.usrCoords); 2169 //return this.meetLineLine(v, line.stdform, 0, board); 2170 return new Coords(Const.COORDS_BY_USER, Mat.crossProduct(v, line.stdform), board); 2171 }, 2172 2173 /** 2174 * Calculates the coordinates of the orthogonal projection of a given coordinate array on a given line 2175 * segment defined by two coordinate arrays. 2176 * @param {Array} p Point to project. 2177 * @param {Array} q1 Start point of the line segment on that the point is projected. 2178 * @param {Array} q2 End point of the line segment on that the point is projected. 2179 * @returns {Array} The coordinates of the projection of the given point on the given segment 2180 * and the factor that determines the projected point as a convex combination of the 2181 * two endpoints q1 and q2 of the segment. 2182 */ 2183 projectCoordsToSegment: function (p, q1, q2) { 2184 var t, denom, 2185 s = [q2[1] - q1[1], q2[2] - q1[2]], 2186 v = [p[1] - q1[1], p[2] - q1[2]]; 2187 2188 /** 2189 * If the segment has length 0, i.e. is a point, 2190 * the projection is equal to that point. 2191 */ 2192 if (Math.abs(s[0]) < Mat.eps && Math.abs(s[1]) < Mat.eps) { 2193 return [q1, 0]; 2194 } 2195 2196 t = Mat.innerProduct(v, s); 2197 denom = Mat.innerProduct(s, s); 2198 t /= denom; 2199 2200 return [ [1, t * s[0] + q1[1], t * s[1] + q1[2]], t]; 2201 }, 2202 2203 /** 2204 * Finds the coordinates of the closest point on a Bezier segment of a 2205 * {@link JXG.Curve} to a given coordinate array. 2206 * @param {Array} pos Point to project in homogeneous coordinates. 2207 * @param {JXG.Curve} curve Curve of type "plot" having Bezier degree 3. 2208 * @param {Number} start Number of the Bezier segment of the curve. 2209 * @returns {Array} The coordinates of the projection of the given point 2210 * on the given Bezier segment and the preimage of the curve which 2211 * determines the closest point. 2212 */ 2213 projectCoordsToBeziersegment: function (pos, curve, start) { 2214 var t0, 2215 /** @ignore */ 2216 minfunc = function (t) { 2217 var z = [1, curve.X(start + t), curve.Y(start + t)]; 2218 2219 z[1] -= pos[1]; 2220 z[2] -= pos[2]; 2221 2222 return z[1] * z[1] + z[2] * z[2]; 2223 }; 2224 2225 t0 = JXG.Math.Numerics.fminbr(minfunc, [0.0, 1.0]); 2226 2227 return [[1, curve.X(t0 + start), curve.Y(t0 + start)], t0]; 2228 }, 2229 2230 /** 2231 * Calculates the coordinates of the projection of a given point on a given curve. 2232 * Uses {@link JXG.Math.Geometry.projectCoordsToCurve}. 2233 * 2234 * @param {JXG.Point} point Point to project. 2235 * @param {JXG.Curve} curve Curve on that the point is projected. 2236 * @param {JXG.Board} [board=point.board] Reference to a board. 2237 * @see #projectCoordsToCurve 2238 * @returns {JXG.Coords} The coordinates of the projection of the given point on the given graph. 2239 */ 2240 projectPointToCurve: function (point, curve, board) { 2241 if (!Type.exists(board)) { 2242 board = point.board; 2243 } 2244 2245 var x = point.X(), 2246 y = point.Y(), 2247 t = point.position || 0.0, 2248 result = this.projectCoordsToCurve(x, y, t, curve, board); 2249 2250 point.position = result[1]; 2251 2252 return result[0]; 2253 }, 2254 2255 /** 2256 * Calculates the coordinates of the projection of a coordinates pair on a given curve. In case of 2257 * function graphs this is the 2258 * intersection point of the curve and the parallel to y-axis through the given point. 2259 * @param {Number} x coordinate to project. 2260 * @param {Number} y coordinate to project. 2261 * @param {Number} t start value for newtons method 2262 * @param {JXG.Curve} curve Curve on that the point is projected. 2263 * @param {JXG.Board} [board=curve.board] Reference to a board. 2264 * @see #projectPointToCurve 2265 * @returns {JXG.Coords} Array containing the coordinates of the projection of the given point on the given graph and 2266 * the position on the curve. 2267 */ 2268 projectCoordsToCurve: function (x, y, t, curve, board) { 2269 var newCoords, newCoordsObj, i, j, 2270 mindist, dist, lbda, v, coords, d, 2271 p1, p2, res, 2272 minfunc, tnew, fnew, fold, delta, steps, 2273 minX, maxX, 2274 infty = Number.POSITIVE_INFINITY; 2275 2276 if (!Type.exists(board)) { 2277 board = curve.board; 2278 } 2279 2280 if (Type.evaluate(curve.visProp.curvetype) === 'plot') { 2281 t = 0; 2282 mindist = infty; 2283 2284 if (curve.numberPoints === 0) { 2285 newCoords = [0, 1, 1]; 2286 } else { 2287 newCoords = [curve.Z(0), curve.X(0), curve.Y(0)]; 2288 } 2289 2290 if (curve.numberPoints > 1) { 2291 2292 v = [1, x, y]; 2293 if (curve.bezierDegree === 3) { 2294 j = 0; 2295 } else { 2296 p1 = [curve.Z(0), curve.X(0), curve.Y(0)]; 2297 } 2298 for (i = 0; i < curve.numberPoints - 1; i++) { 2299 if (curve.bezierDegree === 3) { 2300 res = this.projectCoordsToBeziersegment(v, curve, j); 2301 } else { 2302 p2 = [curve.Z(i + 1), curve.X(i + 1), curve.Y(i + 1)]; 2303 res = this.projectCoordsToSegment(v, p1, p2); 2304 } 2305 lbda = res[1]; 2306 coords = res[0]; 2307 2308 if (0.0 <= lbda && lbda <= 1.0) { 2309 dist = this.distance(coords, v); 2310 d = i + lbda; 2311 } else if (lbda < 0.0) { 2312 coords = p1; 2313 dist = this.distance(p1, v); 2314 d = i; 2315 } else if (lbda > 1.0 && i === curve.numberPoints - 2) { 2316 coords = p2; 2317 dist = this.distance(coords, v); 2318 d = curve.numberPoints - 1; 2319 } 2320 2321 if (dist < mindist) { 2322 mindist = dist; 2323 t = d; 2324 newCoords = coords; 2325 } 2326 2327 if (curve.bezierDegree === 3) { 2328 j++; 2329 i += 2; 2330 } else { 2331 p1 = p2; 2332 } 2333 } 2334 } 2335 2336 newCoordsObj = new Coords(Const.COORDS_BY_USER, newCoords, board); 2337 } else { // 'parameter', 'polar', 'functiongraph' 2338 /** @ignore */ 2339 minfunc = function (t) { 2340 var dx, dy; 2341 if (t < curve.minX() || t > curve.maxX()) { 2342 return NaN; 2343 } 2344 dx = x - curve.X(t); 2345 dy = y - curve.Y(t); 2346 return dx * dx + dy * dy; 2347 }; 2348 2349 fold = minfunc(t); 2350 steps = 50; 2351 delta = (curve.maxX() - curve.minX()) / steps; 2352 tnew = curve.minX(); 2353 2354 for (i = 0; i < steps; i++) { 2355 fnew = minfunc(tnew); 2356 2357 if (fnew < fold || isNaN(fold)) { 2358 t = tnew; 2359 fold = fnew; 2360 } 2361 2362 tnew += delta; 2363 } 2364 2365 //t = Numerics.root(Numerics.D(minfunc), t); 2366 t = Numerics.fminbr(minfunc, [t - delta, t + delta]); 2367 2368 2369 minX = curve.minX(); 2370 maxX = curve.maxX(); 2371 // Distinction between closed and open curves is not necessary. 2372 // If closed, the cyclic projection shift will work anyhow 2373 // if (Math.abs(curve.X(minX) - curve.X(maxX)) < Mat.eps && 2374 // Math.abs(curve.Y(minX) - curve.Y(maxX)) < Mat.eps) { 2375 // // Cyclically 2376 // if (t < minX) { 2377 // t = maxX + t - minX; 2378 // } 2379 // if (t > maxX) { 2380 // t = minX + t - maxX; 2381 // } 2382 // } else { 2383 t = (t < minX) ? minX : t; 2384 t = (t > maxX) ? maxX : t; 2385 // } 2386 2387 newCoordsObj = new Coords(Const.COORDS_BY_USER, [curve.X(t), curve.Y(t)], board); 2388 } 2389 2390 return [curve.updateTransform(newCoordsObj), t]; 2391 }, 2392 2393 /** 2394 * Calculates the coordinates of the closest orthogonal projection of a given coordinate array onto the 2395 * border of a polygon. 2396 * @param {Array} p Point to project. 2397 * @param {JXG.Polygon} pol Polygon element 2398 * @returns {Array} The coordinates of the closest projection of the given point to the border of the polygon. 2399 */ 2400 projectCoordsToPolygon: function (p, pol) { 2401 var i, 2402 len = pol.vertices.length, 2403 d_best = Infinity, 2404 d, 2405 projection, 2406 bestprojection; 2407 2408 for (i = 0; i < len; i++) { 2409 projection = JXG.Math.Geometry.projectCoordsToSegment( 2410 p, 2411 pol.vertices[i].coords.usrCoords, 2412 pol.vertices[(i + 1) % len].coords.usrCoords 2413 ); 2414 2415 d = JXG.Math.Geometry.distance(projection[0], p, 3); 2416 if (0 <= projection[1] && projection[1] <= 1 && d < d_best) { 2417 bestprojection = projection[0].slice(0); 2418 d_best = d; 2419 } 2420 } 2421 return bestprojection; 2422 }, 2423 2424 /** 2425 * Calculates the coordinates of the projection of a given point on a given turtle. A turtle consists of 2426 * one or more curves of curveType 'plot'. Uses {@link JXG.Math.Geometry.projectPointToCurve}. 2427 * @param {JXG.Point} point Point to project. 2428 * @param {JXG.Turtle} turtle on that the point is projected. 2429 * @param {JXG.Board} [board=point.board] Reference to a board. 2430 * @returns {JXG.Coords} The coordinates of the projection of the given point on the given turtle. 2431 */ 2432 projectPointToTurtle: function (point, turtle, board) { 2433 var newCoords, t, x, y, i, dist, el, minEl, 2434 np = 0, 2435 npmin = 0, 2436 mindist = Number.POSITIVE_INFINITY, 2437 len = turtle.objects.length; 2438 2439 if (!Type.exists(board)) { 2440 board = point.board; 2441 } 2442 2443 // run through all curves of this turtle 2444 for (i = 0; i < len; i++) { 2445 el = turtle.objects[i]; 2446 2447 if (el.elementClass === Const.OBJECT_CLASS_CURVE) { 2448 newCoords = this.projectPointToCurve(point, el); 2449 dist = this.distance(newCoords.usrCoords, point.coords.usrCoords); 2450 2451 if (dist < mindist) { 2452 x = newCoords.usrCoords[1]; 2453 y = newCoords.usrCoords[2]; 2454 t = point.position; 2455 mindist = dist; 2456 minEl = el; 2457 npmin = np; 2458 } 2459 np += el.numberPoints; 2460 } 2461 } 2462 2463 newCoords = new Coords(Const.COORDS_BY_USER, [x, y], board); 2464 point.position = t + npmin; 2465 2466 return minEl.updateTransform(newCoords); 2467 }, 2468 2469 /** 2470 * Trivial projection of a point to another point. 2471 * @param {JXG.Point} point Point to project (not used). 2472 * @param {JXG.Point} dest Point on that the point is projected. 2473 * @returns {JXG.Coords} The coordinates of the projection of the given point on the given circle. 2474 */ 2475 projectPointToPoint: function (point, dest) { 2476 return dest.coords; 2477 }, 2478 2479 /** 2480 * 2481 * @param {JXG.Point|JXG.Coords} point 2482 * @param {JXG.Board} [board] 2483 */ 2484 projectPointToBoard: function (point, board) { 2485 var i, l, c, 2486 brd = board || point.board, 2487 // comparison factor, point coord idx, bbox idx, 1st bbox corner x & y idx, 2nd bbox corner x & y idx 2488 config = [ 2489 // left 2490 [1, 1, 0, 0, 3, 0, 1], 2491 // top 2492 [-1, 2, 1, 0, 1, 2, 1], 2493 // right 2494 [-1, 1, 2, 2, 1, 2, 3], 2495 // bottom 2496 [1, 2, 3, 0, 3, 2, 3] 2497 ], 2498 coords = point.coords || point, 2499 bbox = brd.getBoundingBox(); 2500 2501 for (i = 0; i < 4; i++) { 2502 c = config[i]; 2503 if (c[0] * coords.usrCoords[c[1]] < c[0] * bbox[c[2]]) { 2504 // define border 2505 l = Mat.crossProduct([1, bbox[c[3]], bbox[c[4]]], [1, bbox[c[5]], bbox[c[6]]]); 2506 l[3] = 0; 2507 l = Mat.normalize(l); 2508 2509 // project point 2510 coords = this.projectPointToLine({coords: coords}, {stdform: l}, brd); 2511 } 2512 } 2513 2514 return coords; 2515 }, 2516 2517 /** 2518 * Calculates the distance of a point to a line. The point and the line are given by homogeneous 2519 * coordinates. For lines this can be line.stdform. 2520 * @param {Array} point Homogeneous coordinates of a point. 2521 * @param {Array} line Homogeneous coordinates of a line ([C,A,B] where A*x+B*y+C*z=0). 2522 * @returns {Number} Distance of the point to the line. 2523 */ 2524 distPointLine: function (point, line) { 2525 var a = line[1], 2526 b = line[2], 2527 c = line[0], 2528 nom; 2529 2530 if (Math.abs(a) + Math.abs(b) < Mat.eps) { 2531 return Number.POSITIVE_INFINITY; 2532 } 2533 2534 nom = a * point[1] + b * point[2] + c; 2535 a *= a; 2536 b *= b; 2537 2538 return Math.abs(nom) / Math.sqrt(a + b); 2539 }, 2540 2541 2542 /** 2543 * Helper function to create curve which displays a Reuleaux polygons. 2544 * @param {Array} points Array of points which should be the vertices of the Reuleaux polygon. Typically, 2545 * these point list is the array vertices of a regular polygon. 2546 * @param {Number} nr Number of vertices 2547 * @returns {Array} An array containing the two functions defining the Reuleaux polygon and the two values 2548 * for the start and the end of the paramtric curve. array may be used as parent array of a 2549 * {@link JXG.Curve}. 2550 * 2551 * @example 2552 * var A = brd.create('point',[-2,-2]); 2553 * var B = brd.create('point',[0,1]); 2554 * var pol = brd.create('regularpolygon',[A,B,3], {withLines:false, fillColor:'none', highlightFillColor:'none', fillOpacity:0.0}); 2555 * var reuleauxTriangle = brd.create('curve', JXG.Math.Geometry.reuleauxPolygon(pol.vertices, 3), 2556 * {strokeWidth:6, strokeColor:'#d66d55', fillColor:'#ad5544', highlightFillColor:'#ad5544'}); 2557 * 2558 * </pre><div class="jxgbox" id="2543a843-46a9-4372-abc1-94d9ad2db7ac" style="width: 300px; height: 300px;"></div> 2559 * <script type="text/javascript"> 2560 * var brd = JXG.JSXGraph.initBoard('2543a843-46a9-4372-abc1-94d9ad2db7ac', {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright:false, shownavigation: false}); 2561 * var A = brd.create('point',[-2,-2]); 2562 * var B = brd.create('point',[0,1]); 2563 * var pol = brd.create('regularpolygon',[A,B,3], {withLines:false, fillColor:'none', highlightFillColor:'none', fillOpacity:0.0}); 2564 * var reuleauxTriangle = brd.create('curve', JXG.Math.Geometry.reuleauxPolygon(pol.vertices, 3), 2565 * {strokeWidth:6, strokeColor:'#d66d55', fillColor:'#ad5544', highlightFillColor:'#ad5544'}); 2566 * </script><pre> 2567 */ 2568 reuleauxPolygon: function (points, nr) { 2569 var beta, 2570 pi2 = Math.PI * 2, 2571 pi2_n = pi2 / nr, 2572 diag = (nr - 1) / 2, 2573 d = 0, 2574 makeFct = function (which, trig) { 2575 return function (t, suspendUpdate) { 2576 var t1 = (t % pi2 + pi2) % pi2, 2577 j = Math.floor(t1 / pi2_n) % nr; 2578 2579 if (!suspendUpdate) { 2580 d = points[0].Dist(points[diag]); 2581 beta = Mat.Geometry.rad([points[0].X() + 1, points[0].Y()], points[0], points[diag % nr]); 2582 } 2583 2584 if (isNaN(j)) { 2585 return j; 2586 } 2587 2588 t1 = t1 * 0.5 + j * pi2_n * 0.5 + beta; 2589 2590 return points[j][which]() + d * Math[trig](t1); 2591 }; 2592 }; 2593 2594 return [makeFct('X', 'cos'), makeFct('Y', 'sin'), 0, pi2]; 2595 } 2596 }); 2597 2598 return Mat.Geometry; 2599 }); 2600