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