      1 /*
      2     Copyright 2008-2011
      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: you can redistribute it and/or modify
     13     it under the terms of the GNU Lesser General Public License as published by
     14     the Free Software Foundation, either version 3 of the License, or
     15     (at your option) any later version.
     16
     17     JSXGraph is distributed in the hope that it will be useful,
     18     but WITHOUT ANY WARRANTY; without even the implied warranty of
     19     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     20     GNU Lesser General Public License for more details.
     21
     22     You should have received a copy of the GNU Lesser General Public License
     23     along with JSXGraph.  If not, see <http://www.gnu.org/licenses/>.
     24 */
     25
     26 /**
     27  * @fileoverview In this file the geometry object Arc is defined. Arc stores all
     28  * style and functional properties that are required to draw an arc on a board.
     29  */
     30
     31 /**
     32  * @class An arc is a segment of the circumference of a circle. It is defined by a center, one point that
     33  * defines the radius, and a third point that defines the angle of the arc.
     34  * @pseudo
     35  * @name Arc
     36  * @augments Curve
     37  * @constructor
     38  * @type JXG.Curve
     39  * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     40  * @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
     41  * counter-clockwise from p2 to p3.
     42  * @example
     43  * // Create an arc out of three free points
     44  * var p1 = board.create('point', [2.0, 2.0]);
     45  * var p2 = board.create('point', [1.0, 0.5]);
     46  * var p3 = board.create('point', [3.5, 1.0]);
     47  *
     48  * var a = board.create('arc', [p1, p2, p3]);
     49  * </pre><div id="114ef584-4a5e-4686-8392-c97501befb5b" style="width: 300px; height: 300px;"></div>
     50  * <script type="text/javascript">
     51  * (function () {
     52  *   var board = JXG.JSXGraph.initBoard('114ef584-4a5e-4686-8392-c97501befb5b', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
     53  *       p1 = board.create('point', [2.0, 2.0]),
     54  *       p2 = board.create('point', [1.0, 0.5]),
     55  *       p3 = board.create('point', [3.5, 1.0]),
     56  *
     57  *       a = board.create('arc', [p1, p2, p3]);
     58  * })();
     59  * </script><pre>
     60  */
     61 JXG.createArc = function(board, parents, attributes) {
     62     var el, attr, i;
     63
     64
     65     // this method is used to create circumccirclearcs, too. if a circumcirclearc is created we get a fourth
     66     // point, that's why we need to check that case, too.
     67     if(!(parents = JXG.checkParents('arc', parents, [
     68             [JXG.OBJECT_CLASS_POINT, JXG.OBJECT_CLASS_POINT, JXG.OBJECT_CLASS_POINT],
     69             [JXG.OBJECT_CLASS_POINT, JXG.OBJECT_CLASS_POINT, JXG.OBJECT_CLASS_POINT, JXG.OBJECT_CLASS_POINT]]))) {
     70         throw new Error("JSXGraph: Can't create Arc with parent types '" +
     71                         (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" +
     72                         (typeof parents[2]) + "'." +
     73                         "\nPossible parent types: [point,point,point]");
     74     }
     75
     76     attr = JXG.copyAttributes(attributes, board.options, 'arc');
     77     el = board.create('curve', [[0],[0]], attr);
     78
     79     el.elType = 'arc';
     80
     81     el.parents = [];
     82     for (i = 0; i < parents.length; i++) {
     83         if (parents[i].id) {
     84             el.parents.push(parents[i].id);
     85         }
     86     }
     87
     88     /**
     89      * documented in JXG.GeometryElement
     90      * @ignore
     91      */
     92     el.type = JXG.OBJECT_TYPE_ARC;
     93
     94     /**
     95      * Center of the arc.
     96      * @memberOf Arc.prototype
     97      * @name center
     98      * @type JXG.Point
     99      */
    100     el.center = JXG.getReference(board, parents[0]);
    101
    102     /**
    103      * Point defining the arc's radius.
    104      * @memberOf Arc.prototype
    105      * @name radiuspoint
    106      * @type JXG.Point
    107      */
    108     el.radiuspoint = JXG.getReference(board, parents[1]);
    109     el.point2 = el.radiuspoint;
    110
    111     /**
    112      * The point defining the arc's angle.
    113      * @memberOf Arc.prototype
    114      * @name anglepoint
    115      * @type JXG.Point
    116      */
    117     el.anglepoint = JXG.getReference(board, parents[2]);
    118     el.point3 = el.anglepoint;
    119
    120     // Add arc as child to defining points
    121     el.center.addChild(el);
    122     el.radiuspoint.addChild(el);
    123     el.anglepoint.addChild(el);
    124
    125     /**
    126      * TODO
    127      */
    128     el.useDirection = attr['usedirection'];      // useDirection is necessary for circumCircleArcs
    129
    130     // documented in JXG.Curve
    131     el.updateDataArray = function() {
    132         var A = this.radiuspoint,
    133             B = this.center,
    134             C = this.anglepoint,
    135             beta, co, si, matrix,
    136             phi = JXG.Math.Geometry.rad(A,B,C),
    137             i,
    138             n = Math.ceil(phi/Math.PI*90)+1,
    139             delta = phi/n,
    140             x = B.X(),
    141             y = B.Y(),
    142             v,
    143             det, p0c, p1c, p2c;
    144
    145         if (this.useDirection) {  // This is true for circumCircleArcs. In that case there is
    146                                   // a fourth parent element: [center, point1, point3, point2]
    147             p0c = parents[1].coords.usrCoords;
    148             p1c = parents[3].coords.usrCoords;
    149             p2c = parents[2].coords.usrCoords;
    150             det = (p0c[1]-p2c[1])*(p0c[2]-p1c[2]) - (p0c[2]-p2c[2])*(p0c[1]-p1c[1]);
    151             if(det < 0) {
    152                 this.radiuspoint = parents[1];
    153                 this.anglepoint = parents[2];
    154             } else {
    155                 this.radiuspoint = parents[2];
    156                 this.anglepoint = parents[1];
    157             }
    158         }
    159         this.dataX = [A.X()];
    160         this.dataY = [A.Y()];
    161
    162         for (beta=delta,i=1; i<=n; i++, beta+=delta) {
    163             co = Math.cos(beta);
    164             si = Math.sin(beta);
    165             matrix = [[1,            0,   0],
    166                       [x*(1-co)+y*si,co,-si],
    167                       [y*(1-co)-x*si,si, co]];
    168             v = JXG.Math.matVecMult(matrix,A.coords.usrCoords);
    169             this.dataX.push(v[1]/v[0]);
    170             this.dataY.push(v[2]/v[0]);
    171         }
    172
    173         this.updateStdform();
    174         this.updateQuadraticform();
    175     };
    176
    177     /**
    178      * Determines the arc's current radius. I.e. the distance between {@link Arc#center} and {@link Arc#radiuspoint}.
    179      * @memberOf Arc.prototype
    180      * @name Radius
    181      * @function
    182      * @returns {Number} The arc's radius
    183      */
    184     el.Radius = function() {
    185         return this.radiuspoint.Dist(this.center);
    186     };
    187
    188     /**
    189      * @deprecated Use {@link Arc#Radius}
    190      * @memberOf Arc.prototype
    191      * @name getRadius
    192      * @function
    193      * @returns {Number}
    194      */
    195     el.getRadius = function() {
    196         return this.Radius();
    197     };
    198
    199     // documented in geometry element
    200     el.hasPoint = function (x, y) {
    201         var prec = this.board.options.precision.hasPoint/(this.board.unitX),
    202             checkPoint = new JXG.Coords(JXG.COORDS_BY_SCREEN, [x,y], this.board),
    203             r = this.Radius(),
    204             dist = this.center.coords.distance(JXG.COORDS_BY_USER,checkPoint),
    205             has = (Math.abs(dist-r) < prec),
    206             angle;
    207
    208         if(has) {
    209             angle = JXG.Math.Geometry.rad(this.radiuspoint,this.center,checkPoint.usrCoords.slice(1));
    210             if (angle>JXG.Math.Geometry.rad(this.radiuspoint,this.center,this.anglepoint)) { has = false; }
    211         }
    212         return has;
    213     };
    214
    215     /**
    216      * Checks whether (x,y) is within the sector defined by the arc.
    217      * @memberOf Arc.prototype
    218      * @name hasPointSector
    219      * @function
    220      * @param {Number} x Coordinate in x direction, screen coordinates.
    221      * @param {Number} y Coordinate in y direction, screen coordinates.
    222      * @returns {Boolean} True if (x,y) is within the sector defined by the arc, False otherwise.
    223      */
    224     el.hasPointSector = function (x, y) {
    225         var checkPoint = new JXG.Coords(JXG.COORDS_BY_SCREEN, [x,y], this.board),
    226             r = this.Radius(),
    227             dist = this.center.coords.distance(JXG.COORDS_BY_USER,checkPoint),
    228             has = (dist<r),
    229             angle;
    230
    231         if(has) {
    232             angle = JXG.Math.Geometry.rad(this.radiuspoint,this.center,checkPoint.usrCoords.slice(1));
    233             if (angle>JXG.Math.Geometry.rad(this.radiuspoint,this.center,this.anglepoint)) { has = false; }
    234         }
    235         return has;
    236     };
    237
    238     // documented in geometry element
    239     el.getTextAnchor = function() {
    240         return this.center.coords;
    241     };
    242
    243     // documented in geometry element
    244     el.getLabelAnchor = function() {
    245         var angle = JXG.Math.Geometry.rad(this.radiuspoint, this.center, this.anglepoint),
    246             dx = 10/(this.board.unitX),
    247             dy = 10/(this.board.unitY),
    248             p2c = this.point2.coords.usrCoords,
    249             pmc = this.center.coords.usrCoords,
    250             bxminusax = p2c[1] - pmc[1],
    251             byminusay = p2c[2] - pmc[2],
    252             coords, vecx, vecy, len;
    253
    254         if(this.label.content != null) {
    255             this.label.content.relativeCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [0,0],this.board);
    256         }
    257
    258         coords = new JXG.Coords(JXG.COORDS_BY_USER,
    259                         [pmc[1]+ Math.cos(angle*0.5)*bxminusax - Math.sin(angle*0.5)*byminusay,
    260                         pmc[2]+ Math.sin(angle*0.5)*bxminusax + Math.cos(angle*0.5)*byminusay],
    261                         this.board);
    262
    263         vecx = coords.usrCoords[1] - pmc[1];
    264         vecy = coords.usrCoords[2] - pmc[2];
    265
    266         len = Math.sqrt(vecx*vecx+vecy*vecy);
    267         vecx = vecx*(len+dx)/len;
    268         vecy = vecy*(len+dy)/len;
    269
    270         return new JXG.Coords(JXG.COORDS_BY_USER, [pmc[1]+vecx,pmc[2]+vecy], this.board);
    271     };
    272
    273     /**
    274      * TODO description
    275      */
    276     el.updateQuadraticform = function () {
    277         var m = this.center,
    278             mX = m.X(), mY = m.Y(), r = this.Radius();
    279         this.quadraticform = [[mX*mX+mY*mY-r*r,-mX,-mY],
    280             [-mX,1,0],
    281             [-mY,0,1]
    282         ];
    283     };
    284
    285     /**
    286      * TODO description
    287      */
    288     el.updateStdform = function () {
    289         this.stdform[3] = 0.5;
    290         this.stdform[4] = this.Radius();
    291         this.stdform[1] = -this.center.coords.usrCoords[1];
    292         this.stdform[2] = -this.center.coords.usrCoords[2];
    293         this.normalize();
    294     };
    295
    296     el.prepareUpdate().update();
    297     return el;
    298 };
    299
    300 JXG.JSXGraph.registerElement('arc', JXG.createArc);
    301
    302 /**
    303  * @class A semicircle is a special arc defined by two points. The arc hits both points.
    304  * @pseudo
    305  * @name Semicircle
    306  * @augments Arc
    307  * @constructor
    308  * @type Arc
    309  * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
    310  * @param {JXG.Point_JXG.Point} p1,p2 The result will be a composition of an arc drawn clockwise from <tt>p1</tt> and
    311  * <tt>p2</tt> and the midpoint of <tt>p1</tt> and <tt>p2</tt>.
    312  * @example
    313  * // Create an arc out of three free points
    314  * var p1 = board.create('point', [4.5, 2.0]);
    315  * var p2 = board.create('point', [1.0, 0.5]);
    316  *
    317  * var a = board.create('semicircle', [p1, p2]);
    318  * </pre><div id="5385d349-75d7-4078-b732-9ae808db1b0e" style="width: 300px; height: 300px;"></div>
    319  * <script type="text/javascript">
    320  * (function () {
    321  *   var board = JXG.JSXGraph.initBoard('5385d349-75d7-4078-b732-9ae808db1b0e', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
    322  *       p1 = board.create('point', [4.5, 2.0]),
    323  *       p2 = board.create('point', [1.0, 0.5]),
    324  *
    325  *       sc = board.create('semicircle', [p1, p2]);
    326  * })();
    327  * </script><pre>
    328  */
    329 JXG.createSemicircle = function(board, parents, attributes) {
    330     var el, mp, attr;
    331
    332
    333     // we need 2 points
    334     if ( (JXG.isPoint(parents[0])) && (JXG.isPoint(parents[1])) ) {
    335
    336         attr = JXG.copyAttributes(attributes, board.options, 'semicircle', 'midpoint');
    337         mp = board.create('midpoint', [parents[0], parents[1]], attr);
    338
    339         mp.dump = false;
    340
    341         attr = JXG.copyAttributes(attributes, board.options, 'semicircle');
    342         el = board.create('arc', [mp, parents[1], parents[0]], attr);
    343
    344         el.elType = 'semicircle';
    345         el.parents = [parents[0].id, parents[1].id];
    346         el.subs = {
    347             midpoint: mp
    348         };
    349
    350         /**
    351          * The midpoint of the two defining points.
    352          * @memberOf Semicircle.prototype
    353          * @name midpoint
    354          * @type Midpoint
    355          */
    356         el.midpoint = mp;
    357     } else
    358         throw new Error("JSXGraph: Can't create Semicircle with parent types '" +
    359                         (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    360                         "\nPossible parent types: [point,point]");
    361
    362     return el;
    363 };
    364
    365 JXG.JSXGraph.registerElement('semicircle', JXG.createSemicircle);
    366
    367 /**
    368  * @class A circumcircle arc is an {@link Arc} defined by three points. All three points lie on the arc.
    369  * @pseudo
    370  * @name CircumcircleArc
    371  * @augments Arc
    372  * @constructor
    373  * @type Arc
    374  * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
    375  * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The result will be a composition of an arc of the circumcircle of
    376  * <tt>p1</tt>, <tt>p2</tt>, and <tt>p3</tt> and the midpoint of the circumcircle of the three points. The arc is drawn
    377  * counter-clockwise from <tt>p1</tt> over <tt>p2</tt> to <tt>p3</tt>.
    378  * @example
    379  * // Create a circum circle arc out of three free points
    380  * var p1 = board.create('point', [2.0, 2.0]);
    381  * var p2 = board.create('point', [1.0, 0.5]);
    382  * var p3 = board.create('point', [3.5, 1.0]);
    383  *
    384  * var a = board.create('arc', [p1, p2, p3]);
    385  * </pre><div id="87125fd4-823a-41c1-88ef-d1a1369504e3" style="width: 300px; height: 300px;"></div>
    386  * <script type="text/javascript">
    387  * (function () {
    388  *   var board = JXG.JSXGraph.initBoard('87125fd4-823a-41c1-88ef-d1a1369504e3', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
    389  *       p1 = board.create('point', [2.0, 2.0]),
    390  *       p2 = board.create('point', [1.0, 0.5]),
    391  *       p3 = board.create('point', [3.5, 1.0]),
    392  *
    393  *       cca = board.create('circumcirclearc', [p1, p2, p3]);
    394  * })();
    395  * </script><pre>
    396  */
    397 JXG.createCircumcircleArc = function(board, parents, attributes) {
    398     var el, mp, attr;
    399
    400     // We need three points
    401     if ( (JXG.isPoint(parents[0])) && (JXG.isPoint(parents[1])) && (JXG.isPoint(parents[2]))) {
    402
    403         attr = JXG.copyAttributes(attributes, board.options, 'circumcirclearc', 'center');
    404         mp = board.create('circumcenter',[parents[0], parents[1], parents[2]], attr);
    405
    406         mp.dump = false;
    407
    408         attr = JXG.copyAttributes(attributes, board.options, 'circumcirclearc');
    409         attr.usedirection = true;
    410         el = board.create('arc', [mp, parents[0], parents[2], parents[1]], attr);
    411
    412         el.elType = 'circumcirclearc';
    413         el.parents = [parents[0].id, parents[1].id, parents[2].id];
    414         el.subs = {
    415             center: mp
    416         };
    417
    418         /**
    419          * The midpoint of the circumcircle of the three points defining the circumcircle arc.
    420          * @memberOf CircumcircleArc.prototype
    421          * @name center
    422          * @type Circumcenter
    423          */
    424         el.center = mp;
    425     } else
    426         throw new Error("JSXGraph: create Circumcircle Arc with parent types '" +
    427                         (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'." +
    428                         "\nPossible parent types: [point,point,point]");
    429
    430     return el;
    431 };
    432
    433 JXG.JSXGraph.registerElement('circumcirclearc', JXG.createCircumcircleArc);
    434