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