      1 /*
      2     Copyright 2008-2017
      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