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