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, console: true, window: true*/
 34 /*jslint nomen: true, plusplus: true*/
 35 
 36 /* depends:
 37  jxg
 38  options
 39  math/math
 40  math/geometry
 41  math/numerics
 42  base/coords
 43  base/constants
 44  base/element
 45  parser/geonext
 46  utils/type
 47   elements:
 48    transform
 49  */
 50 
 51 /**
 52  * @fileoverview The geometry object Point is defined in this file. Point stores all
 53  * style and functional properties that are required to draw and move a point on
 54  * a board.
 55  */
 56 
 57 define([
 58     'jxg', 'options', 'math/math', 'math/geometry', 'math/numerics', 'base/coords', 'base/constants', 'base/element',
 59     'parser/geonext', 'utils/type', 'base/transformation'
 60 ], function (JXG, Options, Mat, Geometry, Numerics, Coords, Const, GeometryElement, GeonextParser, Type, Transform) {
 61 
 62     "use strict";
 63 
 64     /**
 65      * A point is the basic geometric element. Based on points lines and circles can be constructed which can be intersected
 66      * which in turn are points again which can be used to construct new lines, circles, polygons, etc. This class holds methods for
 67      * all kind of points like free points, gliders, and intersection points.
 68      * @class Creates a new point object. Do not use this constructor to create a point. Use {@link JXG.Board#create} with
 69      * type {@link Point}, {@link Glider}, or {@link Intersection} instead.
 70      * @augments JXG.GeometryElement
 71      * @param {string|JXG.Board} board The board the new point is drawn on.
 72      * @param {Array} coordinates An array with the affine user coordinates of the point.
 73      * @param {Object} attributes An object containing visual properties like in {@link JXG.Options#point} and
 74      * {@link JXG.Options#elements}, and optional a name and a id.
 75      * @see JXG.Board#generateName
 76      * @see JXG.Board#addPoint
 77      */
 78     JXG.Point = function (board, coordinates, attributes) {
 79         this.constructor(board, attributes, Const.OBJECT_TYPE_POINT, Const.OBJECT_CLASS_POINT);
 80 
 81         if (!Type.exists(coordinates)) {
 82             coordinates = [0, 0];
 83         }
 84 
 85         /**
 86          * Coordinates of the point.
 87          * @type JXG.Coords
 88          * @private
 89          */
 90         this.coords = new Coords(Const.COORDS_BY_USER, coordinates, this.board);
 91         this.initialCoords = new Coords(Const.COORDS_BY_USER, coordinates, this.board);
 92 
 93         /**
 94          * Relative position on a line if point is a glider on a line.
 95          * @type Number
 96          * @private
 97          */
 98         this.position = null;
 99 
100         /**
101          * Determines whether the point slides on a polygon if point is a glider.
102          * @type boolean
103          * @default false
104          * @private
105          */
106         this.onPolygon = false;
107 
108         /**
109          * When used as a glider this member stores the object, where to glide on. To set the object to glide on use the method
110          * {@link JXG.Point#makeGlider} and DO NOT set this property directly as it will break the dependency tree.
111          * @type JXG.GeometryElement
112          * @name Glider#slideObject
113          */
114         this.slideObject = null;
115 
116         /**
117          * List of elements the point is bound to, i.e. the point glides on.
118          * Only the last entry is active.
119          * Use {@link JXG.Point#popSlideObject} to remove the currently active slideObject.
120          */
121         this.slideObjects = [];
122 
123         /**
124          * A {@link JXG.Point#updateGlider} call is usually followed by a general {@link JXG.Board#update} which calls
125          * {@link JXG.Point#updateGliderFromParent}. To prevent double updates, {@link JXG.Point#needsUpdateFromParent}
126          * is set to false in updateGlider() and reset to true in the following call to
127          * {@link JXG.Point#updateGliderFromParent}
128          * @type {Boolean}
129          */
130         this.needsUpdateFromParent = true;
131 
132         this.Xjc = null;
133         this.Yjc = null;
134 
135         // documented in GeometryElement
136         this.methodMap = Type.deepCopy(this.methodMap, {
137             move: 'moveTo',
138             moveTo: 'moveTo',
139             moveAlong: 'moveAlong',
140             visit: 'visit',
141             glide: 'makeGlider',
142             makeGlider: 'makeGlider',
143             X: 'X',
144             Y: 'Y',
145             free: 'free',
146             setPosition: 'setGliderPosition',
147             setGliderPosition: 'setGliderPosition',
148             addConstraint: 'addConstraint',
149             dist: 'Dist',
150             onPolygon: 'onPolygon'
151         });
152 
153         /**
154          * Stores the groups of this point in an array of Group.
155          * @type array
156          * @see JXG.Group
157          * @private
158          */
159         this.group = [];
160 
161         this.elType = 'point';
162 
163         /* Register point at board. */
164         this.id = this.board.setId(this, 'P');
165         this.board.renderer.drawPoint(this);
166         this.board.finalizeAdding(this);
167 
168         this.createLabel();
169     };
170 
171     /**
172      * Inherits here from {@link JXG.GeometryElement}.
173      */
174     JXG.Point.prototype = new GeometryElement();
175 
176 
177     JXG.extend(JXG.Point.prototype, /** @lends JXG.Point.prototype */ {
178         /**
179          * Checks whether (x,y) is near the point.
180          * @param {Number} x Coordinate in x direction, screen coordinates.
181          * @param {Number} y Coordinate in y direction, screen coordinates.
182          * @returns {Boolean} True if (x,y) is near the point, False otherwise.
183          * @private
184          */
185         hasPoint: function (x, y) {
186             var coordsScr = this.coords.scrCoords, r;
187             r = parseFloat(this.visProp.size) + parseFloat(this.visProp.strokewidth) * 0.5;
188             if (r < this.board.options.precision.hasPoint) {
189                 r = this.board.options.precision.hasPoint;
190             }
191 
192             return ((Math.abs(coordsScr[1] - x) < r + 2) && (Math.abs(coordsScr[2] - y)) < r + 2);
193         },
194 
195         /**
196          * Dummy function for unconstrained points or gliders.
197          * @private
198          */
199         updateConstraint: function () {
200             return this;
201         },
202 
203         /**
204          * Updates the position of the point.
205          */
206         update: function (fromParent) {
207             if (!this.needsUpdate) {
208                 return this;
209             }
210 
211             if (!Type.exists(fromParent)) {
212                 fromParent = false;
213             }
214 
215             /*
216              * We need to calculate the new coordinates no matter of the points visibility because
217              * a child could be visible and depend on the coordinates of the point (e.g. perpendicular).
218              *
219              * Check if point is a glider and calculate new coords in dependency of this.slideObject.
220              * This function is called with fromParent==true for example if
221              * the defining elements of the line or circle have been changed.
222              */
223             if (this.type === Const.OBJECT_TYPE_GLIDER) {
224                 if (fromParent) {
225                     this.updateGliderFromParent();
226                 } else {
227                     this.updateGlider();
228                 }
229             }
230 
231             /**
232              * If point is a calculated point, call updateConstraint() to calculate new coords.
233              * The second test is for dynamic axes.
234              */
235             if (this.type === Const.OBJECT_TYPE_CAS || this.type === Const.OBJECT_TYPE_INTERSECTION || this.type === Const.OBJECT_TYPE_AXISPOINT) {
236                 this.updateConstraint();
237             }
238 
239             this.updateTransform();
240 
241             if (this.visProp.trace) {
242                 this.cloneToBackground(true);
243             }
244 
245             return this;
246         },
247 
248         /**
249          * Update of glider in case of dragging the glider or setting the postion of the glider.
250          * The relative position of the glider has to be updated.
251          * If the second point is an ideal point, then -1 < this.position < 1,
252          * this.position==+/-1 equals point2, this.position==0 equals point1
253          *
254          * If the first point is an ideal point, then 0 < this.position < 2
255          * this.position==0  or 2 equals point1, this.position==1 equals point2
256          *
257          * @private
258          */
259         updateGlider: function () {
260             var i, p1c, p2c, d, v, poly, cc, pos, sgn,
261                 alpha, beta, angle,
262                 cp, c, invMat, newCoords, newPos,
263                 doRound = false,
264                 slide = this.slideObject;
265 
266             this.needsUpdateFromParent = false;
267 
268             if (slide.elementClass === Const.OBJECT_CLASS_CIRCLE) {
269                 //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToCircle(this, slide, this.board).usrCoords, false);
270                 newCoords = Geometry.projectPointToCircle(this, slide, this.board);
271                 newPos = Geometry.rad([slide.center.X() + 1.0, slide.center.Y()], slide.center, this);
272             } else if (slide.elementClass === Const.OBJECT_CLASS_LINE) {
273                 /*
274                  * onPolygon==true: the point is a slider on a segment and this segment is one of the
275                  * "borders" of a polygon.
276                  * This is a GEONExT feature.
277                  */
278                 if (this.onPolygon) {
279                     p1c = slide.point1.coords.usrCoords;
280                     p2c = slide.point2.coords.usrCoords;
281                     i = 1;
282                     d = p2c[i] - p1c[i];
283 
284                     if (Math.abs(d) < Mat.eps) {
285                         i = 2;
286                         d = p2c[i] - p1c[i];
287                     }
288 
289                     cc = Geometry.projectPointToLine(this, slide, this.board);
290                     pos = (cc.usrCoords[i] - p1c[i]) / d;
291                     poly = slide.parentPolygon;
292 
293                     if (pos < 0) {
294                         for (i = 0; i < poly.borders.length; i++) {
295                             if (slide === poly.borders[i]) {
296                                 slide = poly.borders[(i - 1 + poly.borders.length) % poly.borders.length];
297                                 break;
298                             }
299                         }
300                     } else if (pos > 1.0) {
301                         for (i = 0; i < poly.borders.length; i++) {
302                             if (slide === poly.borders[i]) {
303                                 slide = poly.borders[(i + 1 + poly.borders.length) % poly.borders.length];
304                                 break;
305                             }
306                         }
307                     }
308 
309                     // If the slide object has changed, save the change to the glider.
310                     if (slide.id !== this.slideObject.id) {
311                         this.slideObject = slide;
312                     }
313                 }
314 
315                 p1c = slide.point1.coords;
316                 p2c = slide.point2.coords;
317 
318                 // Distance between the two defining points
319                 d = p1c.distance(Const.COORDS_BY_USER, p2c);
320 
321                 // The defining points are identical
322                 if (d < Mat.eps) {
323                     //this.coords.setCoordinates(Const.COORDS_BY_USER, p1c);
324                     newCoords = p1c;
325                     doRound = true;
326                     newPos = 0.0;
327                 } else {
328                     //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToLine(this, slide, this.board).usrCoords, false);
329                     newCoords = Geometry.projectPointToLine(this, slide, this.board);
330                     p1c = p1c.usrCoords.slice(0);
331                     p2c = p2c.usrCoords.slice(0);
332 
333                     // The second point is an ideal point
334                     if (Math.abs(p2c[0]) < Mat.eps) {
335                         i = 1;
336                         d = p2c[i];
337 
338                         if (Math.abs(d) < Mat.eps) {
339                             i = 2;
340                             d = p2c[i];
341                         }
342 
343                         d = (newCoords.usrCoords[i] - p1c[i]) / d;
344                         sgn = (d >= 0) ? 1 : -1;
345                         d = Math.abs(d);
346                         newPos = sgn * d / (d + 1);
347 
348                     // The first point is an ideal point
349                     } else if (Math.abs(p1c[0]) < Mat.eps) {
350                         i = 1;
351                         d = p1c[i];
352 
353                         if (Math.abs(d) < Mat.eps) {
354                             i = 2;
355                             d = p1c[i];
356                         }
357 
358                         d = (newCoords.usrCoords[i] - p2c[i]) / d;
359 
360                         // 1.0 - d/(1-d);
361                         if (d < 0.0) {
362                             newPos = (1 - 2.0 * d) / (1.0 - d);
363                         } else {
364                             newPos = 1 / (d + 1);
365                         }
366                     } else {
367                         i = 1;
368                         d = p2c[i] - p1c[i];
369 
370                         if (Math.abs(d) < Mat.eps) {
371                             i = 2;
372                             d = p2c[i] - p1c[i];
373                         }
374                         newPos = (newCoords.usrCoords[i] - p1c[i]) / d;
375                     }
376                 }
377 
378                 // Snap the glider point of the slider into its appropiate position
379                 // First, recalculate the new value of this.position
380                 // Second, call update(fromParent==true) to make the positioning snappier.
381                 if (this.visProp.snapwidth > 0.0 && Math.abs(this._smax - this._smin) >= Mat.eps) {
382                     newPos = Math.max(Math.min(newPos, 1), 0);
383 
384                     v = newPos * (this._smax - this._smin) + this._smin;
385                     v = Math.round(v / this.visProp.snapwidth) * this.visProp.snapwidth;
386                     newPos = (v - this._smin) / (this._smax - this._smin);
387                     this.update(true);
388                 }
389 
390                 p1c = slide.point1.coords;
391                 if (!slide.visProp.straightfirst && Math.abs(p1c.usrCoords[0]) > Mat.eps && newPos < 0) {
392                     //this.coords.setCoordinates(Const.COORDS_BY_USER, p1c);
393                     newCoords = p1c;
394                     doRound = true;
395                     newPos = 0;
396                 }
397 
398                 p2c = slide.point2.coords;
399                 if (!slide.visProp.straightlast && Math.abs(p2c.usrCoords[0]) > Mat.eps && newPos > 1) {
400                     //this.coords.setCoordinates(Const.COORDS_BY_USER, p2c);
401                     newCoords = p2c;
402                     doRound = true;
403                     newPos = 1;
404                 }
405             } else if (slide.type === Const.OBJECT_TYPE_TURTLE) {
406                 // In case, the point is a constrained glider.
407                 // side-effect: this.position is overwritten
408                 this.updateConstraint();
409                 //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToTurtle(this, slide, this.board).usrCoords, false);
410                 newCoords = Geometry.projectPointToTurtle(this, slide, this.board);
411                 newPos = this.position;     // save position for the overwriting below
412             } else if (slide.elementClass === Const.OBJECT_CLASS_CURVE) {
413                 if ((slide.type === Const.OBJECT_TYPE_ARC ||
414                         slide.type === Const.OBJECT_TYPE_SECTOR)) {
415                     //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToCircle(this, slide, this.board).usrCoords, false);
416                     newCoords = Geometry.projectPointToCircle(this, slide, this.board);
417 
418                     angle = Geometry.rad(slide.radiuspoint, slide.center, this);
419                     alpha = 0.0;
420                     beta = Geometry.rad(slide.radiuspoint, slide.center, slide.anglepoint);
421                     newPos = angle;
422 
423                     if ((slide.visProp.type === 'minor' && beta > Math.PI) ||
424                             (slide.visProp.type === 'major' && beta < Math.PI)) {
425                         alpha = beta;
426                         beta = 2 * Math.PI;
427                     }
428 
429                     // Correct the position if we are outside of the sector/arc
430                     if (angle < alpha || angle > beta) {
431                         newPos = beta;
432 
433                         if ((angle < alpha && angle > alpha * 0.5) || (angle > beta && angle > beta * 0.5 + Math.PI)) {
434                             newPos = alpha;
435                         }
436                         this.updateGliderFromParent();
437                     }
438 
439                 } else {
440                     // In case, the point is a constrained glider.
441                     this.updateConstraint();
442 
443                     if (slide.transformations.length > 0) {
444                         slide.updateTransformMatrix();
445                         invMat = Mat.inverse(slide.transformMat);
446                         c = Mat.matVecMult(invMat, this.coords.usrCoords);
447 
448                         cp = (new Coords(Const.COORDS_BY_USER, c, this.board)).usrCoords;
449                         c = Geometry.projectCoordsToCurve(cp[1], cp[2], this.position || 0, slide, this.board);
450 
451                         newCoords = c[0];
452                         newPos = c[1];
453                     } else {
454                         // side-effect: this.position is overwritten
455                         //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToCurve(this, slide, this.board).usrCoords, false);
456                         newCoords = Geometry.projectPointToCurve(this, slide, this.board);
457                         newPos = this.position; // save position for the overwriting below
458                     }
459                 }
460             } else if (slide.elementClass === Const.OBJECT_CLASS_POINT) {
461                 //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToPoint(this, slide, this.board).usrCoords, false);
462                 newCoords = Geometry.projectPointToPoint(this, slide, this.board);
463                 newPos = this.position; // save position for the overwriting below
464             }
465 
466             this.coords.setCoordinates(Const.COORDS_BY_USER, newCoords.usrCoords, doRound);
467             this.position = newPos;
468         },
469 
470         /**
471          * Update of a glider in case a parent element has been updated. That means the
472          * relative position of the glider stays the same.
473          * @private
474          */
475         updateGliderFromParent: function () {
476             var p1c, p2c, r, lbda, c,
477                 slide = this.slideObject, alpha;
478 
479             if (!this.needsUpdateFromParent) {
480                 this.needsUpdateFromParent = true;
481                 return;
482             }
483 
484             if (slide.elementClass === Const.OBJECT_CLASS_CIRCLE) {
485                 r = slide.Radius();
486                 c = [
487                     slide.center.X() + r * Math.cos(this.position),
488                     slide.center.Y() + r * Math.sin(this.position)
489                 ];
490             } else if (slide.elementClass === Const.OBJECT_CLASS_LINE) {
491                 p1c = slide.point1.coords.usrCoords;
492                 p2c = slide.point2.coords.usrCoords;
493 
494                 // The second point is an ideal point
495                 if (Math.abs(p2c[0]) < Mat.eps) {
496                     lbda = Math.min(Math.abs(this.position), 1 - Mat.eps);
497                     lbda /= (1.0 - lbda);
498 
499                     if (this.position < 0) {
500                         lbda = -lbda;
501                     }
502 
503                     c = [
504                         p1c[0] + lbda * p2c[0],
505                         p1c[1] + lbda * p2c[1],
506                         p1c[2] + lbda * p2c[2]
507                     ];
508                 // The first point is an ideal point
509                 } else if (Math.abs(p1c[0]) < Mat.eps) {
510                     lbda = Math.max(this.position, Mat.eps);
511                     lbda = Math.min(lbda, 2 - Mat.eps);
512 
513                     if (lbda > 1) {
514                         lbda = (lbda - 1) / (lbda - 2);
515                     } else {
516                         lbda = (1 - lbda) / lbda;
517                     }
518 
519                     c = [
520                         p2c[0] + lbda * p1c[0],
521                         p2c[1] + lbda * p1c[1],
522                         p2c[2] + lbda * p1c[2]
523                     ];
524                 } else {
525                     lbda = this.position;
526                     c = [
527                         p1c[0] + lbda * (p2c[0] - p1c[0]),
528                         p1c[1] + lbda * (p2c[1] - p1c[1]),
529                         p1c[2] + lbda * (p2c[2] - p1c[2])
530                     ];
531                 }
532             } else if (slide.type === Const.OBJECT_TYPE_TURTLE) {
533                 this.coords.setCoordinates(Const.COORDS_BY_USER, [slide.Z(this.position), slide.X(this.position), slide.Y(this.position)]);
534                 // In case, the point is a constrained glider.
535                 // side-effect: this.position is overwritten:
536                 this.updateConstraint();
537                 c  = Geometry.projectPointToTurtle(this, slide, this.board).usrCoords;
538             } else if (slide.elementClass === Const.OBJECT_CLASS_CURVE) {
539                 this.coords.setCoordinates(Const.COORDS_BY_USER, [slide.Z(this.position), slide.X(this.position), slide.Y(this.position)]);
540 
541                 if (slide.type === Const.OBJECT_TYPE_ARC || slide.type === Const.OBJECT_TYPE_SECTOR) {
542                     alpha = Geometry.rad([slide.center.X() + 1, slide.center.Y()], slide.center, slide.radiuspoint);
543                     r = slide.Radius();
544                     c = [
545                         slide.center.X() + r * Math.cos(this.position + alpha),
546                         slide.center.Y() + r * Math.sin(this.position + alpha)
547                     ];
548                 } else {
549                     // In case, the point is a constrained glider.
550                     // side-effect: this.position is overwritten
551                     this.updateConstraint();
552                     c = Geometry.projectPointToCurve(this, slide, this.board).usrCoords;
553                 }
554 
555             } else if (slide.elementClass === Const.OBJECT_CLASS_POINT) {
556                 c = Geometry.projectPointToPoint(this, slide, this.board).usrCoords;
557             }
558 
559             this.coords.setCoordinates(Const.COORDS_BY_USER, c, false);
560         },
561 
562         /**
563          * Calls the renderer to update the drawing.
564          * @private
565          */
566         updateRenderer: function () {
567             var wasReal;
568 
569             if (!this.needsUpdate) {
570                 return this;
571             }
572 
573             /* Call the renderer only if point is visible. */
574             if (this.visProp.visible && this.visProp.size > 0) {
575                 wasReal = this.isReal;
576                 this.isReal = (!isNaN(this.coords.usrCoords[1] + this.coords.usrCoords[2]));
577                 //Homogeneous coords: ideal point
578                 this.isReal = (Math.abs(this.coords.usrCoords[0]) > Mat.eps) ? this.isReal : false;
579 
580                 if (this.isReal) {
581                     if (wasReal !== this.isReal) {
582                         this.board.renderer.show(this);
583 
584                         if (this.hasLabel && this.label.visProp.visible) {
585                             this.board.renderer.show(this.label);
586                         }
587                     }
588                     this.board.renderer.updatePoint(this);
589                 } else {
590                     if (wasReal !== this.isReal) {
591                         this.board.renderer.hide(this);
592 
593                         if (this.hasLabel && this.label.visProp.visible) {
594                             this.board.renderer.hide(this.label);
595                         }
596                     }
597                 }
598             }
599 
600             /* Update the label if visible. */
601             if (this.hasLabel && this.visProp.visible && this.label && this.label.visProp.visible && this.isReal) {
602                 this.label.update();
603                 this.board.renderer.updateText(this.label);
604             }
605 
606             this.needsUpdate = false;
607             return this;
608         },
609 
610         /**
611          * Getter method for x, this is used by for CAS-points to access point coordinates.
612          * @returns {Number} User coordinate of point in x direction.
613          */
614         X: function () {
615             return this.coords.usrCoords[1];
616         },
617 
618         /**
619          * Getter method for y, this is used by CAS-points to access point coordinates.
620          * @returns {Number} User coordinate of point in y direction.
621          */
622         Y: function () {
623             return this.coords.usrCoords[2];
624         },
625 
626         /**
627          * Getter method for z, this is used by CAS-points to access point coordinates.
628          * @returns {Number} User coordinate of point in z direction.
629          */
630         Z: function () {
631             return this.coords.usrCoords[0];
632         },
633 
634         /**
635          * New evaluation of the function term.
636          * This is required for CAS-points: Their XTerm() method is overwritten in {@link #addConstraint}
637          * @returns {Number} User coordinate of point in x direction.
638          * @private
639          */
640         XEval: function () {
641             return this.coords.usrCoords[1];
642         },
643 
644         /**
645          * New evaluation of the function term.
646          * This is required for CAS-points: Their YTerm() method is overwritten in {@link #addConstraint}
647          * @returns {Number} User coordinate of point in y direction.
648          * @private
649          */
650         YEval: function () {
651             return this.coords.usrCoords[2];
652         },
653 
654         /**
655          * New evaluation of the function term.
656          * This is required for CAS-points: Their ZTerm() method is overwritten in {@link #addConstraint}
657          * @returns {Number} User coordinate of point in z direction.
658          * @private
659          */
660         ZEval: function () {
661             return this.coords.usrCoords[0];
662         },
663 
664         // documented in JXG.GeometryElement
665         bounds: function () {
666             return this.coords.usrCoords.slice(1).concat(this.coords.usrCoords.slice(1));
667         },
668 
669         /**
670          * Getter method for the distance to a second point, this is required for CAS-elements.
671          * Here, function inlining seems to be worthwile  (for plotting).
672          * @param {JXG.Point} point2 The point to which the distance shall be calculated.
673          * @returns {Number} Distance in user coordinate to the given point
674          */
675         Dist: function (point2) {
676             var sum, f,
677                 r = NaN,
678                 c = point2.coords.usrCoords,
679                 ucr = this.coords.usrCoords;
680 
681             if (this.isReal && point2.isReal) {
682                 f = ucr[0] - c[0];
683                 sum = f * f;
684                 f = ucr[1] - c[1];
685                 sum += f * f;
686                 f = ucr[2] - c[2];
687                 sum += f * f;
688 
689                 r = Math.sqrt(sum);
690             }
691 
692             return r;
693         },
694 
695         snapToGrid: function () {
696             return this.handleSnapToGrid();
697         },
698 
699         /**
700          * Move a point to its nearest grid point.
701          * The function uses the coords object of the point as
702          * its actual position.
703          **/
704         handleSnapToGrid: function () {
705             var x, y,
706                 sX = this.visProp.snapsizex,
707                 sY = this.visProp.snapsizey;
708 
709             if (this.visProp.snaptogrid) {
710                 x = this.coords.usrCoords[1];
711                 y = this.coords.usrCoords[2];
712 
713                 if (sX <= 0 && this.board.defaultAxes && this.board.defaultAxes.x.defaultTicks) {
714                     sX = this.board.defaultAxes.x.defaultTicks.ticksDelta * (this.board.defaultAxes.x.defaultTicks.visProp.minorticks + 1);
715                 }
716 
717                 if (sY <= 0 && this.board.defaultAxes && this.board.defaultAxes.y.defaultTicks) {
718                     sY = this.board.defaultAxes.y.defaultTicks.ticksDelta * (this.board.defaultAxes.y.defaultTicks.visProp.minorticks + 1);
719                 }
720 
721                 // if no valid snap sizes are available, don't change the coords.
722                 if (sX > 0 && sY > 0) {
723                     this.coords.setCoordinates(Const.COORDS_BY_USER, [Math.round(x / sX) * sX, Math.round(y / sY) * sY]);
724                 }
725             }
726             return this;
727         },
728 
729         /**
730          * Let a point snap to the nearest point in distance of
731          * {@link JXG.Point#attractorDistance}.
732          * The function uses the coords object of the point as
733          * its actual position.
734          **/
735         handleSnapToPoints: function () {
736             var i, pEl, pCoords,
737                 d = 0,
738                 dMax = Infinity,
739                 c = null;
740 
741             if (this.visProp.snaptopoints) {
742                 for (i = 0; i < this.board.objectsList.length; i++) {
743                     pEl = this.board.objectsList[i];
744 
745                     if (pEl.elementClass === Const.OBJECT_CLASS_POINT && pEl !== this && pEl.visProp.visible) {
746                         pCoords = Geometry.projectPointToPoint(this, pEl, this.board);
747                         if (this.visProp.attractorunit === 'screen') {
748                             d = pCoords.distance(Const.COORDS_BY_SCREEN, this.coords);
749                         } else {
750                             d = pCoords.distance(Const.COORDS_BY_USER, this.coords);
751                         }
752 
753                         if (d < this.visProp.attractordistance && d < dMax) {
754                             dMax = d;
755                             c = pCoords;
756                         }
757                     }
758                 }
759 
760                 if (c !== null) {
761                     this.coords.setCoordinates(Const.COORDS_BY_USER, c.usrCoords);
762                 }
763             }
764 
765             return this;
766         },
767 
768         /**
769          * A point can change its type from free point to glider
770          * and vice versa. If it is given an array of attractor elements
771          * (attribute attractors) and the attribute attractorDistance
772          * then the pint will be made a glider if it less than attractorDistance
773          * apart from one of its attractor elements.
774          * If attractorDistance is equal to zero, the point stays in its
775          * current form.
776          **/
777         handleAttractors: function () {
778             var i, el, projCoords,
779                 d = 0.0,
780                 len = this.visProp.attractors.length;
781 
782             if (this.visProp.attractordistance === 0.0) {
783                 return;
784             }
785 
786             for (i = 0; i < len; i++) {
787                 el = this.board.select(this.visProp.attractors[i]);
788 
789                 if (Type.exists(el) && el !== this) {
790                     if (el.elementClass === Const.OBJECT_CLASS_POINT) {
791                         projCoords = Geometry.projectPointToPoint(this, el, this.board);
792                     } else if (el.elementClass === Const.OBJECT_CLASS_LINE) {
793                         projCoords = Geometry.projectPointToLine(this, el, this.board);
794                     } else if (el.elementClass === Const.OBJECT_CLASS_CIRCLE) {
795                         projCoords = Geometry.projectPointToCircle(this, el, this.board);
796                     } else if (el.elementClass === Const.OBJECT_CLASS_CURVE) {
797                         projCoords = Geometry.projectPointToCurve(this, el, this.board);
798                     } else if (el.type === Const.OBJECT_TYPE_TURTLE) {
799                         projCoords = Geometry.projectPointToTurtle(this, el, this.board);
800                     }
801 
802                     if (this.visProp.attractorunit === 'screen') {
803                         d = projCoords.distance(Const.COORDS_BY_SCREEN, this.coords);
804                     } else {
805                         d = projCoords.distance(Const.COORDS_BY_USER, this.coords);
806                     }
807 
808                     if (d < this.visProp.attractordistance) {
809                         if (!(this.type === Const.OBJECT_TYPE_GLIDER && this.slideObject === el)) {
810                             this.makeGlider(el);
811                         }
812 
813                         break;       // bind the point to the first attractor in its list.
814                     } else {
815                         if (el === this.slideObject && d >= this.visProp.snatchdistance) {
816                             this.popSlideObject();
817                         }
818                     }
819                 }
820             }
821 
822             return this;
823         },
824 
825         /**
826          * Sets coordinates and calls the point's update() method.
827          * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
828          * @param {Array} coords coordinates <tt>(z, x, y)</tt> in screen/user units
829          * @returns {JXG.Point} this element
830          */
831         setPositionDirectly: function (method, coords) {
832             var i, dx, dy, dz, el, p,
833                 oldCoords = this.coords,
834                 newCoords;
835 
836             this.coords.setCoordinates(method, coords);
837             this.handleSnapToGrid();
838             this.handleSnapToPoints();
839             this.handleAttractors();
840 
841             if (this.group.length === 0) {
842                 // Here used to be the udpate of the groups. I'm not sure why we don't need to execute
843                 // the else branch if there are groups defined on this point, hence I'll let the if live.
844 
845                 // Update the initial coordinates. This is needed for free points
846                 // that have a transformation bound to it.
847                 for (i = this.transformations.length - 1; i >= 0; i--) {
848                     if (method === Const.COORDS_BY_SCREEN) {
849                         newCoords = (new Coords(method, coords, this.board)).usrCoords;
850                     } else {
851                         if (coords.length === 2) {
852                             coords = [1].concat(coords);
853                         }
854                         newCoords = coords;
855                     }
856                     this.initialCoords.setCoordinates(Const.COORDS_BY_USER, Mat.matVecMult(Mat.inverse(this.transformations[i].matrix), newCoords));
857                 }
858                 this.update();
859             }
860 
861             // if the user suspends the board updates we need to recalculate the relative position of
862             // the point on the slide object. this is done in updateGlider() which is NOT called during the
863             // update process triggered by unsuspendUpdate.
864             if (this.board.isSuspendedUpdate && this.type === Const.OBJECT_TYPE_GLIDER) {
865                 this.updateGlider();
866             }
867 
868             return coords;
869         },
870 
871         /**
872          * Translates the point by <tt>tv = (x, y)</tt>.
873          * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
874          * @param {Number} tv (x, y)
875          * @returns {JXG.Point}
876          */
877         setPositionByTransform: function (method, tv) {
878             var t;
879 
880             tv = new Coords(method, tv, this.board);
881             t = this.board.create('transform', tv.usrCoords.slice(1), {type: 'translate'});
882 
883             if (this.transformations.length > 0 && this.transformations[this.transformations.length - 1].isNumericMatrix) {
884                 this.transformations[this.transformations.length - 1].melt(t);
885             } else {
886                 this.addTransform(this, t);
887             }
888 
889             this.update();
890 
891             return this;
892         },
893 
894         /**
895          * Sets coordinates and calls the point's update() method.
896          * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
897          * @param {Array} coords coordinates in screen/user units
898          * @returns {JXG.Point}
899          */
900         setPosition: function (method, coords) {
901             return this.setPositionDirectly(method, coords);
902         },
903 
904         /**
905          * Sets the position of a glider relative to the defining elements of the {@link JXG.Point#slideObject}.
906          * @param {Number} x
907          * @returns {JXG.Point} Reference to the point element.
908          */
909         setGliderPosition: function (x) {
910             if (this.type === Const.OBJECT_TYPE_GLIDER) {
911                 this.position = x;
912                 this.board.update();
913             }
914 
915             return this;
916         },
917 
918         /**
919          * Convert the point to glider and update the construction.
920          * @param {String|Object} glideObject The Object the point will be bound to.
921          */
922         makeGlider: function (glideObject) {
923             this.slideObject = this.board.select(glideObject);
924             this.slideObjects.push(this.slideObject);
925 
926             this.type = Const.OBJECT_TYPE_GLIDER;
927             this.elType = 'glider';
928             this.visProp.snapwidth = -1;          // By default, deactivate snapWidth
929             this.slideObject.addChild(this);
930             this.isDraggable = true;
931 
932             this.generatePolynomial = function () {
933                 return this.slideObject.generatePolynomial(this);
934             };
935 
936             // Determine the initial value of this.position
937             this.updateGlider();
938 
939             return this;
940         },
941 
942         /**
943          * Remove the last slideObject. If there are more than one elements the point is bound to,
944          * the second last element is the new active slideObject.
945          */
946         popSlideObject: function () {
947             if (this.slideObjects.length > 0) {
948                 this.slideObjects.pop();
949 
950                 // It may not be sufficient to remove the point from
951                 // the list of childElement. For complex dependencies
952                 // one may have to go to the list of ancestor and descendants.  A.W.
953                 // yes indeed, see #51 on github bugtracker
954                 //delete this.slideObject.childElements[this.id];
955                 this.slideObject.removeChild(this);
956 
957                 if (this.slideObjects.length === 0) {
958                     this.elType = 'point';
959                     this.type = Const.OBJECT_TYPE_POINT;
960                     this.slideObject = null;
961                 } else {
962                     this.slideObject = this.slideObjects[this.slideObjects.length - 1];
963                 }
964             }
965         },
966 
967         /**
968          * Converts a calculated point into a free point, i.e. it will delete all ancestors and transformations and,
969          * if the point is currently a glider, will remove the slideObject reference.
970          */
971         free: function () {
972             var ancestorId, ancestor, child;
973 
974             if (this.type !== Const.OBJECT_TYPE_GLIDER) {
975                 // remove all transformations
976                 this.transformations.length = 0;
977 
978                 if (!this.isDraggable) {
979                     this.isDraggable = true;
980                     this.type = Const.OBJECT_TYPE_POINT;
981 
982                     this.XEval = function () {
983                         return this.coords.usrCoords[1];
984                     };
985 
986                     this.YEval = function () {
987                         return this.coords.usrCoords[2];
988                     };
989 
990                     this.ZEval = function () {
991                         return this.coords.usrCoords[0];
992                     };
993 
994                     this.Xjc = null;
995                     this.Yjc = null;
996                 } else {
997                     return;
998                 }
999             }
1000 
1001             // a free point does not depend on anything. And instead of running through tons of descendants and ancestor
1002             // structures, where we eventually are going to visit a lot of objects twice or thrice with hard to read and
1003             // comprehend code, just run once through all objects and delete all references to this point and its label.
1004             for (ancestorId in this.board.objects) {
1005                 if (this.board.objects.hasOwnProperty(ancestorId)) {
1006                     ancestor = this.board.objects[ancestorId];
1007 
1008                     if (ancestor.descendants) {
1009                         delete ancestor.descendants[this.id];
1010                         delete ancestor.childElements[this.id];
1011 
1012                         if (this.hasLabel) {
1013                             delete ancestor.descendants[this.label.id];
1014                             delete ancestor.childElements[this.label.id];
1015                         }
1016                     }
1017                 }
1018             }
1019 
1020             // A free point does not depend on anything. Remove all ancestors.
1021             this.ancestors = {}; // only remove the reference
1022 
1023             // Completely remove all slideObjects of the point
1024             this.slideObject = null;
1025             this.slideObjects = [];
1026             this.elType = 'point';
1027             this.type = Const.OBJECT_TYPE_POINT;
1028         },
1029 
1030         /**
1031          * Convert the point to CAS point and call update().
1032          * @param {Array} terms [[zterm], xterm, yterm] defining terms for the z, x and y coordinate.
1033          * The z-coordinate is optional and it is used for homogeneous coordinates.
1034          * The coordinates may be either <ul>
1035          *   <li>a JavaScript function,</li>
1036          *   <li>a string containing GEONExT syntax. This string will be converted into a JavaScript
1037          *     function here,</li>
1038          *   <li>a Number</li>
1039          *   <li>a pointer to a slider object. This will be converted into a call of the Value()-method
1040          *     of this slider.</li>
1041          *   </ul>
1042          * @see JXG.GeonextParser#geonext2JS
1043          */
1044         addConstraint: function (terms) {
1045             var fs, i, v, t,
1046                 newfuncs = [],
1047                 what = ['X', 'Y'],
1048 
1049                 makeConstFunction = function (z) {
1050                     return function () {
1051                         return z;
1052                     };
1053                 },
1054 
1055                 makeSliderFunction = function (a) {
1056                     return function () {
1057                         return a.Value();
1058                     };
1059                 };
1060 
1061             this.type = Const.OBJECT_TYPE_CAS;
1062             this.isDraggable = false;
1063 
1064             for (i = 0; i < terms.length; i++) {
1065                 v = terms[i];
1066 
1067                 if (typeof v === 'string') {
1068                     // Convert GEONExT syntax into  JavaScript syntax
1069                     //t  = JXG.GeonextParser.geonext2JS(v, this.board);
1070                     //newfuncs[i] = new Function('','return ' + t + ';');
1071                     //v = GeonextParser.replaceNameById(v, this.board);
1072                     newfuncs[i] = this.board.jc.snippet(v, true, null, true);
1073 
1074                     if (terms.length === 2) {
1075                         this[what[i] + 'jc'] = terms[i];
1076                     }
1077                 } else if (typeof v === 'function') {
1078                     newfuncs[i] = v;
1079                 } else if (typeof v === 'number') {
1080                     newfuncs[i] = makeConstFunction(v);
1081                 // Slider
1082                 } else if (typeof v === 'object' && typeof v.Value === 'function') {
1083                     newfuncs[i] = makeSliderFunction(v);
1084                 }
1085 
1086                 newfuncs[i].origin = v;
1087             }
1088 
1089             // Intersection function
1090             if (terms.length === 1) {
1091                 this.updateConstraint = function () {
1092                     var c = newfuncs[0]();
1093 
1094                     // Array
1095                     if (Type.isArray(c)) {
1096                         this.coords.setCoordinates(Const.COORDS_BY_USER, c);
1097                     // Coords object
1098                     } else {
1099                         this.coords = c;
1100                     }
1101                 };
1102             // Euclidean coordinates
1103             } else if (terms.length === 2) {
1104                 this.XEval = newfuncs[0];
1105                 this.YEval = newfuncs[1];
1106 
1107                 this.parents = [newfuncs[0].origin, newfuncs[1].origin];
1108 
1109                 this.updateConstraint = function () {
1110                     this.coords.setCoordinates(Const.COORDS_BY_USER, [this.XEval(), this.YEval()]);
1111                 };
1112             // Homogeneous coordinates
1113             } else {
1114                 this.ZEval = newfuncs[0];
1115                 this.XEval = newfuncs[1];
1116                 this.YEval = newfuncs[2];
1117 
1118                 this.parents = [newfuncs[0].origin, newfuncs[1].origin, newfuncs[2].origin];
1119 
1120                 this.updateConstraint = function () {
1121                     this.coords.setCoordinates(Const.COORDS_BY_USER, [this.ZEval(), this.XEval(), this.YEval()]);
1122                 };
1123             }
1124 
1125             /**
1126             * We have to do an update. Otherwise, elements relying on this point will receive NaN.
1127             */
1128             this.update();
1129             if (!this.board.isSuspendedUpdate) {
1130                 this.updateRenderer();
1131             }
1132 
1133             return this;
1134         },
1135 
1136         /**
1137          * Applies the transformations of the curve to {@link JXG.Point#baseElement}.
1138          * @returns {JXG.Point} Reference to this point object.
1139          */
1140         updateTransform: function () {
1141             var c, i;
1142 
1143             if (this.transformations.length === 0 || this.baseElement === null) {
1144                 return this;
1145             }
1146 
1147             // case of bindTo
1148             if (this === this.baseElement) {
1149                 c = this.transformations[0].apply(this.baseElement, 'self');
1150             // case of board.create('point',[baseElement,transform]);
1151             } else {
1152                 c = this.transformations[0].apply(this.baseElement);
1153             }
1154 
1155             this.coords.setCoordinates(Const.COORDS_BY_USER, c);
1156 
1157             for (i = 1; i < this.transformations.length; i++) {
1158                 this.coords.setCoordinates(Const.COORDS_BY_USER, this.transformations[i].apply(this));
1159             }
1160             return this;
1161         },
1162 
1163         /**
1164          * Add transformations to this point.
1165          * @param {JXG.GeometryElement} el
1166          * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} or an array of {@link JXG.Transformation}s.
1167          * @returns {JXG.Point} Reference to this point object.
1168          */
1169         addTransform: function (el, transform) {
1170             var i,
1171                 list = Type.isArray(transform) ? transform : [transform],
1172                 len = list.length;
1173 
1174             // There is only one baseElement possible
1175             if (this.transformations.length === 0) {
1176                 this.baseElement = el;
1177             }
1178 
1179             for (i = 0; i < len; i++) {
1180                 this.transformations.push(list[i]);
1181             }
1182 
1183             return this;
1184         },
1185 
1186         /**
1187          * Animate the point.
1188          * @param {Number} direction The direction the glider is animated. Can be +1 or -1.
1189          * @param {Number} stepCount The number of steps.
1190          * @name Glider#startAnimation
1191          * @see Glider#stopAnimation
1192          * @function
1193          */
1194         startAnimation: function (direction, stepCount) {
1195             var that = this;
1196 
1197             if ((this.type === Const.OBJECT_TYPE_GLIDER) && !Type.exists(this.intervalCode)) {
1198                 this.intervalCode = window.setInterval(function () {
1199                     that._anim(direction, stepCount);
1200                 }, 250);
1201 
1202                 if (!Type.exists(this.intervalCount)) {
1203                     this.intervalCount = 0;
1204                 }
1205             }
1206             return this;
1207         },
1208 
1209         /**
1210          * Stop animation.
1211          * @name Glider#stopAnimation
1212          * @see Glider#startAnimation
1213          * @function
1214          */
1215         stopAnimation: function () {
1216             if (Type.exists(this.intervalCode)) {
1217                 window.clearInterval(this.intervalCode);
1218                 delete this.intervalCode;
1219             }
1220 
1221             return this;
1222         },
1223 
1224         /**
1225          * Starts an animation which moves the point along a given path in given time.
1226          * @param {Array|function} path The path the point is moved on. This can be either an array of arrays containing x and y values of the points of
1227          * the path, or  function taking the amount of elapsed time since the animation has started and returns an array containing a x and a y value or NaN.
1228          * In case of NaN the animation stops.
1229          * @param {Number} time The time in milliseconds in which to finish the animation
1230          * @param {Object} [options] Optional settings for the animation.
1231          * @param {function} [options.callback] A function that is called as soon as the animation is finished.
1232          * @param {Boolean} [options.interpolate=true] If <tt>path</tt> is an array moveAlong() will interpolate the path
1233          * using {@link JXG.Math.Numerics#Neville}. Set this flag to false if you don't want to use interpolation.
1234          * @returns {JXG.Point} Reference to the point.
1235          */
1236         moveAlong: function (path, time, options) {
1237             options = options || {};
1238 
1239             var i, neville,
1240                 interpath = [],
1241                 p = [],
1242                 delay = this.board.attr.animationdelay,
1243                 steps = time / delay,
1244 
1245                 makeFakeFunction = function (i, j) {
1246                     return function () {
1247                         return path[i][j];
1248                     };
1249                 };
1250 
1251             if (Type.isArray(path)) {
1252                 for (i = 0; i < path.length; i++) {
1253                     if (Type.isPoint(path[i])) {
1254                         p[i] = path[i];
1255                     } else {
1256                         p[i] = {
1257                             elementClass: Const.OBJECT_CLASS_POINT,
1258                             X: makeFakeFunction(i, 0),
1259                             Y: makeFakeFunction(i, 1)
1260                         };
1261                     }
1262                 }
1263 
1264                 time = time || 0;
1265                 if (time === 0) {
1266                     this.setPosition(Const.COORDS_BY_USER, [p[p.length - 1].X(), p[p.length - 1].Y()]);
1267                     return this.board.update(this);
1268                 }
1269 
1270                 if (!Type.exists(options.interpolate) || options.interpolate) {
1271                     neville = Numerics.Neville(p);
1272                     for (i = 0; i < steps; i++) {
1273                         interpath[i] = [];
1274                         interpath[i][0] = neville[0]((steps - i) / steps * neville[3]());
1275                         interpath[i][1] = neville[1]((steps - i) / steps * neville[3]());
1276                     }
1277                 } else {
1278                     for (i = 0; i < steps; i++) {
1279                         interpath[i] = [];
1280                         interpath[i][0] = path[Math.floor((steps - i) / steps * (path.length - 1))][0];
1281                         interpath[i][1] = path[Math.floor((steps - i) / steps * (path.length - 1))][1];
1282                     }
1283                 }
1284 
1285                 this.animationPath = interpath;
1286             } else if (Type.isFunction(path)) {
1287                 this.animationPath = path;
1288                 this.animationStart = new Date().getTime();
1289             }
1290 
1291             this.animationCallback = options.callback;
1292             this.board.addAnimation(this);
1293 
1294             return this;
1295         },
1296 
1297         /**
1298          * Starts an animated point movement towards the given coordinates <tt>where</tt>. The animation is done after <tt>time</tt> milliseconds.
1299          * If the second parameter is not given or is equal to 0, setPosition() is called, see #setPosition.
1300          * @param {Array} where Array containing the x and y coordinate of the target location.
1301          * @param {Number} [time] Number of milliseconds the animation should last.
1302          * @param {Object} [options] Optional settings for the animation
1303          * @param {function} [options.callback] A function that is called as soon as the animation is finished.
1304          * @param {String} [options.effect='<>'] animation effects like speed fade in and out. possible values are
1305          * '<>' for speed increase on start and slow down at the end (default) and '--' for constant speed during
1306          * the whole animation.
1307          * @returns {JXG.Point} Reference to itself.
1308          * @see #animate
1309          */
1310         moveTo: function (where, time, options) {
1311             options = options || {};
1312             where = new Coords(Const.COORDS_BY_USER, where, this.board);
1313 
1314             var i,
1315                 delay = this.board.attr.animationdelay,
1316                 steps = Math.ceil(time / delay),
1317                 coords = [],
1318                 X = this.coords.usrCoords[1],
1319                 Y = this.coords.usrCoords[2],
1320                 dX = (where.usrCoords[1] - X),
1321                 dY = (where.usrCoords[2] - Y),
1322 
1323                 /** @ignore */
1324                 stepFun = function (i) {
1325                     if (options.effect && options.effect === '<>') {
1326                         return Math.pow(Math.sin((i / steps) * Math.PI / 2), 2);
1327                     }
1328                     return i / steps;
1329                 };
1330 
1331             if (!Type.exists(time) || time === 0 || (Math.abs(where.usrCoords[0] - this.coords.usrCoords[0]) > Mat.eps)) {
1332                 this.setPosition(Const.COORDS_BY_USER, where.usrCoords);
1333                 return this.board.update(this);
1334             }
1335 
1336             if (Math.abs(dX) < Mat.eps && Math.abs(dY) < Mat.eps) {
1337                 return this;
1338             }
1339 
1340             for (i = steps; i >= 0; i--) {
1341                 coords[steps - i] = [where.usrCoords[0], X + dX * stepFun(i), Y + dY * stepFun(i)];
1342             }
1343 
1344             this.animationPath = coords;
1345             this.animationCallback = options.callback;
1346             this.board.addAnimation(this);
1347 
1348             return this;
1349         },
1350 
1351         /**
1352          * Starts an animated point movement towards the given coordinates <tt>where</tt>. After arriving at
1353          * <tt>where</tt> the point moves back to where it started. The animation is done after <tt>time</tt>
1354          * milliseconds.
1355          * @param {Array} where Array containing the x and y coordinate of the target location.
1356          * @param {Number} time Number of milliseconds the animation should last.
1357          * @param {Object} [options] Optional settings for the animation
1358          * @param {function} [options.callback] A function that is called as soon as the animation is finished.
1359          * @param {String} [options.effect='<>'] animation effects like speed fade in and out. possible values are
1360          * '<>' for speed increase on start and slow down at the end (default) and '--' for constant speed during
1361          * the whole animation.
1362          * @param {Number} [options.repeat=1] How often this animation should be repeated.
1363          * @returns {JXG.Point} Reference to itself.
1364          * @see #animate
1365          */
1366         visit: function (where, time, options) {
1367             where = new Coords(Const.COORDS_BY_USER, where, this.board);
1368 
1369             var i, j, steps,
1370                 delay = this.board.attr.animationdelay,
1371                 coords = [],
1372                 X = this.coords.usrCoords[1],
1373                 Y = this.coords.usrCoords[2],
1374                 dX = (where.usrCoords[1] - X),
1375                 dY = (where.usrCoords[2] - Y),
1376 
1377                 /** @ignore */
1378                 stepFun = function (i) {
1379                     var x = (i < steps / 2 ? 2 * i / steps : 2 * (steps - i) / steps);
1380 
1381                     if (options.effect && options.effect === '<>') {
1382                         return Math.pow(Math.sin(x * Math.PI / 2), 2);
1383                     }
1384 
1385                     return x;
1386                 };
1387 
1388             // support legacy interface where the third parameter was the number of repeats
1389             if (typeof options === 'number') {
1390                 options = {repeat: options};
1391             } else {
1392                 options = options || {};
1393                 if (!Type.exists(options.repeat)) {
1394                     options.repeat = 1;
1395                 }
1396             }
1397 
1398             steps = Math.ceil(time / (delay * options.repeat));
1399 
1400             for (j = 0; j < options.repeat; j++) {
1401                 for (i = steps; i >= 0; i--) {
1402                     coords[j * (steps + 1) + steps - i] = [where.usrCoords[0], X + dX * stepFun(i), Y + dY * stepFun(i)];
1403                 }
1404             }
1405             this.animationPath = coords;
1406             this.animationCallback = options.callback;
1407             this.board.addAnimation(this);
1408 
1409             return this;
1410         },
1411 
1412         /**
1413          * Animates a glider. Is called by the browser after startAnimation is called.
1414          * @param {Number} direction The direction the glider is animated.
1415          * @param {Number} stepCount The number of steps.
1416          * @see #startAnimation
1417          * @see #stopAnimation
1418          * @private
1419          */
1420         _anim: function (direction, stepCount) {
1421             var distance, slope, dX, dY, alpha, startPoint, newX, radius,
1422                 factor = 1;
1423 
1424             this.intervalCount += 1;
1425             if (this.intervalCount > stepCount) {
1426                 this.intervalCount = 0;
1427             }
1428 
1429             if (this.slideObject.elementClass === Const.OBJECT_CLASS_LINE) {
1430                 distance = this.slideObject.point1.coords.distance(Const.COORDS_BY_SCREEN, this.slideObject.point2.coords);
1431                 slope = this.slideObject.getSlope();
1432                 if (slope !== Infinity) {
1433                     alpha = Math.atan(slope);
1434                     dX = Math.round((this.intervalCount / stepCount) * distance * Math.cos(alpha));
1435                     dY = Math.round((this.intervalCount / stepCount) * distance * Math.sin(alpha));
1436                 } else {
1437                     dX = 0;
1438                     dY = Math.round((this.intervalCount / stepCount) * distance);
1439                 }
1440 
1441                 if (direction < 0) {
1442                     startPoint = this.slideObject.point2;
1443 
1444                     if (this.slideObject.point2.coords.scrCoords[1] - this.slideObject.point1.coords.scrCoords[1] > 0) {
1445                         factor = -1;
1446                     } else if (this.slideObject.point2.coords.scrCoords[1] - this.slideObject.point1.coords.scrCoords[1] === 0) {
1447                         if (this.slideObject.point2.coords.scrCoords[2] - this.slideObject.point1.coords.scrCoords[2] > 0) {
1448                             factor = -1;
1449                         }
1450                     }
1451                 } else {
1452                     startPoint = this.slideObject.point1;
1453 
1454                     if (this.slideObject.point1.coords.scrCoords[1] - this.slideObject.point2.coords.scrCoords[1] > 0) {
1455                         factor = -1;
1456                     } else if (this.slideObject.point1.coords.scrCoords[1] - this.slideObject.point2.coords.scrCoords[1] === 0) {
1457                         if (this.slideObject.point1.coords.scrCoords[2] - this.slideObject.point2.coords.scrCoords[2] > 0) {
1458                             factor = -1;
1459                         }
1460                     }
1461                 }
1462 
1463                 this.coords.setCoordinates(Const.COORDS_BY_SCREEN, [
1464                     startPoint.coords.scrCoords[1] + factor * dX,
1465                     startPoint.coords.scrCoords[2] + factor * dY
1466                 ]);
1467             } else if (this.slideObject.elementClass === Const.OBJECT_CLASS_CURVE) {
1468                 if (direction > 0) {
1469                     newX = Math.round(this.intervalCount / stepCount * this.board.canvasWidth);
1470                 } else {
1471                     newX = Math.round((stepCount - this.intervalCount) / stepCount * this.board.canvasWidth);
1472                 }
1473 
1474                 this.coords.setCoordinates(Const.COORDS_BY_SCREEN, [newX, 0]);
1475                 this.coords = Geometry.projectPointToCurve(this, this.slideObject, this.board);
1476             } else if (this.slideObject.elementClass === Const.OBJECT_CLASS_CIRCLE) {
1477                 if (direction < 0) {
1478                     alpha = this.intervalCount / stepCount * 2 * Math.PI;
1479                 } else {
1480                     alpha = (stepCount - this.intervalCount) / stepCount * 2 * Math.PI;
1481                 }
1482 
1483                 radius = this.slideObject.Radius();
1484 
1485                 this.coords.setCoordinates(Const.COORDS_BY_USER, [
1486                     this.slideObject.center.coords.usrCoords[1] + radius * Math.cos(alpha),
1487                     this.slideObject.center.coords.usrCoords[2] + radius * Math.sin(alpha)
1488                 ]);
1489             }
1490 
1491             this.board.update(this);
1492             return this;
1493         },
1494 
1495         /**
1496          * Set the style of a point. Used for GEONExT import and should not be used to set the point's face and size.
1497          * @param {Number} i Integer to determine the style.
1498          * @private
1499          */
1500         setStyle: function (i) {
1501             var facemap = [
1502                 // 0-2
1503                 'cross', 'cross', 'cross',
1504                 // 3-6
1505                 'circle', 'circle', 'circle', 'circle',
1506                 // 7-9
1507                 'square', 'square', 'square',
1508                 // 10-12
1509                 'plus', 'plus', 'plus'
1510             ], sizemap = [
1511                 // 0-2
1512                 2, 3, 4,
1513                 // 3-6
1514                 1, 2, 3, 4,
1515                 // 7-9
1516                 2, 3, 4,
1517                 // 10-12
1518                 2, 3, 4
1519             ];
1520 
1521             this.visProp.face = facemap[i];
1522             this.visProp.size = sizemap[i];
1523 
1524             this.board.renderer.changePointStyle(this);
1525             return this;
1526         },
1527 
1528         /**
1529          * @deprecated Use JXG#normalizePointFace instead
1530          * @param s
1531          * @return {*}
1532          */
1533         normalizeFace: function (s) {
1534             return Options.normalizePointFace(s);
1535         },
1536 
1537         /**
1538          * Remove the point from the drawing. This only removes the SVG or VML node of the point and its label from the renderer, to remove
1539          * the object completely you should use {@link JXG.Board#removeObject}.
1540          */
1541         remove: function () {
1542             if (this.hasLabel) {
1543                 this.board.renderer.remove(this.board.renderer.getElementById(this.label.id));
1544             }
1545             this.board.renderer.remove(this.board.renderer.getElementById(this.id));
1546         },
1547 
1548         // documented in GeometryElement
1549         getTextAnchor: function () {
1550             return this.coords;
1551         },
1552 
1553         // documented in GeometryElement
1554         getLabelAnchor: function () {
1555             return this.coords;
1556         },
1557 
1558         /**
1559          * Set the face of a point element.
1560          * @param {String} f String which determines the face of the point. See {@link JXG.GeometryElement#face} for a list of available faces.
1561          * @see JXG.GeometryElement#face
1562          * @deprecated Use setAttribute()
1563          */
1564         face: function (f) {
1565             this.setAttribute({face: f});
1566         },
1567 
1568         /**
1569          * Set the size of a point element
1570          * @param {Number} s Integer which determines the size of the point.
1571          * @see JXG.GeometryElement#size
1572          * @deprecated Use setAttribute()
1573          */
1574         size: function (s) {
1575             this.setAttribute({size: s});
1576         },
1577 
1578         // already documented in GeometryElement
1579         cloneToBackground: function () {
1580             var copy = {};
1581 
1582             copy.id = this.id + 'T' + this.numTraces;
1583             this.numTraces += 1;
1584 
1585             copy.coords = this.coords;
1586             copy.visProp = Type.deepCopy(this.visProp, this.visProp.traceattributes, true);
1587             copy.visProp.layer = this.board.options.layer.trace;
1588             copy.elementClass = Const.OBJECT_CLASS_POINT;
1589             copy.board = this.board;
1590             Type.clearVisPropOld(copy);
1591 
1592             this.board.renderer.drawPoint(copy);
1593             this.traces[copy.id] = copy.rendNode;
1594 
1595             return this;
1596         },
1597 
1598         getParents: function () {
1599             var p = [this.Z(), this.X(), this.Y()];
1600 
1601             if (this.parents) {
1602                 p = this.parents;
1603             }
1604 
1605             if (this.type === Const.OBJECT_TYPE_GLIDER) {
1606                 p = [this.X(), this.Y(), this.slideObject.id];
1607 
1608             }
1609 
1610             return p;
1611         }
1612     });
1613 
1614 
1615     /**
1616      * @class This element is used to provide a constructor for a general point. A free point is created if the given parent elements are all numbers
1617      * and the property fixed is not set or set to false. If one or more parent elements is not a number but a string containing a GEONE<sub>x</sub>T
1618      * constraint or a function the point will be considered as constrained). That means that the user won't be able to change the point's
1619      * position directly.
1620      * @pseudo
1621      * @description
1622      * @name Point
1623      * @augments JXG.Point
1624      * @constructor
1625      * @type JXG.Point
1626      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
1627      * @param {Number,string,function_Number,string,function_Number,string,function} z_,x,y Parent elements can be two or three elements of type number, a string containing a GEONE<sub>x</sub>T
1628      * constraint, or a function which takes no parameter and returns a number. Every parent element determines one coordinate. If a coordinate is
1629      * given by a number, the number determines the initial position of a free point. If given by a string or a function that coordinate will be constrained
1630      * that means the user won't be able to change the point's position directly by mouse because it will be calculated automatically depending on the string
1631      * or the function's return value. If two parent elements are given the coordinates will be interpreted as 2D affine euclidean coordinates, if three such
1632      * parent elements are given they will be interpreted as homogeneous coordinates.
1633      * @param {JXG.Point_JXG.Transformation} Point,Transformation A point can also be created providing a transformation. The resulting point is a clone of the base
1634      * point transformed by the given Transformation. {@see JXG.Transformation}.
1635      * @example
1636      * // Create a free point using affine euclidean coordinates
1637      * var p1 = board.create('point', [3.5, 2.0]);
1638      * </pre><div id="672f1764-7dfa-4abc-a2c6-81fbbf83e44b" style="width: 200px; height: 200px;"></div>
1639      * <script type="text/javascript">
1640      *   var board = JXG.JSXGraph.initBoard('672f1764-7dfa-4abc-a2c6-81fbbf83e44b', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false});
1641      *   var p1 = board.create('point', [3.5, 2.0]);
1642      * </script><pre>
1643      * @example
1644      * // Create a constrained point using anonymous function
1645      * var p2 = board.create('point', [3.5, function () { return p1.X(); }]);
1646      * </pre><div id="4fd4410c-3383-4e80-b1bb-961f5eeef224" style="width: 200px; height: 200px;"></div>
1647      * <script type="text/javascript">
1648      *   var fpex1_board = JXG.JSXGraph.initBoard('4fd4410c-3383-4e80-b1bb-961f5eeef224', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false});
1649      *   var fpex1_p1 = fpex1_board.create('point', [3.5, 2.0]);
1650      *   var fpex1_p2 = fpex1_board.create('point', [3.5, function () { return fpex1_p1.X(); }]);
1651      * </script><pre>
1652      * @example
1653      * // Create a point using transformations
1654      * var trans = board.create('transform', [2, 0.5], {type:'scale'});
1655      * var p3 = board.create('point', [p2, trans]);
1656      * </pre><div id="630afdf3-0a64-46e0-8a44-f51bd197bb8d" style="width: 400px; height: 400px;"></div>
1657      * <script type="text/javascript">
1658      *   var fpex2_board = JXG.JSXGraph.initBoard('630afdf3-0a64-46e0-8a44-f51bd197bb8d', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
1659      *   var fpex2_trans = fpex2_board.create('transform', [2, 0.5], {type:'scale'});
1660      *   var fpex2_p2 = fpex2_board.create('point', [3.5, 2.0]);
1661      *   var fpex2_p3 = fpex2_board.create('point', [fpex2_p2, fpex2_trans]);
1662      * </script><pre>
1663      */
1664     JXG.createPoint = function (board, parents, attributes) {
1665         var el, isConstrained = false, i, attr;
1666 
1667         attr = Type.copyAttributes(attributes, board.options, 'point');
1668 
1669         if (parents.length === 1 && Type.isArray(parents[0]) && parents[0].length > 1 && parents[0].length < 4) {
1670             parents = parents[0];
1671         }
1672 
1673         for (i = 0; i < parents.length; i++) {
1674             if (typeof parents[i] === 'function' || typeof parents[i] === 'string') {
1675                 isConstrained = true;
1676             }
1677         }
1678 
1679         if (!isConstrained) {
1680             if ((Type.isNumber(parents[0])) && (Type.isNumber(parents[1]))) {
1681                 el = new JXG.Point(board, parents, attr);
1682 
1683                 if (Type.exists(attr.slideobject)) {
1684                     el.makeGlider(attr.slideobject);
1685                 } else {
1686                     // Free point
1687                     el.baseElement = el;
1688                 }
1689                 el.isDraggable = true;
1690             } else if ((typeof parents[0] === 'object') && (typeof parents[1] === 'object')) {
1691                 // Transformation
1692                 el = new JXG.Point(board, [0, 0], attr);
1693                 el.addTransform(parents[0], parents[1]);
1694                 el.isDraggable = false;
1695 
1696                 el.parents = [parents[0].id];
1697             } else {
1698                 throw new Error("JSXGraph: Can't create point with parent types '" +
1699                     (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
1700                     "\nPossible parent types: [x,y], [z,x,y], [point,transformation]");
1701             }
1702         } else {
1703             el = new JXG.Point(board, [NaN, NaN], attr);
1704             el.addConstraint(parents);
1705         }
1706 
1707         if (!board.isSuspendedUpdate) {
1708             el.handleSnapToGrid();
1709             el.handleSnapToPoints();
1710             el.handleAttractors();
1711         }
1712 
1713         return el;
1714     };
1715 
1716     /**
1717      * @class This element is used to provide a constructor for a glider point.
1718      * @pseudo
1719      * @description A glider is a point which lives on another geometric element like a line, circle, curve, turtle.
1720      * @name Glider
1721      * @augments JXG.Point
1722      * @constructor
1723      * @type JXG.Point
1724      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
1725      * @param {Number_Number_Number_JXG.GeometryElement} z_,x_,y_,GlideObject Parent elements can be two or three elements of type number and the object the glider lives on.
1726      * The coordinates are completely optional. If not given the origin is used. If you provide two numbers for coordinates they will be interpreted as affine euclidean
1727      * coordinates, otherwise they will be interpreted as homogeneous coordinates. In any case the point will be projected on the glide object.
1728      * @example
1729      * // Create a glider with user defined coordinates. If the coordinates are not on
1730      * // the circle (like in this case) the point will be projected onto the circle.
1731      * var p1 = board.create('point', [2.0, 2.0]);
1732      * var c1 = board.create('circle', [p1, 2.0]);
1733      * var p2 = board.create('glider', [2.0, 1.5, c1]);
1734      * </pre><div id="4f65f32f-e50a-4b50-9b7c-f6ec41652930" style="width: 300px; height: 300px;"></div>
1735      * <script type="text/javascript">
1736      *   var gpex1_board = JXG.JSXGraph.initBoard('4f65f32f-e50a-4b50-9b7c-f6ec41652930', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false});
1737      *   var gpex1_p1 = gpex1_board.create('point', [2.0, 2.0]);
1738      *   var gpex1_c1 = gpex1_board.create('circle', [gpex1_p1, 2.0]);
1739      *   var gpex1_p2 = gpex1_board.create('glider', [2.0, 1.5, gpex1_c1]);
1740      * </script><pre>
1741      * @example
1742      * // Create a glider with default coordinates (1,0,0). Same premises as above.
1743      * var p1 = board.create('point', [2.0, 2.0]);
1744      * var c1 = board.create('circle', [p1, 2.0]);
1745      * var p2 = board.create('glider', [c1]);
1746      * </pre><div id="4de7f181-631a-44b1-a12f-bc4d995609e8" style="width: 200px; height: 200px;"></div>
1747      * <script type="text/javascript">
1748      *   var gpex2_board = JXG.JSXGraph.initBoard('4de7f181-631a-44b1-a12f-bc4d995609e8', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false});
1749      *   var gpex2_p1 = gpex2_board.create('point', [2.0, 2.0]);
1750      *   var gpex2_c1 = gpex2_board.create('circle', [gpex2_p1, 2.0]);
1751      *   var gpex2_p2 = gpex2_board.create('glider', [gpex2_c1]);
1752      * </script><pre>
1753      */
1754     JXG.createGlider = function (board, parents, attributes) {
1755         var el,
1756             attr = Type.copyAttributes(attributes, board.options, 'glider');
1757 
1758         if (parents.length === 1) {
1759             el = board.create('point', [0, 0], attr);
1760         } else {
1761             el = board.create('point', parents.slice(0, 2), attr);
1762         }
1763 
1764         // eltype is set in here
1765         el.makeGlider(parents[parents.length - 1]);
1766 
1767         return el;
1768     };
1769 
1770     /**
1771      * @class This element is used to provide a constructor for an intersection point.
1772      * @pseudo
1773      * @description An intersection point is a point which lives on two Lines or Circles or one Line and one Circle at the same time, i.e.
1774      * an intersection point of the two elements.
1775      * @name Intersection
1776      * @augments JXG.Point
1777      * @constructor
1778      * @type JXG.Point
1779      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
1780      * @param {JXG.Line,JXG.Circle_JXG.Line,JXG.Circle_Number} el1,el2,i The result will be a intersection point on el1 and el2. i determines the
1781      * intersection point if two points are available: <ul>
1782      *   <li>i==0: use the positive square root,</li>
1783      *   <li>i==1: use the negative square root.</li></ul>
1784      * @example
1785      * // Create an intersection point of circle and line
1786      * var p1 = board.create('point', [2.0, 2.0]);
1787      * var c1 = board.create('circle', [p1, 2.0]);
1788      *
1789      * var p2 = board.create('point', [2.0, 2.0]);
1790      * var p3 = board.create('point', [2.0, 2.0]);
1791      * var l1 = board.create('line', [p2, p3]);
1792      *
1793      * var i = board.create('intersection', [c1, l1, 0]);
1794      * </pre><div id="e5b0e190-5200-4bc3-b995-b6cc53dc5dc0" style="width: 300px; height: 300px;"></div>
1795      * <script type="text/javascript">
1796      *   var ipex1_board = JXG.JSXGraph.initBoard('e5b0e190-5200-4bc3-b995-b6cc53dc5dc0', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
1797      *   var ipex1_p1 = ipex1_board.create('point', [4.0, 4.0]);
1798      *   var ipex1_c1 = ipex1_board.create('circle', [ipex1_p1, 2.0]);
1799      *   var ipex1_p2 = ipex1_board.create('point', [1.0, 1.0]);
1800      *   var ipex1_p3 = ipex1_board.create('point', [5.0, 3.0]);
1801      *   var ipex1_l1 = ipex1_board.create('line', [ipex1_p2, ipex1_p3]);
1802      *   var ipex1_i = ipex1_board.create('intersection', [ipex1_c1, ipex1_l1, 0]);
1803      * </script><pre>
1804      */
1805     JXG.createIntersectionPoint = function (board, parents, attributes) {
1806         var el, el1, el2, func, i, j,
1807             attr = Type.copyAttributes(attributes, board.options, 'intersection');
1808 
1809 
1810         // make sure we definitely have the indices
1811         parents.push(0, 0);
1812 
1813         el = board.create('point', [0, 0, 0], attr);
1814 
1815         el1 = board.select(parents[0]);
1816         el2 = board.select(parents[1]);
1817 
1818         i = parents[2] || 0;
1819         j = parents[3] || 0;
1820 
1821         if (el1.elementClass === Const.OBJECT_CLASS_CURVE &&
1822                 el2.elementClass === Const.OBJECT_CLASS_CURVE) {
1823             // curve - curve
1824             /** @ignore */
1825             func = function () {
1826                 return Geometry.meetCurveCurve(el1, el2, i, j, el1.board);
1827             };
1828 
1829         //} else if ((el1.type === Const.OBJECT_TYPE_ARC && el2.elementClass === Const.OBJECT_CLASS_LINE) ||
1830 //                (el2.type === Const.OBJECT_TYPE_ARC && el1.elementClass === Const.OBJECT_CLASS_LINE)) {
1831             // arc - line   (arcs are of class curve, but are intersected like circles)
1832             // TEMPORARY FIX!!!
1833             /** @ignore */
1834 //            func = function () {
1835                 //return Geometry.meet(el1.stdform, el2.stdform, i, el1.board);
1836             //};
1837 
1838         } else if ((el1.elementClass === Const.OBJECT_CLASS_CURVE && el2.elementClass === Const.OBJECT_CLASS_LINE) ||
1839                 (el2.elementClass === Const.OBJECT_CLASS_CURVE && el1.elementClass === Const.OBJECT_CLASS_LINE)) {
1840             // curve - line (this includes intersections between conic sections and lines
1841             /** @ignore */
1842             func = function () {
1843                 return Geometry.meetCurveLine(el1, el2, i, el1.board, el.visProp.alwaysintersect);
1844             };
1845 
1846         } else if (el1.elementClass === Const.OBJECT_CLASS_LINE && el2.elementClass === Const.OBJECT_CLASS_LINE) {
1847             // line - line, lines may also be segments.
1848             /** @ignore */
1849             func = function () {
1850                 var res, c,
1851                     first1 = el1.visProp.straightfirst,
1852                     first2 = el2.visProp.straightfirst,
1853                     last1 = el1.visProp.straightlast,
1854                     last2 = el2.visProp.straightlast;
1855 
1856                 /**
1857                  * If one of the lines is a segment or ray and
1858                  * the the intersection point shpould disappear if outside
1859                  * of the segment or ray we call
1860                  * meetSegmentSegment
1861                  */
1862                 if (!el.visProp.alwaysintersect && (!first1 || !last1 || !first2 || !last2)) {
1863                     res = Geometry.meetSegmentSegment(
1864                         el1.point1.coords.usrCoords,
1865                         el1.point2.coords.usrCoords,
1866                         el2.point1.coords.usrCoords,
1867                         el2.point2.coords.usrCoords,
1868                         el1.board
1869                     );
1870 
1871                     if ((!first1 && res[1] < 0) || (!last1 && res[1] > 1) ||
1872                             (!first2 && res[2] < 0) || (!last2 && res[2] > 1)) {
1873                         // Non-existent
1874                         c = [0, NaN, NaN];
1875                     } else {
1876                         c = res[0];
1877                     }
1878 
1879                     return (new Coords(Const.COORDS_BY_USER, c, el1.board));
1880                 }
1881 
1882                 return Geometry.meet(el1.stdform, el2.stdform, i, el1.board);
1883             };
1884         } else {
1885             // All other combinations of circles and lines
1886             /** @ignore */
1887             func = function () {
1888                 return Geometry.meet(el1.stdform, el2.stdform, i, el1.board);
1889             };
1890         }
1891 
1892         el.addConstraint([func]);
1893 
1894         try {
1895             el1.addChild(el);
1896             el2.addChild(el);
1897         } catch (e) {
1898             throw new Error("JSXGraph: Can't create 'intersection' with parent types '" +
1899                 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'.");
1900         }
1901 
1902         el.type = Const.OBJECT_TYPE_INTERSECTION;
1903         el.elType = 'intersection';
1904         el.parents = [el1.id, el2.id, i, j];
1905 
1906         el.generatePolynomial = function () {
1907             var poly1 = el1.generatePolynomial(el),
1908                 poly2 = el2.generatePolynomial(el);
1909 
1910             if ((poly1.length === 0) || (poly2.length === 0)) {
1911                 return [];
1912             }
1913 
1914             return [poly1[0], poly2[0]];
1915         };
1916 
1917         return el;
1918     };
1919 
1920     /**
1921      * @class This element is used to provide a constructor for the "other" intersection point.
1922      * @pseudo
1923      * @description An intersection point is a point which lives on two Lines or Circles or one Line and one Circle at the same time, i.e.
1924      * an intersection point of the two elements. Additionally, one intersection point is provided. The function returns the other intersection point.
1925      * @name OtherIntersection
1926      * @augments JXG.Point
1927      * @constructor
1928      * @type JXG.Point
1929      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
1930      * @param {JXG.Line,JXG.Circle_JXG.Line,JXG.Circle_JXG.Point} el1,el2,p The result will be a intersection point on el1 and el2. i determines the
1931      * intersection point different from p:
1932      * @example
1933      * // Create an intersection point of circle and line
1934      * var p1 = board.create('point', [2.0, 2.0]);
1935      * var c1 = board.create('circle', [p1, 2.0]);
1936      *
1937      * var p2 = board.create('point', [2.0, 2.0]);
1938      * var p3 = board.create('point', [2.0, 2.0]);
1939      * var l1 = board.create('line', [p2, p3]);
1940      *
1941      * var i = board.create('intersection', [c1, l1, 0]);
1942      * var j = board.create('otherintersection', [c1, l1, i]);
1943      * </pre><div id="45e25f12-a1de-4257-a466-27a2ae73614c" style="width: 300px; height: 300px;"></div>
1944      * <script type="text/javascript">
1945      *   var ipex2_board = JXG.JSXGraph.initBoard('45e25f12-a1de-4257-a466-27a2ae73614c', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
1946      *   var ipex2_p1 = ipex2_board.create('point', [4.0, 4.0]);
1947      *   var ipex2_c1 = ipex2_board.create('circle', [ipex2_p1, 2.0]);
1948      *   var ipex2_p2 = ipex2_board.create('point', [1.0, 1.0]);
1949      *   var ipex2_p3 = ipex2_board.create('point', [5.0, 3.0]);
1950      *   var ipex2_l1 = ipex2_board.create('line', [ipex2_p2, ipex2_p3]);
1951      *   var ipex2_i = ipex2_board.create('intersection', [ipex2_c1, ipex2_l1, 0], {name:'D'});
1952      *   var ipex2_j = ipex2_board.create('otherintersection', [ipex2_c1, ipex2_l1, ipex2_i], {name:'E'});
1953      * </script><pre>
1954      */
1955     JXG.createOtherIntersectionPoint = function (board, parents, attributes) {
1956         var el, el1, el2, other;
1957 
1958         if (parents.length !== 3 ||
1959                 !Type.isPoint(parents[2]) ||
1960                 (parents[0].elementClass !== Const.OBJECT_CLASS_LINE && parents[0].elementClass !== Const.OBJECT_CLASS_CIRCLE) ||
1961                 (parents[1].elementClass !== Const.OBJECT_CLASS_LINE && parents[1].elementClass !== Const.OBJECT_CLASS_CIRCLE)) {
1962             // Failure
1963             throw new Error("JSXGraph: Can't create 'other intersection point' with parent types '" +
1964                 (typeof parents[0]) + "',  '" + (typeof parents[1]) + "'and  '" + (typeof parents[2]) + "'." +
1965                 "\nPossible parent types: [circle|line,circle|line,point]");
1966         }
1967 
1968         el1 = board.select(parents[0]);
1969         el2 = board.select(parents[1]);
1970         other = board.select(parents[2]);
1971 
1972         el = board.create('point', [function () {
1973             var c = Geometry.meet(el1.stdform, el2.stdform, 0, el1.board);
1974 
1975             if (Math.abs(other.X() - c.usrCoords[1]) > Mat.eps ||
1976                     Math.abs(other.Y() - c.usrCoords[2]) > Mat.eps ||
1977                     Math.abs(other.Z() - c.usrCoords[0]) > Mat.eps) {
1978                 return c;
1979             }
1980 
1981             return Geometry.meet(el1.stdform, el2.stdform, 1, el1.board);
1982         }], attributes);
1983 
1984         el.type = Const.OBJECT_TYPE_INTERSECTION;
1985         el.elType = 'otherintersection';
1986         el.parents = [el1.id, el2.id, other];
1987 
1988         el1.addChild(el);
1989         el2.addChild(el);
1990 
1991         el.generatePolynomial = function () {
1992             var poly1 = el1.generatePolynomial(el),
1993                 poly2 = el2.generatePolynomial(el);
1994 
1995             if ((poly1.length === 0) || (poly2.length === 0)) {
1996                 return [];
1997             }
1998 
1999             return [poly1[0], poly2[0]];
2000         };
2001 
2002         return el;
2003     };
2004 
2005 
2006     JXG.registerElement('point', JXG.createPoint);
2007     JXG.registerElement('glider', JXG.createGlider);
2008     JXG.registerElement('intersection', JXG.createIntersectionPoint);
2009     JXG.registerElement('otherintersection', JXG.createOtherIntersectionPoint);
2010 
2011     return {
2012         Point: JXG.Point,
2013         createPoint: JXG.createPoint,
2014         createGlider: JXG.createGlider,
2015         createIntersection: JXG.createIntersectionPoint,
2016         createOtherIntersection: JXG.createOtherIntersectionPoint
2017     };
2018 });
2019