1 /* 2 Copyright 2008-2024 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 <https://www.gnu.org/licenses/> 29 and <https://opensource.org/licenses/MIT/>. 30 */ 31 32 /*global JXG: true, define: true*/ 33 /*jslint nomen: true, plusplus: true*/ 34 35 /** 36 * @fileoverview In this file the conic sections defined. 37 */ 38 39 import JXG from "../jxg.js"; 40 import Const from "../base/constants.js"; 41 import Coords from "../base/coords.js"; 42 import Mat from "../math/math.js"; 43 import Numerics from "../math/numerics.js"; 44 import Geometry from "../math/geometry.js"; 45 import Type from "../utils/type.js"; 46 47 /** 48 * @class This element is used to provide a constructor for an ellipse. An ellipse is given by two points (the foci) and a third point on the ellipse or 49 * the length of the major axis. 50 * @pseudo 51 * @name Ellipse 52 * @augments Conic 53 * @constructor 54 * @type JXG.Curve 55 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 56 * @param {JXG.Point,array_JXG.Point,array_JXG.Point,array} point1,point2,point3 Parent elements can be three elements either of type {@link JXG.Point} or array of 57 * numbers describing the coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 58 * @param {JXG.Point,array_JXG.Point,array_number,function} point1,point2,number Parent elements can be two elements either of type {@link JXG.Point} or array of 59 * numbers describing the coordinates of a point. The third parameter is a number/function which defines the length of the major axis 60 * @param {Number} start (Optional) parameter of the curve start, default: 0. 61 * @param {Number} end (Optional) parameter for the curve end, default: 2π. 62 * @example 63 * // Create an Ellipse by three points 64 * var A = board.create('point', [-1,4]); 65 * var B = board.create('point', [-1,-4]); 66 * var C = board.create('point', [1,1]); 67 * var el = board.create('ellipse',[A,B,C]); 68 * </pre><div class="jxgbox" id="JXGa4d7fb6f-8708-4e45-87f2-2379ae2bd2c0" style="width: 300px; height: 300px;"></div> 69 * <script type="text/javascript"> 70 * (function() { 71 * var glex1_board = JXG.JSXGraph.initBoard('JXGa4d7fb6f-8708-4e45-87f2-2379ae2bd2c0', {boundingbox:[-6,6,6,-6], keepaspectratio:true, showcopyright: false, shownavigation: false}); 72 * var A = glex1_board.create('point', [-1,4]); 73 * var B = glex1_board.create('point', [-1,-4]); 74 * var C = glex1_board.create('point', [1,1]); 75 * var el = glex1_board.create('ellipse',[A,B,C]); 76 * })(); 77 * </script><pre> 78 * 79 * @example 80 * // Create an elliptical arc 81 * var p1 = board.create('point', [-1, 2]); 82 * var p2 = board.create('point', [ 1, 2]); 83 * var p3 = board.create('point', [0, 3]); 84 * 85 * var ell = board.create('ellipse', [ 86 * p1, p2, p3, 0, Math.PI], { 87 * lastArrow: {type: 7} 88 * }); 89 * 90 * </pre><div id="JXG950f7c07-27a4-4c67-9505-c73c22ce9345" class="jxgbox" style="width: 300px; height: 300px;"></div> 91 * <script type="text/javascript"> 92 * (function() { 93 * var board = JXG.JSXGraph.initBoard('JXG950f7c07-27a4-4c67-9505-c73c22ce9345', 94 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 95 * var p1 = board.create('point', [-1, 2]); 96 * var p2 = board.create('point', [ 1, 2]); 97 * var p3 = board.create('point', [0, 3]); 98 * 99 * var ell = board.create('ellipse', [ 100 * p1, p2, p3, 0, Math.PI], { 101 * lastArrow: {type: 7} 102 * }); 103 * 104 * })(); 105 * 106 * </script><pre> 107 * 108 * 109 */ 110 JXG.createEllipse = function (board, parents, attributes) { 111 var polarForm, 112 curve, 113 M, 114 C, 115 majorAxis, 116 i, 117 hasPointOrg, 118 // focus 1 and focus 2 119 F = [], 120 attr_foci = Type.copyAttributes(attributes, board.options, "conic", "foci"), 121 attr_center = Type.copyAttributes(attributes, board.options, "conic", "center"), 122 attr_curve = Type.copyAttributes(attributes, board.options, "conic"); 123 124 // The foci and the third point are either points or coordinate arrays. 125 for (i = 0; i < 2; i++) { 126 // focus i given by coordinates 127 if (parents[i].length > 1) { 128 F[i] = board.create("point", parents[i], attr_foci); 129 // focus i given by point 130 } else if (Type.isPoint(parents[i])) { 131 F[i] = board.select(parents[i]); 132 // given by function 133 } else if (Type.isFunction(parents[i]) && Type.isPoint(parents[i]())) { 134 F[i] = parents[i](); 135 // focus i given by point name 136 } else if (Type.isString(parents[i])) { 137 F[i] = board.select(parents[i]); 138 } else { 139 throw new Error( 140 "JSXGraph: Can't create Ellipse with parent types '" + 141 typeof parents[0] + 142 "' and '" + 143 typeof parents[1] + 144 "'." + 145 "\nPossible parent types: [point,point,point], [point,point,number|function]" 146 ); 147 } 148 } 149 150 // length of major axis 151 if (Type.isNumber(parents[2])) { 152 majorAxis = Type.createFunction(parents[2], board); 153 } else if (Type.isFunction(parents[2]) && Type.isNumber(parents[2]())) { 154 majorAxis = parents[2]; 155 } else { 156 // point on ellipse 157 if (Type.isPoint(parents[2])) { 158 C = board.select(parents[2]); 159 // point on ellipse given by coordinates 160 } else if (parents[2].length > 1) { 161 C = board.create("point", parents[2], attr_foci); 162 // given by function 163 } else if (Type.isFunction(parents[2]) && Type.isPoint(parents[2]())) { 164 C = parents[2](); 165 // focus i given by point name 166 } else if (Type.isString(parents[2])) { 167 C = board.select(parents[2]); 168 } else { 169 throw new Error( 170 "JSXGraph: Can't create Ellipse with parent types '" + 171 typeof parents[0] + 172 "' and '" + 173 typeof parents[1] + 174 "' and '" + 175 typeof parents[2] + 176 "'." + 177 "\nPossible parent types: [point,point,point], [point,point,number|function]" 178 ); 179 } 180 /** @ignore */ 181 majorAxis = function () { 182 return C.Dist(F[0]) + C.Dist(F[1]); 183 }; 184 } 185 186 // to 187 if (!Type.exists(parents[4])) { 188 parents[4] = 2 * Math.PI; 189 } 190 191 // from 192 if (!Type.exists(parents[3])) { 193 parents[3] = 0.0; 194 } 195 196 M = board.create( 197 "point", 198 [ 199 function () { 200 return (F[0].X() + F[1].X()) * 0.5; 201 }, 202 function () { 203 return (F[0].Y() + F[1].Y()) * 0.5; 204 } 205 ], 206 attr_center 207 ); 208 209 /** 210 * @class 211 * @ignore 212 */ 213 curve = board.create( 214 "curve", 215 [ 216 function (x) { 217 return 0; 218 }, 219 function (x) { 220 return 0; 221 }, 222 parents[3], 223 parents[4] 224 ], 225 attr_curve 226 ); 227 228 curve.majorAxis = majorAxis; 229 230 // Save the original hasPoint method. It will be called inside of the new hasPoint method. 231 hasPointOrg = curve.hasPoint; 232 233 /** @ignore */ 234 polarForm = function (phi, suspendUpdate) { 235 var r, rr, ax, ay, bx, by, axbx, ayby, f; 236 237 if (!suspendUpdate) { 238 r = majorAxis(); 239 rr = r * r; 240 ax = F[0].X(); 241 ay = F[0].Y(); 242 bx = F[1].X(); 243 by = F[1].Y(); 244 axbx = ax - bx; 245 ayby = ay - by; 246 f = (rr - ax * ax - ay * ay + bx * bx + by * by) / (2 * r); 247 248 curve.quadraticform = [ 249 [f * f - bx * bx - by * by, (f * axbx) / r + bx, (f * ayby) / r + by], 250 [(f * axbx) / r + bx, (axbx * axbx) / rr - 1, (axbx * ayby) / rr], 251 [(f * ayby) / r + by, (axbx * ayby) / rr, (ayby * ayby) / rr - 1] 252 ]; 253 } 254 }; 255 256 /** @ignore */ 257 curve.X = function (phi, suspendUpdate) { 258 var r = majorAxis(), 259 c = F[1].Dist(F[0]), 260 b = (0.5 * (c * c - r * r)) / (c * Math.cos(phi) - r), 261 beta = Math.atan2(F[1].Y() - F[0].Y(), F[1].X() - F[0].X()); 262 263 if (!suspendUpdate) { 264 polarForm(phi, suspendUpdate); 265 } 266 267 return F[0].X() + Math.cos(beta + phi) * b; 268 }; 269 270 /** @ignore */ 271 curve.Y = function (phi, suspendUpdate) { 272 var r = majorAxis(), 273 c = F[1].Dist(F[0]), 274 b = (0.5 * (c * c - r * r)) / (c * Math.cos(phi) - r), 275 beta = Math.atan2(F[1].Y() - F[0].Y(), F[1].X() - F[0].X()); 276 277 return F[0].Y() + Math.sin(beta + phi) * b; 278 }; 279 280 curve.midpoint = curve.center = M; 281 curve.type = Const.OBJECT_TYPE_CONIC; 282 curve.subs = { 283 center: curve.center 284 }; 285 curve.inherits.push(curve.center, F[0], F[1]); 286 if (Type.isPoint(C)) { 287 curve.inherits.push(C); 288 } 289 290 /** 291 * Checks whether (x,y) is near the ellipse line or inside of the ellipse 292 * (in case JXG.Options.conic#hasInnerPoints is true). 293 * @param {Number} x Coordinate in x direction, screen coordinates. 294 * @param {Number} y Coordinate in y direction, screen coordinates. 295 * @returns {Boolean} True if (x,y) is near the ellipse, False otherwise. 296 * @private 297 * @ignore 298 */ 299 curve.hasPoint = function (x, y) { 300 var ac, bc, r, p, dist; 301 302 if (Type.evaluate(this.visProp.hasinnerpoints)) { 303 ac = F[0].coords; 304 bc = F[1].coords; 305 r = this.majorAxis(); 306 p = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board); 307 dist = p.distance(Const.COORDS_BY_USER, ac) + p.distance(Const.COORDS_BY_USER, bc); 308 309 return dist <= r; 310 } 311 312 return hasPointOrg.apply(this, arguments); 313 }; 314 315 M.addChild(curve); 316 for (i = 0; i < 2; i++) { 317 if (Type.isPoint(F[i])) { 318 F[i].addChild(curve); 319 } 320 } 321 if (Type.isPoint(C)) { 322 C.addChild(curve); 323 } 324 curve.setParents(parents); 325 326 return curve; 327 }; 328 329 /** 330 * @class This element is used to provide a constructor for an hyperbola. An hyperbola is given by two points (the foci) and a third point on the hyperbola or 331 * the length of the major axis. 332 * @pseudo 333 * @name Hyperbola 334 * @augments Conic 335 * @constructor 336 * @type JXG.Curve 337 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 338 * @param {JXG.Point,array_JXG.Point,array_JXG.Point,array} point1,point2,point3 Parent elements can be three elements either of type {@link JXG.Point} or array of 339 * numbers describing the coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 340 * @param {JXG.Point,array_JXG.Point,array_number,function} point1,point2,number Parent elements can be two elements either of type {@link JXG.Point} or array of 341 * numbers describing the coordinates of a point. The third parameter is a number/function which defines the length of the major axis 342 * @param {Number} start (Optional) parameter of the curve start, default: -π. 343 * @param {Number} end (Optional) parameter for the curve end, default: π. 344 * @example 345 * // Create an Hyperbola by three points 346 * var A = board.create('point', [-1,4]); 347 * var B = board.create('point', [-1,-4]); 348 * var C = board.create('point', [1,1]); 349 * var el = board.create('hyperbola',[A,B,C]); 350 * </pre><div class="jxgbox" id="JXGcf99049d-a3fe-407f-b936-27d76550f8c4" style="width: 300px; height: 300px;"></div> 351 * <script type="text/javascript"> 352 * (function(){ 353 * var glex1_board = JXG.JSXGraph.initBoard('JXGcf99049d-a3fe-407f-b936-27d76550f8c4', {boundingbox:[-6,6,6,-6], keepaspectratio:true, showcopyright: false, shownavigation: false}); 354 * var A = glex1_board.create('point', [-1,4]); 355 * var B = glex1_board.create('point', [-1,-4]); 356 * var C = glex1_board.create('point', [1,1]); 357 * var el = glex1_board.create('hyperbola',[A,B,C]); 358 * })(); 359 * </script><pre> 360 */ 361 JXG.createHyperbola = function (board, parents, attributes) { 362 var polarForm, 363 curve, 364 M, 365 C, 366 majorAxis, 367 i, 368 // focus 1 and focus 2 369 F = [], 370 attr_foci = Type.copyAttributes(attributes, board.options, "conic", "foci"), 371 attr_center = Type.copyAttributes(attributes, board.options, "conic", "center"), 372 attr_curve = Type.copyAttributes(attributes, board.options, "conic"); 373 374 // The foci and the third point are either points or coordinate arrays. 375 for (i = 0; i < 2; i++) { 376 // focus i given by coordinates 377 if (parents[i].length > 1) { 378 F[i] = board.create("point", parents[i], attr_foci); 379 // focus i given by point 380 } else if (Type.isPoint(parents[i])) { 381 F[i] = board.select(parents[i]); 382 // given by function 383 } else if (Type.isFunction(parents[i]) && Type.isPoint(parents[i]())) { 384 F[i] = parents[i](); 385 // focus i given by point name 386 } else if (Type.isString(parents[i])) { 387 F[i] = board.select(parents[i]); 388 } else { 389 throw new Error( 390 "JSXGraph: Can't create Hyperbola with parent types '" + 391 typeof parents[0] + 392 "' and '" + 393 typeof parents[1] + 394 "'." + 395 "\nPossible parent types: [point,point,point], [point,point,number|function]" 396 ); 397 } 398 } 399 400 // length of major axis 401 if (Type.isNumber(parents[2])) { 402 majorAxis = Type.createFunction(parents[2], board); 403 } else if (Type.isFunction(parents[2]) && Type.isNumber(parents[2]())) { 404 majorAxis = parents[2]; 405 } else { 406 // point on ellipse 407 if (Type.isPoint(parents[2])) { 408 C = board.select(parents[2]); 409 // point on ellipse given by coordinates 410 } else if (parents[2].length > 1) { 411 C = board.create("point", parents[2], attr_foci); 412 // given by function 413 } else if (Type.isFunction(parents[2]) && Type.isPoint(parents[2]())) { 414 C = parents[2](); 415 // focus i given by point name 416 } else if (Type.isString(parents[2])) { 417 C = board.select(parents[2]); 418 } else { 419 throw new Error( 420 "JSXGraph: Can't create Hyperbola with parent types '" + 421 typeof parents[0] + 422 "' and '" + 423 typeof parents[1] + 424 "' and '" + 425 typeof parents[2] + 426 "'." + 427 "\nPossible parent types: [point,point,point], [point,point,number|function]" 428 ); 429 } 430 /** @ignore */ 431 majorAxis = function () { 432 return C.Dist(F[0]) - C.Dist(F[1]); 433 }; 434 } 435 436 // to 437 if (!Type.exists(parents[4])) { 438 parents[4] = 1.0001 * Math.PI; 439 } 440 441 // from 442 if (!Type.exists(parents[3])) { 443 parents[3] = -1.0001 * Math.PI; 444 } 445 446 M = board.create( 447 "point", 448 [ 449 function () { 450 return (F[0].X() + F[1].X()) * 0.5; 451 }, 452 function () { 453 return (F[0].Y() + F[1].Y()) * 0.5; 454 } 455 ], 456 attr_center 457 ); 458 459 /** 460 * @class 461 * @ignore 462 */ 463 curve = board.create( 464 "curve", 465 [ 466 function (x) { 467 return 0; 468 }, 469 function (x) { 470 return 0; 471 }, 472 parents[3], 473 parents[4] 474 ], 475 attr_curve 476 ); 477 478 curve.majorAxis = majorAxis; 479 480 // Hyperbola is defined by (a*sec(t),b*tan(t)) and sec(t) = 1/cos(t) 481 /** @ignore */ 482 polarForm = function (phi, suspendUpdate) { 483 var r, rr, ax, ay, bx, by, axbx, ayby, f; 484 485 if (!suspendUpdate) { 486 r = majorAxis(); 487 rr = r * r; 488 ax = F[0].X(); 489 ay = F[0].Y(); 490 bx = F[1].X(); 491 by = F[1].Y(); 492 axbx = ax - bx; 493 ayby = ay - by; 494 f = (rr - ax * ax - ay * ay + bx * bx + by * by) / (2 * r); 495 496 curve.quadraticform = [ 497 [f * f - bx * bx - by * by, (f * axbx) / r + bx, (f * ayby) / r + by], 498 [(f * axbx) / r + bx, (axbx * axbx) / rr - 1, (axbx * ayby) / rr], 499 [(f * ayby) / r + by, (axbx * ayby) / rr, (ayby * ayby) / rr - 1] 500 ]; 501 } 502 }; 503 504 /** @ignore */ 505 curve.X = function (phi, suspendUpdate) { 506 var r = majorAxis(), 507 c = F[1].Dist(F[0]), 508 b = (0.5 * (c * c - r * r)) / (c * Math.cos(phi) + r), 509 beta = Math.atan2(F[1].Y() - F[0].Y(), F[1].X() - F[0].X()); 510 511 if (!suspendUpdate) { 512 polarForm(phi, suspendUpdate); 513 } 514 515 return F[0].X() + Math.cos(beta + phi) * b; 516 }; 517 518 /** @ignore */ 519 curve.Y = function (phi, suspendUpdate) { 520 var r = majorAxis(), 521 c = F[1].Dist(F[0]), 522 b = (0.5 * (c * c - r * r)) / (c * Math.cos(phi) + r), 523 beta = Math.atan2(F[1].Y() - F[0].Y(), F[1].X() - F[0].X()); 524 525 return F[0].Y() + Math.sin(beta + phi) * b; 526 }; 527 528 curve.midpoint = curve.center = M; 529 curve.subs = { 530 center: curve.center 531 }; 532 curve.inherits.push(curve.center, F[0], F[1]); 533 if (Type.isPoint(C)) { 534 curve.inherits.push(C); 535 } 536 curve.type = Const.OBJECT_TYPE_CONIC; 537 538 M.addChild(curve); 539 for (i = 0; i < 2; i++) { 540 if (Type.isPoint(F[i])) { 541 F[i].addChild(curve); 542 } 543 } 544 if (Type.isPoint(C)) { 545 C.addChild(curve); 546 } 547 curve.setParents(parents); 548 549 return curve; 550 }; 551 552 /** 553 * @class This element is used to provide a constructor for a parabola. A parabola is given by one point (the focus) and a line (the directrix). 554 * @pseudo 555 * @name Parabola 556 * @augments Conic 557 * @constructor 558 * @type Object 559 * @description JXG.Curve 560 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 561 * @param {JXG.Point,array_JXG.Line} point,line Parent elements are a point and a line or a pair of coordinates. 562 * Optional parameters three and four are numbers which define the curve length (e.g. start/end). Default values are -pi and pi. 563 * @example 564 * // Create a parabola by a point C and a line l. 565 * var A = board.create('point', [-1,4]); 566 * var B = board.create('point', [-1,-4]); 567 * var l = board.create('line', [A,B]); 568 * var C = board.create('point', [1,1]); 569 * var el = board.create('parabola',[C,l]); 570 * </pre><div class="jxgbox" id="JXG524d1aae-217d-44d4-ac58-a19c7ab1de36" style="width: 300px; height: 300px;"></div> 571 * <script type="text/javascript"> 572 * (function() { 573 * var glex1_board = JXG.JSXGraph.initBoard('JXG524d1aae-217d-44d4-ac58-a19c7ab1de36', {boundingbox:[-6,6,6,-6], keepaspectratio:true, showcopyright: false, shownavigation: false}); 574 * var A = glex1_board.create('point', [-1,4]); 575 * var B = glex1_board.create('point', [-1,-4]); 576 * var l = glex1_board.create('line', [A,B]); 577 * var C = glex1_board.create('point', [1,1]); 578 * var el = glex1_board.create('parabola',[C,l]); 579 * })(); 580 * </script><pre> 581 * 582 * @example 583 * var par = board.create('parabola',[[3.25, 0], [[0.25, 1],[0.25, 0]]]); 584 * 585 * </pre><div id="JXG09252542-b77a-4990-a109-66ffb649a472" class="jxgbox" style="width: 300px; height: 300px;"></div> 586 * <script type="text/javascript"> 587 * (function() { 588 * var board = JXG.JSXGraph.initBoard('JXG09252542-b77a-4990-a109-66ffb649a472', 589 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 590 * var par = board.create('parabola',[[3.25, 0], [[0.25, 1],[0.25, 0]]]); 591 * 592 * })(); 593 * 594 * </script><pre> 595 * 596 */ 597 JXG.createParabola = function (board, parents, attributes) { 598 var polarForm, 599 curve, 600 M, 601 // focus 602 F1 = parents[0], 603 // directrix 604 l = parents[1], 605 attr_foci = Type.copyAttributes(attributes, board.options, "conic", "foci"), 606 attr_center = Type.copyAttributes(attributes, board.options, "conic", "center"), 607 attr_curve = Type.copyAttributes(attributes, board.options, "conic"), 608 attr_line; 609 610 // focus 1 given by coordinates 611 if (parents[0].length > 1) { 612 F1 = board.create("point", parents[0], attr_foci); 613 // focus 1 given by point 614 } else if (Type.isPoint(parents[0])) { 615 F1 = board.select(parents[0]); 616 // given by function 617 } else if (Type.isFunction(parents[0]) && Type.isPoint(parents[0]())) { 618 F1 = parents[0](); 619 // focus 1 given by point name 620 } else if (Type.isString(parents[0])) { 621 F1 = board.select(parents[0]); 622 } else { 623 throw new Error( 624 "JSXGraph: Can't create Parabola with parent types '" + 625 typeof parents[0] + 626 "' and '" + 627 typeof parents[1] + 628 "'." + 629 "\nPossible parent types: [point,line]" 630 ); 631 } 632 633 // Create line if given as array of two points. 634 if (Type.isArray(l) && l.length === 2) { 635 attr_line = Type.copyAttributes(attributes, board.options, "conic", "line"); 636 l = board.create("line", l, attr_line); 637 } 638 639 // to 640 if (!Type.exists(parents[3])) { 641 parents[3] = 2 * Math.PI; 642 } 643 644 // from 645 if (!Type.exists(parents[2])) { 646 parents[2] = 0; 647 } 648 649 M = board.create( 650 "point", 651 [ 652 function () { 653 /* 654 var v = [0, l.stdform[1], l.stdform[2]]; 655 v = Mat.crossProduct(v, F1.coords.usrCoords); 656 return Geometry.meetLineLine(v, l.stdform, 0, board).usrCoords; 657 */ 658 return Geometry.projectPointToLine(F1, l, board).usrCoords; 659 } 660 ], 661 attr_center 662 ); 663 664 /** 665 * @class 666 * @ignore 667 */ 668 curve = board.create( 669 "curve", 670 [ 671 function (x) { 672 return 0; 673 }, 674 function (x) { 675 return 0; 676 }, 677 parents[2], 678 parents[3] 679 ], 680 attr_curve 681 ); 682 683 curve.midpoint = curve.center = M; 684 curve.subs = { 685 center: curve.center 686 }; 687 curve.inherits.push(curve.center); 688 689 /** @ignore */ 690 polarForm = function (t, suspendUpdate) { 691 var a, b, c, ab, px, py; 692 693 if (!suspendUpdate) { 694 a = l.stdform[1]; 695 b = l.stdform[2]; 696 c = l.stdform[0]; 697 ab = a * a + b * b; 698 px = F1.X(); 699 py = F1.Y(); 700 701 curve.quadraticform = [ 702 [c * c - ab * (px * px + py * py), c * a + ab * px, c * b + ab * py], 703 [c * a + ab * px, -b * b, a * b], 704 [c * b + ab * py, a * b, -a * a] 705 ]; 706 } 707 }; 708 709 /** @ignore */ 710 curve.X = function (phi, suspendUpdate) { 711 var a, 712 det, 713 beta = l.getAngle(), 714 d = Geometry.distPointLine(F1.coords.usrCoords, l.stdform), 715 A = l.point1.coords.usrCoords, 716 B = l.point2.coords.usrCoords, 717 M = F1.coords.usrCoords; 718 719 // Handle the case if one of the two defining points of the line is an ideal point 720 if (A[0] === 0) { 721 A = [1, B[1] + l.stdform[2], B[2] - l.stdform[1]]; 722 } else if (B[0] === 0) { 723 B = [1, A[1] + l.stdform[2], A[2] - l.stdform[1]]; 724 } 725 det = (B[1] - A[1]) * (M[2] - A[2]) - (B[2] - A[2]) * (M[1] - A[1]) >= 0 ? 1 : -1; 726 a = (det * d) / (1 - Math.sin(phi)); 727 728 if (!suspendUpdate) { 729 polarForm(phi, suspendUpdate); 730 } 731 732 return F1.X() + Math.cos(phi + beta) * a; 733 }; 734 735 /** @ignore */ 736 curve.Y = function (phi, suspendUpdate) { 737 var a, 738 det, 739 beta = l.getAngle(), 740 d = Geometry.distPointLine(F1.coords.usrCoords, l.stdform), 741 A = l.point1.coords.usrCoords, 742 B = l.point2.coords.usrCoords, 743 M = F1.coords.usrCoords; 744 745 // Handle the case if one of the two defining points of the line is an ideal point 746 if (A[0] === 0) { 747 A = [1, B[1] + l.stdform[2], B[2] - l.stdform[1]]; 748 } else if (B[0] === 0) { 749 B = [1, A[1] + l.stdform[2], A[2] - l.stdform[1]]; 750 } 751 det = (B[1] - A[1]) * (M[2] - A[2]) - (B[2] - A[2]) * (M[1] - A[1]) >= 0 ? 1 : -1; 752 a = (det * d) / (1 - Math.sin(phi)); 753 754 return F1.Y() + Math.sin(phi + beta) * a; 755 }; 756 757 curve.type = Const.OBJECT_TYPE_CONIC; 758 M.addChild(curve); 759 760 if (Type.isPoint(F1)) { 761 F1.addChild(curve); 762 curve.inherits.push(F1); 763 } 764 765 l.addChild(curve); 766 curve.setParents(parents); 767 768 return curve; 769 }; 770 771 /** 772 * 773 * @class This element is used to provide a constructor for a generic conic section uniquely defined by five points or 774 * a conic defined by the coefficients of the equation 775 * <p><i>Ax<sup>2</sup>+ Bxy+Cy<sup>2</sup> + Dx + Ey + F = 0</i></p>. 776 * Then the parameters are as follows: 777 * <pre> 778 * board.create('conic', [A, C, F, B/2, D/2, E/2]); 779 * </pre> 780 * @pseudo 781 * @name Conic 782 * @augments JXG.Curve 783 * @constructor 784 * @type JXG.Conic 785 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 786 * @param {JXG.Point,Array_JXG.Point,Array_JXG.Point,Array_JXG.Point,Array_JXG.Point,Array} a,b,c,d,e Parent elements are five points. 787 * @param {Number_Number_Number_Number_Number_Number} a_00,a_11,a_22,a_01,a_02,a_12 6 numbers, i.e. A, C, F, B/2, D/2, E/2 788 * @example 789 * // Create a conic section through the points A, B, C, D, and E. 790 * var A = board.create('point', [1,5]); 791 * var B = board.create('point', [1,2]); 792 * var C = board.create('point', [2,0]); 793 * var D = board.create('point', [0,0]); 794 * var E = board.create('point', [-1,5]); 795 * var conic = board.create('conic',[A,B,C,D,E]); 796 * </pre><div class="jxgbox" id="JXG2d79bd6a-db9b-423c-9cba-2497f0b06320" style="width: 300px; height: 300px;"></div> 797 * <script type="text/javascript"> 798 * (function(){ 799 * var glex1_board = JXG.JSXGraph.initBoard('JXG2d79bd6a-db9b-423c-9cba-2497f0b06320', {boundingbox:[-6,6,6,-6], keepaspectratio:true, showcopyright: false, shownavigation: false}); 800 * var A = glex1_board.create('point', [1,5]); 801 * var B = glex1_board.create('point', [1,2]); 802 * var C = glex1_board.create('point', [2,0]); 803 * var D = glex1_board.create('point', [0,0]); 804 * var E = glex1_board.create('point', [-1,5]); 805 * var conic = glex1_board.create('conic',[A,B,C,D,E]); 806 * })(); 807 * </script><pre> 808 * 809 * @example 810 * // Parameters: A, C, F, B/2, D/2, E/2 811 * var conic = board.create('conic', [1, 2, -4, 0, 0, 0]); 812 * 813 * </pre><div id="JXG8576a04a-52d8-4a7e-8d54-e32443910b97" class="jxgbox" style="width: 300px; height: 300px;"></div> 814 * <script type="text/javascript"> 815 * (function() { 816 * var board = JXG.JSXGraph.initBoard('JXG8576a04a-52d8-4a7e-8d54-e32443910b97', 817 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 818 * // Parameters: A, C, F, B/2, D/2, E/2 819 * var conic = board.create('conic', [1, 2, -4, 0, 0, 0]); 820 * })(); 821 * 822 * </script><pre> 823 * 824 */ 825 JXG.createConic = function (board, parents, attributes) { 826 var polarForm, 827 curve, 828 fitConic, 829 degconic, 830 sym, 831 eigen, 832 a, 833 b, 834 c, 835 c1, 836 c2, 837 i, 838 definingMat, 839 givenByPoints, 840 rotationMatrix = [ 841 [1, 0, 0], 842 [0, 1, 0], 843 [0, 0, 1] 844 ], 845 M = [ 846 [1, 0, 0], 847 [0, 1, 0], 848 [0, 0, 1] 849 ], 850 points = [], 851 p = [], 852 attr_point = Type.copyAttributes(attributes, board.options, "conic", "point"), 853 attr_center = Type.copyAttributes(attributes, board.options, "conic", "center"), 854 attr_curve = Type.copyAttributes(attributes, board.options, "conic"); 855 856 if (parents.length === 5) { 857 givenByPoints = true; 858 } else if (parents.length === 6) { 859 givenByPoints = false; 860 } else { 861 throw new Error( 862 "JSXGraph: Can't create generic Conic with " + parents.length + " parameters." 863 ); 864 } 865 866 if (givenByPoints) { 867 for (i = 0; i < 5; i++) { 868 // point i given by coordinates 869 if (parents[i].length > 1) { 870 points[i] = board.create("point", parents[i], attr_point); 871 // point i given by point 872 } else if (Type.isPoint(parents[i])) { 873 points[i] = board.select(parents[i]); 874 // given by function 875 } else if (Type.isFunction(parents[i]) && Type.isPoint(parents[i]())) { 876 points[i] = parents[i](); 877 // point i given by point name 878 } else if (Type.isString(parents[i])) { 879 points[i] = board.select(parents[i]); 880 } else { 881 throw new Error( 882 "JSXGraph: Can't create Conic section with parent types '" + 883 typeof parents[i] + 884 "'." + 885 "\nPossible parent types: [point,point,point,point,point], [a00,a11,a22,a01,a02,a12]" 886 ); 887 } 888 } 889 } else { 890 /* Usual notation (x,y,z): 891 * [[A0,A3,A4], 892 * [A3,A1,A5], 893 * [A4,A5,A2]]. 894 * Our notation (z,x,y): 895 * [[A2, A4, A5], 896 * [A4, A0, A3], 897 * [A5, A3, A1]] 898 */ 899 definingMat = [ 900 [0, 0, 0], 901 [0, 0, 0], 902 [0, 0, 0] 903 ]; 904 definingMat[0][0] = Type.isFunction(parents[2]) 905 ? function () { 906 return parents[2](); 907 } 908 : function () { 909 return parents[2]; 910 }; 911 definingMat[0][1] = Type.isFunction(parents[4]) 912 ? function () { 913 return parents[4](); 914 } 915 : function () { 916 return parents[4]; 917 }; 918 definingMat[0][2] = Type.isFunction(parents[5]) 919 ? function () { 920 return parents[5](); 921 } 922 : function () { 923 return parents[5]; 924 }; 925 definingMat[1][1] = Type.isFunction(parents[0]) 926 ? function () { 927 return parents[0](); 928 } 929 : function () { 930 return parents[0]; 931 }; 932 definingMat[1][2] = Type.isFunction(parents[3]) 933 ? function () { 934 return parents[3](); 935 } 936 : function () { 937 return parents[3]; 938 }; 939 definingMat[2][2] = Type.isFunction(parents[1]) 940 ? function () { 941 return parents[1](); 942 } 943 : function () { 944 return parents[1]; 945 }; 946 } 947 948 // sym(A) = A + A^t . Manipulates A in place. 949 sym = function (A) { 950 var i, j; 951 for (i = 0; i < 3; i++) { 952 for (j = i; j < 3; j++) { 953 A[i][j] += A[j][i]; 954 } 955 } 956 for (i = 0; i < 3; i++) { 957 for (j = 0; j < i; j++) { 958 A[i][j] = A[j][i]; 959 } 960 } 961 return A; 962 }; 963 964 // degconic(v,w) = sym(v*w^t) 965 degconic = function (v, w) { 966 var i, 967 j, 968 mat = [ 969 [0, 0, 0], 970 [0, 0, 0], 971 [0, 0, 0] 972 ]; 973 974 for (i = 0; i < 3; i++) { 975 for (j = 0; j < 3; j++) { 976 mat[i][j] = v[i] * w[j]; 977 } 978 } 979 980 return sym(mat); 981 }; 982 983 // (p^t*B*p)*A-(p^t*A*p)*B 984 fitConic = function (A, B, p) { 985 var i, 986 j, 987 pBp, 988 pAp, 989 Mv, 990 mat = [ 991 [0, 0, 0], 992 [0, 0, 0], 993 [0, 0, 0] 994 ]; 995 996 Mv = Mat.matVecMult(B, p); 997 pBp = Mat.innerProduct(p, Mv); 998 Mv = Mat.matVecMult(A, p); 999 pAp = Mat.innerProduct(p, Mv); 1000 1001 for (i = 0; i < 3; i++) { 1002 for (j = 0; j < 3; j++) { 1003 mat[i][j] = pBp * A[i][j] - pAp * B[i][j]; 1004 } 1005 } 1006 return mat; 1007 }; 1008 1009 // Here, the defining functions for the curve are just dummy functions. 1010 // In polarForm there is a reference to curve.quadraticform. 1011 /** 1012 * @class 1013 * @ignore 1014 */ 1015 curve = board.create( 1016 "curve", 1017 [ 1018 function (x) { 1019 return 0; 1020 }, 1021 function (x) { 1022 return 0; 1023 }, 1024 0, 1025 2 * Math.PI 1026 ], 1027 attr_curve 1028 ); 1029 1030 /** @ignore */ 1031 polarForm = function (phi, suspendUpdate) { 1032 var i, j, v; // len,; 1033 1034 if (!suspendUpdate) { 1035 if (givenByPoints) { 1036 // Copy the point coordinate vectors 1037 for (i = 0; i < 5; i++) { 1038 p[i] = points[i].coords.usrCoords; 1039 } 1040 1041 // Compute the quadratic form 1042 c1 = degconic(Mat.crossProduct(p[0], p[1]), Mat.crossProduct(p[2], p[3])); 1043 c2 = degconic(Mat.crossProduct(p[0], p[2]), Mat.crossProduct(p[1], p[3])); 1044 M = fitConic(c1, c2, p[4]); 1045 } else { 1046 for (i = 0; i < 3; i++) { 1047 for (j = i; j < 3; j++) { 1048 M[i][j] = definingMat[i][j](); 1049 if (j > i) { 1050 M[j][i] = M[i][j]; 1051 } 1052 } 1053 } 1054 } 1055 1056 // Here is the reference back to the curve. 1057 curve.quadraticform = M; 1058 1059 // Compute Eigenvalues and Eigenvectors 1060 eigen = Numerics.Jacobi(M); 1061 1062 // Scale the Eigenvalues such that the first Eigenvalue is positive 1063 if (eigen[0][0][0] < 0) { 1064 eigen[0][0][0] *= -1; 1065 eigen[0][1][1] *= -1; 1066 eigen[0][2][2] *= -1; 1067 } 1068 1069 // Normalize the Eigenvectors 1070 // for (i = 0; i < 3; i++) { 1071 // // len = Mat.hypot(eigen[1][0][i], eigen[1][1][i], eigen[1][2][i]) 1072 // for (j = 0; j < 3; j++) { 1073 // len += eigen[1][j][i] * eigen[1][j][i]; 1074 // } 1075 // len = Math.sqrt(len); 1076 // /*for (j = 0; j < 3; j++) { 1077 // //eigen[1][j][i] /= len; 1078 // }*/ 1079 // } 1080 rotationMatrix = eigen[1]; 1081 c = Math.sqrt(Math.abs(eigen[0][0][0])); 1082 a = Math.sqrt(Math.abs(eigen[0][1][1])); 1083 b = Math.sqrt(Math.abs(eigen[0][2][2])); 1084 } 1085 1086 // The degenerate cases with eigen[0][i][i]==0 are not handled correct yet. 1087 if (eigen[0][1][1] <= 0.0 && eigen[0][2][2] <= 0.0) { 1088 v = Mat.matVecMult(rotationMatrix, [1 / c, Math.cos(phi) / a, Math.sin(phi) / b]); 1089 } else if (eigen[0][1][1] <= 0.0 && eigen[0][2][2] > 0.0) { 1090 v = Mat.matVecMult(rotationMatrix, [Math.cos(phi) / c, 1 / a, Math.sin(phi) / b]); 1091 } else if (eigen[0][2][2] < 0.0) { 1092 v = Mat.matVecMult(rotationMatrix, [Math.sin(phi) / c, Math.cos(phi) / a, 1 / b]); 1093 } 1094 1095 if (Type.exists(v)) { 1096 // Normalize 1097 v[1] /= v[0]; 1098 v[2] /= v[0]; 1099 v[0] = 1.0; 1100 } else { 1101 v = [1, NaN, NaN]; 1102 } 1103 1104 return v; 1105 }; 1106 1107 /** @ignore */ 1108 curve.X = function (phi, suspendUpdate) { 1109 return polarForm(phi, suspendUpdate)[1]; 1110 }; 1111 1112 /** @ignore */ 1113 curve.Y = function (phi, suspendUpdate) { 1114 return polarForm(phi, suspendUpdate)[2]; 1115 }; 1116 1117 // Center coordinates see https://en.wikipedia.org/wiki/Matrix_representation_of_conic_sections 1118 curve.midpoint = board.create( 1119 "point", 1120 [ 1121 function () { 1122 var m = curve.quadraticform; 1123 1124 return [ 1125 m[1][1] * m[2][2] - m[1][2] * m[1][2], 1126 m[1][2] * m[0][2] - m[2][2] * m[0][1], 1127 m[0][1] * m[1][2] - m[1][1] * m[0][2] 1128 ]; 1129 } 1130 ], 1131 attr_center 1132 ); 1133 1134 curve.type = Const.OBJECT_TYPE_CONIC; 1135 curve.center = curve.midpoint; 1136 curve.subs = { 1137 center: curve.center 1138 }; 1139 curve.inherits.push(curve.center); 1140 curve.inherits = curve.inherits.concat(points); 1141 1142 if (givenByPoints) { 1143 for (i = 0; i < 5; i++) { 1144 if (Type.isPoint(points[i])) { 1145 points[i].addChild(curve); 1146 } 1147 } 1148 curve.setParents(parents); 1149 } 1150 curve.addChild(curve.center); 1151 1152 return curve; 1153 }; 1154 1155 JXG.registerElement("ellipse", JXG.createEllipse); 1156 JXG.registerElement("hyperbola", JXG.createHyperbola); 1157 JXG.registerElement("parabola", JXG.createParabola); 1158 JXG.registerElement("conic", JXG.createConic); 1159 1160 // export default { 1161 // createEllipse: JXG.createEllipse, 1162 // createHyperbola: JXG.createHyperbola, 1163 // createParabola: JXG.createParabola, 1164 // createConic: JXG.createConic 1165 // }; 1166