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