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