      1 /*
      2     Copyright 2008-2015
      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  math/geometry
     39  math/math
     40  base/coords
     41  base/circle
     42  utils/type
     43  base/constants
     44   elements:
     45    curve
     46    midpoint
     47    circumcenter
     48  */
     49
     50 /**
     51  * @fileoverview In this file the geometry object Arc is defined. Arc stores all
     52  * style and functional properties that are required to draw an arc on a board.
     53  */
     54
     55 define([
     56     'jxg', 'math/geometry', 'math/math', 'base/coords', 'base/circle', 'utils/type', 'base/constants',
     57     'base/curve', 'element/composition'
     58 ], function (JXG, Geometry, Mat, Coords, Circle, Type, Const, Curve, Compositions) {
     59
     60     "use strict";
     61
     62     /**
     63      * @class An arc is a segment of the circumference of a circle. It is defined by a center, one point that
     64      * defines the radius, and a third point that defines the angle of the arc.
     65      * @pseudo
     66      * @name Arc
     67      * @augments Curve
     68      * @constructor
     69      * @type JXG.Curve
     70      * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     71      * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The result will be an arc of a circle around p1 through p2. The arc is drawn
     72      * counter-clockwise from p2 to p3.
     73      * @example
     74      * // Create an arc out of three free points
     75      * var p1 = board.create('point', [2.0, 2.0]);
     76      * var p2 = board.create('point', [1.0, 0.5]);
     77      * var p3 = board.create('point', [3.5, 1.0]);
     78      *
     79      * var a = board.create('arc', [p1, p2, p3]);
     80      * </pre><div id="114ef584-4a5e-4686-8392-c97501befb5b" style="width: 300px; height: 300px;"></div>
     81      * <script type="text/javascript">
     82      * (function () {
     83      *   var board = JXG.JSXGraph.initBoard('114ef584-4a5e-4686-8392-c97501befb5b', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
     84      *       p1 = board.create('point', [2.0, 2.0]),
     85      *       p2 = board.create('point', [1.0, 0.5]),
     86      *       p3 = board.create('point', [3.5, 1.0]),
     87      *
     88      *       a = board.create('arc', [p1, p2, p3]);
     89      * })();
     90      * </script><pre>
     91      */
     92     JXG.createArc = function (board, parents, attributes) {
     93         var el, attr, i, points;
     94
     95         // This method is used to create circumcirclearcs, too. If a circumcirclearc is created we get a fourth
     96         // point, that's why we need to check that case, too.
     97         points = Type.providePoints(board, parents, attributes, 'point');
     98         if (points === false || points.length < 3) {
     99             throw new Error("JSXGraph: Can't create Arc with parent types '" +
    100                 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" +
    101                 (typeof parents[2]) + "'." +
    102                 "\nPossible parent types: [point,point,point]");
    103         }
    104
    105         attr = Type.copyAttributes(attributes, board.options, 'arc');
    106         el = board.create('curve', [[0], [0]], attr);
    107
    108         el.elType = 'arc';
    109
    110         el.parents = [];
    111         for (i = 0; i < points.length; i++) {
    112             if (points[i].id) {
    113                 el.parents.push(points[i].id);
    114             }
    115         }
    116
    117         /**
    118          * documented in JXG.GeometryElement
    119          * @ignore
    120          */
    121         el.type = Const.OBJECT_TYPE_ARC;
    122
    123         /**
    124          * Center of the arc.
    125          * @memberOf Arc.prototype
    126          * @name center
    127          * @type JXG.Point
    128          */
    129         el.center = points[0];
    130
    131         /**
    132          * Point defining the arc's radius.
    133          * @memberOf Arc.prototype
    134          * @name radiuspoint
    135          * @type JXG.Point
    136          */
    137         el.radiuspoint = points[1];
    138         el.point2 = el.radiuspoint;
    139
    140         /**
    141          * The point defining the arc's angle.
    142          * @memberOf Arc.prototype
    143          * @name anglepoint
    144          * @type JXG.Point
    145          */
    146         el.anglepoint = points[2];
    147         el.point3 = el.anglepoint;
    148
    149         // Add arc as child to defining points
    150         el.center.addChild(el);
    151         el.radiuspoint.addChild(el);
    152         el.anglepoint.addChild(el);
    153
    154         // should be documented in options
    155         el.useDirection = attr.usedirection;
    156
    157         // documented in JXG.Curve
    158         el.updateDataArray = function () {
    159             var ar, phi, v, det, p0c, p1c, p2c,
    160                 sgn = 1,
    161                 A = this.radiuspoint,
    162                 B = this.center,
    163                 C = this.anglepoint;
    164
    165             phi = Geometry.rad(A, B, C);
    166             if ((this.visProp.selection === 'minor' && phi > Math.PI) ||
    167                     (this.visProp.selection === 'major' && phi < Math.PI)) {
    168                 sgn = -1;
    169             }
    170
    171             // This is true for circumCircleArcs. In that case there is
    172             // a fourth parent element: [center, point1, point3, point2]
    173             if (this.useDirection) {
    174                 p0c = points[1].coords.usrCoords;
    175                 p1c = points[3].coords.usrCoords;
    176                 p2c = points[2].coords.usrCoords;
    177                 det = (p0c[1] - p2c[1]) * (p0c[2] - p1c[2]) - (p0c[2] - p2c[2]) * (p0c[1] - p1c[1]);
    178
    179                 if (det < 0) {
    180                     this.radiuspoint = points[1];
    181                     this.anglepoint = points[2];
    182                 } else {
    183                     this.radiuspoint = points[2];
    184                     this.anglepoint = points[1];
    185                 }
    186             }
    187
    188             A = A.coords.usrCoords;
    189             B = B.coords.usrCoords;
    190             C = C.coords.usrCoords;
    191
    192             ar = Geometry.bezierArc(A, B, C, false, sgn);
    193
    194             this.dataX = ar[0];
    195             this.dataY = ar[1];
    196
    197             this.bezierDegree = 3;
    198
    199             this.updateStdform();
    200             this.updateQuadraticform();
    201         };
    202
    203         /**
    204          * Determines the arc's current radius. I.e. the distance between {@link Arc#center} and {@link Arc#radiuspoint}.
    205          * @memberOf Arc.prototype
    206          * @name Radius
    207          * @function
    208          * @returns {Number} The arc's radius
    209          */
    210         el.Radius = function () {
    211             return this.radiuspoint.Dist(this.center);
    212         };
    213
    214         /**
    215          * @deprecated Use {@link Arc#Radius}
    216          * @memberOf Arc.prototype
    217          * @name getRadius
    218          * @function
    219          * @returns {Number}
    220          */
    221         el.getRadius = function () {
    222             return this.Radius();
    223         };
    224
    225         /**
    226          * Returns the length of the arc.
    227          * @memberOf Arc.prototype
    228          * @name Value
    229          * @function
    230          * @returns {Number} The arc length
    231          */
    232         el.Value = function () {
    233             return this.Radius() * Geometry.rad(this.radiuspoint, this.center, this.anglepoint);
    234         };
    235
    236         // documented in geometry element
    237         el.hasPoint = function (x, y) {
    238             var dist, checkPoint,
    239                 has, angle, alpha, beta,
    240                 invMat, c,
    241                 prec = this.board.options.precision.hasPoint / this.board.unitX,
    242                 r = this.Radius();
    243
    244             checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board);
    245
    246             if (this.transformations.length > 0) {
    247                 // Transform the mouse/touch coordinates
    248                 // back to the original position of the curve.
    249                 this.updateTransformMatrix();
    250                 invMat = Mat.inverse(this.transformMat);
    251                 c = Mat.matVecMult(invMat, checkPoint.usrCoords);
    252                 checkPoint = new Coords(Const.COORDS_BY_USER, c, this.board);
    253             }
    254
    255             dist = this.center.coords.distance(Const.COORDS_BY_USER, checkPoint);
    256             has = (Math.abs(dist - r) < prec);
    257
    258             /**
    259              * At that point we know that the user has touched the circle line.
    260              */
    261             if (has) {
    262                 angle = Geometry.rad(this.radiuspoint, this.center, checkPoint.usrCoords.slice(1));
    263                 alpha = 0.0;
    264                 beta = Geometry.rad(this.radiuspoint, this.center, this.anglepoint);
    265
    266                 if ((this.visProp.selection === 'minor' && beta > Math.PI) ||
    267                         (this.visProp.selection === 'major' && beta < Math.PI)) {
    268                     alpha = beta;
    269                     beta = 2 * Math.PI;
    270                 }
    271                 if (angle < alpha || angle > beta) {
    272                     has = false;
    273                 }
    274             }
    275
    276             return has;
    277         };
    278
    279         /**
    280          * Checks whether (x,y) is within the sector defined by the arc.
    281          * @memberOf Arc.prototype
    282          * @name hasPointSector
    283          * @function
    284          * @param {Number} x Coordinate in x direction, screen coordinates.
    285          * @param {Number} y Coordinate in y direction, screen coordinates.
    286          * @returns {Boolean} True if (x,y) is within the sector defined by the arc, False otherwise.
    287          */
    288         el.hasPointSector = function (x, y) {
    289             var angle, alpha, beta,
    290                 checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board),
    291                 r = this.Radius(),
    292                 dist = this.center.coords.distance(Const.COORDS_BY_USER, checkPoint),
    293                 has = (dist < r);
    294
    295             if (has) {
    296                 angle = Geometry.rad(this.radiuspoint, this.center, checkPoint.usrCoords.slice(1));
    297                 alpha = 0;
    298                 beta = Geometry.rad(this.radiuspoint, this.center, this.anglepoint);
    299
    300                 if ((this.visProp.selection === 'minor' && beta > Math.PI) ||
    301                         (this.visProp.selection === 'major' && beta < Math.PI)) {
    302                     alpha = beta;
    303                     beta = 2 * Math.PI;
    304                 }
    305                 if (angle < alpha || angle > beta) {
    306                     has = false;
    307                 }
    308             }
    309
    310             return has;
    311         };
    312
    313         // documented in geometry element
    314         el.getTextAnchor = function () {
    315             return this.center.coords;
    316         };
    317
    318         // documented in geometry element
    319         el.getLabelAnchor = function () {
    320             var coords, vecx, vecy, len,
    321                 angle = Geometry.rad(this.radiuspoint, this.center, this.anglepoint),
    322                 dx = 10 / this.board.unitX,
    323                 dy = 10 / this.board.unitY,
    324                 p2c = this.point2.coords.usrCoords,
    325                 pmc = this.center.coords.usrCoords,
    326                 bxminusax = p2c[1] - pmc[1],
    327                 byminusay = p2c[2] - pmc[2];
    328
    329             // If this is uncommented, the angle label can not be dragged
    330             //if (Type.exists(this.label)) {
    331             //    this.label.relativeCoords = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this.board);
    332             //}
    333
    334             if ((this.visProp.selection === 'minor' && angle > Math.PI) ||
    335                     (this.visProp.selection === 'major' && angle < Math.PI)) {
    336                 angle = -(2 * Math.PI - angle);
    337             }
    338
    339             coords = new Coords(Const.COORDS_BY_USER, [
    340                 pmc[1] + Math.cos(angle * 0.5) * bxminusax - Math.sin(angle * 0.5) * byminusay,
    341                 pmc[2] + Math.sin(angle * 0.5) * bxminusax + Math.cos(angle * 0.5) * byminusay
    342             ], this.board);
    343
    344             vecx = coords.usrCoords[1] - pmc[1];
    345             vecy = coords.usrCoords[2] - pmc[2];
    346
    347             len = Math.sqrt(vecx * vecx + vecy * vecy);
    348             vecx = vecx * (len + dx) / len;
    349             vecy = vecy * (len + dy) / len;
    350
    351             return new Coords(Const.COORDS_BY_USER, [pmc[1] + vecx, pmc[2] + vecy], this.board);
    352         };
    353
    354         // documentation in jxg.circle
    355         el.updateQuadraticform = Circle.Circle.prototype.updateQuadraticform;
    356
    357         // documentation in jxg.circle
    358         el.updateStdform = Circle.Circle.prototype.updateStdform;
    359
    360         el.methodMap = JXG.deepCopy(el.methodMap, {
    361             getRadius: 'getRadius',
    362             radius: 'Radius',
    363             center: 'center',
    364             radiuspoint: 'radiuspoint',
    365             anglepoint: 'anglepoint'
    366         });
    367
    368         el.prepareUpdate().update();
    369         return el;
    370     };
    371
    372     JXG.registerElement('arc', JXG.createArc);
    373
    374     /**
    375      * @class A semicircle is a special arc defined by two points. The arc hits both points.
    376      * @pseudo
    377      * @name Semicircle
    378      * @augments Arc
    379      * @constructor
    380      * @type Arc
    381      * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
    382      * @param {JXG.Point_JXG.Point} p1,p2 The result will be a composition of an arc drawn clockwise from <tt>p1</tt> and
    383      * <tt>p2</tt> and the midpoint of <tt>p1</tt> and <tt>p2</tt>.
    384      * @example
    385      * // Create an arc out of three free points
    386      * var p1 = board.create('point', [4.5, 2.0]);
    387      * var p2 = board.create('point', [1.0, 0.5]);
    388      *
    389      * var a = board.create('semicircle', [p1, p2]);
    390      * </pre><div id="5385d349-75d7-4078-b732-9ae808db1b0e" style="width: 300px; height: 300px;"></div>
    391      * <script type="text/javascript">
    392      * (function () {
    393      *   var board = JXG.JSXGraph.initBoard('5385d349-75d7-4078-b732-9ae808db1b0e', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
    394      *       p1 = board.create('point', [4.5, 2.0]),
    395      *       p2 = board.create('point', [1.0, 0.5]),
    396      *
    397      *       sc = board.create('semicircle', [p1, p2]);
    398      * })();
    399      * </script><pre>
    400      */
    401     JXG.createSemicircle = function (board, parents, attributes) {
    402         var el, mp, attr, points;
    403
    404         // we need 2 points
    405         points = Type.providePoints(board, parents, attributes, 'point');
    406         if (points === false || points.length !== 2) {
    407             throw new Error("JSXGraph: Can't create Semicircle with parent types '" +
    408                 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    409                 "\nPossible parent types: [point,point]");
    410         }
    411
    412         attr = Type.copyAttributes(attributes, board.options, 'semicircle', 'midpoint');
    413         mp = board.create('midpoint', points, attr);
    414         mp.dump = false;
    415
    416         attr = Type.copyAttributes(attributes, board.options, 'semicircle');
    417         el = board.create('arc', [mp, points[1], points[0]], attr);
    418         el.elType = 'semicircle';
    419         el.parents = [points[0].id, points[1].id];
    420         el.subs = {
    421             midpoint: mp
    422         };
    423
    424         /**
    425          * The midpoint of the two defining points.
    426          * @memberOf Semicircle.prototype
    427          * @name midpoint
    428          * @type Midpoint
    429          */
    430         el.midpoint = el.center = mp;
    431
    432         return el;
    433     };
    434
    435     JXG.registerElement('semicircle', JXG.createSemicircle);
    436
    437     /**
    438      * @class A circumcircle arc is an {@link Arc} defined by three points. All three points lie on the arc.
    439      * @pseudo
    440      * @name CircumcircleArc
    441      * @augments Arc
    442      * @constructor
    443      * @type Arc
    444      * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
    445      * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The result will be a composition of an arc of the circumcircle of
    446      * <tt>p1</tt>, <tt>p2</tt>, and <tt>p3</tt> and the midpoint of the circumcircle of the three points. The arc is drawn
    447      * counter-clockwise from <tt>p1</tt> over <tt>p2</tt> to <tt>p3</tt>.
    448      * @example
    449      * // Create a circum circle arc out of three free points
    450      * var p1 = board.create('point', [2.0, 2.0]);
    451      * var p2 = board.create('point', [1.0, 0.5]);
    452      * var p3 = board.create('point', [3.5, 1.0]);
    453      *
    454      * var a = board.create('arc', [p1, p2, p3]);
    455      * </pre><div id="87125fd4-823a-41c1-88ef-d1a1369504e3" style="width: 300px; height: 300px;"></div>
    456      * <script type="text/javascript">
    457      * (function () {
    458      *   var board = JXG.JSXGraph.initBoard('87125fd4-823a-41c1-88ef-d1a1369504e3', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
    459      *       p1 = board.create('point', [2.0, 2.0]),
    460      *       p2 = board.create('point', [1.0, 0.5]),
    461      *       p3 = board.create('point', [3.5, 1.0]),
    462      *
    463      *       cca = board.create('circumcirclearc', [p1, p2, p3]);
    464      * })();
    465      * </script><pre>
    466      */
    467     JXG.createCircumcircleArc = function (board, parents, attributes) {
    468         var el, mp, attr, points;
    469
    470         // We need three points
    471         points = Type.providePoints(board, parents, attributes, 'point');
    472         if (points === false || points.length !== 3) {
    473             throw new Error("JSXGraph: create Circumcircle Arc with parent types '" +
    474                 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'." +
    475                 "\nPossible parent types: [point,point,point]");
    476         }
    477
    478         attr = Type.copyAttributes(attributes, board.options, 'circumcirclearc', 'center');
    479         mp = board.create('circumcenter', points, attr);
    480         mp.dump = false;
    481
    482         attr = Type.copyAttributes(attributes, board.options, 'circumcirclearc');
    483         attr.usedirection = true;
    484         el = board.create('arc', [mp, points[0], points[2], points[1]], attr);
    485
    486         el.elType = 'circumcirclearc';
    487         el.parents = [points[0].id, points[1].id, points[2].id];
    488         el.subs = {
    489             center: mp
    490         };
    491
    492         /**
    493          * The midpoint of the circumcircle of the three points defining the circumcircle arc.
    494          * @memberOf CircumcircleArc.prototype
    495          * @name center
    496          * @type Circumcenter
    497          */
    498         el.center = mp;
    499
    500         return el;
    501     };
    502
    503     JXG.registerElement('circumcirclearc', JXG.createCircumcircleArc);
    504
    505     /**
    506      * @class A minor arc is a segment of the circumference of a circle having measure less than or equal to
    507      * 180 degrees (pi radians). It is defined by a center, one point that
    508      * defines the radius, and a third point that defines the angle of the arc.
    509      * @pseudo
    510      * @name MinorArc
    511      * @augments Curve
    512      * @constructor
    513      * @type JXG.Curve
    514      * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
    515      * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 . Minor arc is an arc of a circle around p1 having measure less than or equal to
    516      * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3.
    517      * @example
    518      * // Create an arc out of three free points
    519      * var p1 = board.create('point', [2.0, 2.0]);
    520      * var p2 = board.create('point', [1.0, 0.5]);
    521      * var p3 = board.create('point', [3.5, 1.0]);
    522      *
    523      * var a = board.create('arc', [p1, p2, p3]);
    524      * </pre><div id="64ba7ca2-8728-45f3-96e5-3c7a4414de2f" style="width: 300px; height: 300px;"></div>
    525      * <script type="text/javascript">
    526      * (function () {
    527      *   var board = JXG.JSXGraph.initBoard('64ba7ca2-8728-45f3-96e5-3c7a4414de2f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
    528      *       p1 = board.create('point', [2.0, 2.0]),
    529      *       p2 = board.create('point', [1.0, 0.5]),
    530      *       p3 = board.create('point', [3.5, 1.0]),
    531      *
    532      *       a = board.create('minorarc', [p1, p2, p3]);
    533      * })();
    534      * </script><pre>
    535      */
    536
    537     JXG.createMinorArc = function (board, parents, attributes) {
    538         attributes.selection = 'minor';
    539         return JXG.createArc(board, parents, attributes);
    540     };
    541
    542     JXG.registerElement('minorarc', JXG.createMinorArc);
    543
    544     /**
    545      * @class A major arc is a segment of the circumference of a circle having measure greater than or equal to
    546      * 180 degrees (pi radians). It is defined by a center, one point that
    547      * defines the radius, and a third point that defines the angle of the arc.
    548      * @pseudo
    549      * @name MajorArc
    550      * @augments Curve
    551      * @constructor
    552      * @type JXG.Curve
    553      * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
    554      * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 . Major arc is an arc of a circle around p1 having measure greater than or equal to
    555      * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3.
    556      * @example
    557      * // Create an arc out of three free points
    558      * var p1 = board.create('point', [2.0, 2.0]);
    559      * var p2 = board.create('point', [1.0, 0.5]);
    560      * var p3 = board.create('point', [3.5, 1.0]);
    561      *
    562      * var a = board.create('minorarc', [p1, p2, p3]);
    563      * </pre><div id="17a10d38-5629-40a4-b150-f41806edee9f" style="width: 300px; height: 300px;"></div>
    564      * <script type="text/javascript">
    565      * (function () {
    566      *   var board = JXG.JSXGraph.initBoard('17a10d38-5629-40a4-b150-f41806edee9f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
    567      *       p1 = board.create('point', [2.0, 2.0]),
    568      *       p2 = board.create('point', [1.0, 0.5]),
    569      *       p3 = board.create('point', [3.5, 1.0]),
    570      *
    571      *       a = board.create('majorarc', [p1, p2, p3]);
    572      * })();
    573      * </script><pre>
    574      */
    575     JXG.createMajorArc = function (board, parents, attributes) {
    576         attributes.selection = 'major';
    577         return JXG.createArc(board, parents, attributes);
    578     };
    579
    580     JXG.registerElement('majorarc', JXG.createMajorArc);
    581
    582     return {
    583         createArc: JXG.createArc,
    584         createSemicircle: JXG.createSemicircle,
    585         createCircumcircleArc: JXG.createCircumcircleArc,
    586         createMinorArc: JXG.createMinorArc,
    587         createMajorArc: JXG.createMajorArc
    588     };
    589 });
    590