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