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