1 /*
  2     Copyright 2008-2016
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Alfred Wassermann
  7 
  8     This file is part of JSXGraph.
  9 
 10     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 11 
 12     You can redistribute it and/or modify it under the terms of the
 13 
 14       * GNU Lesser General Public License as published by
 15         the Free Software Foundation, either version 3 of the License, or
 16         (at your option) any later version
 17       OR
 18       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 19 
 20     JSXGraph is distributed in the hope that it will be useful,
 21     but WITHOUT ANY WARRANTY; without even the implied warranty of
 22     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 23     GNU Lesser General Public License for more details.
 24 
 25     You should have received a copy of the GNU Lesser General Public License and
 26     the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
 27     and <http://opensource.org/licenses/MIT/>.
 28  */
 29 
 30 
 31 /*global JXG: true, define: true, console: true, window: true*/
 32 /*jslint nomen: true, plusplus: true*/
 33 
 34 /* depends:
 35  jxg
 36  options
 37  math/math
 38  math/geometry
 39  math/numerics
 40  base/coords
 41  base/constants
 42  base/element
 43  parser/geonext
 44  utils/type
 45   elements:
 46    transform
 47  */
 48 
 49 /**
 50  * @fileoverview The geometry object CoordsElement is defined in this file.
 51  * This object provides the coordinate handling of points, images and texts.
 52  */
 53 
 54 define([
 55     'jxg', 'options', 'math/math', 'math/geometry', 'math/numerics', 'math/statistics', 'base/coords', 'base/constants', 'base/element',
 56     'parser/geonext', 'utils/type', 'base/transformation'
 57 ], function (JXG, Options, Mat, Geometry, Numerics, Statistics, Coords, Const, GeometryElement, GeonextParser, Type, Transform) {
 58 
 59     "use strict";
 60 
 61     /**
 62      * An element containing coords is the basic geometric element. Based on points lines and circles can be constructed which can be intersected
 63      * which in turn are points again which can be used to construct new lines, circles, polygons, etc. This class holds methods for
 64      * all kind of coordinate elements like points, texts and images.
 65      * @class Creates a new coords element object. Do not use this constructor to create an element.
 66      *
 67      * @private
 68      * @augments JXG.GeometryElement
 69      * @param {Array} coordinates An array with the affine user coordinates of the point.
 70      * {@link JXG.Options#elements}, and - optionally - a name and an id.
 71      */
 72     JXG.CoordsElement = function (coordinates, isLabel) {
 73         var i;
 74 
 75         if (!Type.exists(coordinates)) {
 76             coordinates = [1, 0, 0];
 77         }
 78 
 79         for (i = 0; i < coordinates.length; ++i) {
 80             coordinates[i] = parseFloat(coordinates[i]);
 81         }
 82 
 83         /**
 84          * Coordinates of the element.
 85          * @type JXG.Coords
 86          * @private
 87          */
 88         this.coords = new Coords(Const.COORDS_BY_USER, coordinates, this.board);
 89         this.initialCoords = new Coords(Const.COORDS_BY_USER, coordinates, this.board);
 90 
 91         /**
 92          * Relative position on a slide element (line, circle, curve) if element is a glider on this element.
 93          * @type Number
 94          * @private
 95          */
 96         this.position = null;
 97 
 98         /**
 99          * Determines whether the element slides on a polygon if point is a glider.
100          * @type boolean
101          * @default false
102          * @private
103          */
104         this.onPolygon = false;
105 
106         /**
107          * When used as a glider this member stores the object, where to glide on.
108          * To set the object to glide on use the method
109          * {@link JXG.Point#makeGlider} and DO NOT set this property directly
110          * 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 element is bound to, i.e. the element 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.CoordsElement#updateGlider} call is usually followed
125          * by a general {@link JXG.Board#update} which calls
126          * {@link JXG.CoordsElement#updateGliderFromParent}.
127          * To prevent double updates, {@link JXG.CoordsElement#needsUpdateFromParent}
128          * is set to false in updateGlider() and reset to true in the following call to
129          * {@link JXG.CoordsElement#updateGliderFromParent}
130          * @type {Boolean}
131          */
132         this.needsUpdateFromParent = true;
133 
134         /**
135          * Dummy function for unconstrained points or gliders.
136          * @private
137          */
138         this.updateConstraint = function () {
139             return this;
140         };
141 
142         /**
143          * Stores the groups of this element in an array of Group.
144          * @type array
145          * @see JXG.Group
146          * @private
147          */
148         this.groups = [];
149 
150         /*
151          * Do we need this?
152          */
153         this.Xjc = null;
154         this.Yjc = null;
155 
156         // documented in GeometryElement
157         this.methodMap = Type.deepCopy(this.methodMap, {
158             move: 'moveTo',
159             moveTo: 'moveTo',
160             moveAlong: 'moveAlong',
161             visit: 'visit',
162             glide: 'makeGlider',
163             makeGlider: 'makeGlider',
164             intersect: 'makeIntersection',
165             makeIntersection: 'makeIntersection',
166             X: 'X',
167             Y: 'Y',
168             free: 'free',
169             setPosition: 'setGliderPosition',
170             setGliderPosition: 'setGliderPosition',
171             addConstraint: 'addConstraint',
172             dist: 'Dist',
173             onPolygon: 'onPolygon'
174         });
175 
176         /*
177          * this.element may have been set by the object constructor.
178          */
179         if (Type.exists(this.element)) {
180             this.addAnchor(coordinates, isLabel);
181         }
182         this.isDraggable = true;
183 
184     };
185 
186     JXG.extend(JXG.CoordsElement.prototype, /** @lends JXG.CoordsElement.prototype */ {
187         /**
188          * Updates the coordinates of the element.
189          * @private
190          */
191         updateCoords: function (fromParent) {
192             if (!this.needsUpdate) {
193                 return this;
194             }
195 
196             if (!Type.exists(fromParent)) {
197                 fromParent = false;
198             }
199 
200             /*
201              * We need to calculate the new coordinates no matter of the elements visibility because
202              * a child could be visible and depend on the coordinates of the element/point (e.g. perpendicular).
203              *
204              * Check if the element is a glider and calculate new coords in dependency of this.slideObject.
205              * This function is called with fromParent==true in case it is a glider element for example if
206              * the defining elements of the line or circle have been changed.
207              */
208             if (this.type === Const.OBJECT_TYPE_GLIDER) {
209                 if (fromParent) {
210                     this.updateGliderFromParent();
211                 } else {
212                     this.updateGlider();
213                 }
214             }
215 
216             if (!this.visProp.frozen) {
217                 this.updateConstraint();
218             }
219             this.updateTransform();
220 
221             return this;
222         },
223 
224         /**
225          * Update of glider in case of dragging the glider or setting the postion of the glider.
226          * The relative position of the glider has to be updated.
227          *
228          * In case of a glider on a line:
229          * If the second point is an ideal point, then -1 < this.position < 1,
230          * this.position==+/-1 equals point2, this.position==0 equals point1
231          *
232          * If the first point is an ideal point, then 0 < this.position < 2
233          * this.position==0  or 2 equals point1, this.position==1 equals point2
234          *
235          * @private
236          */
237         updateGlider: function () {
238             var i, p1c, p2c, d, v, poly, cc, pos, sgn,
239                 alpha, beta,
240                 delta = 2.0 * Math.PI,
241                 angle,
242                 cp, c, invMat, newCoords, newPos,
243                 doRound = false,
244                 slide = this.slideObject;
245 
246             this.needsUpdateFromParent = false;
247             if (slide.elementClass === Const.OBJECT_CLASS_CIRCLE) {
248                 if (this.visProp.isgeonext) {
249                     delta = 1.0;
250                 }
251                 //this.coords.setCoordinates(Const.COORDS_BY_USER,
252                 //    Geometry.projectPointToCircle(this, slide, this.board).usrCoords, false);
253                 newCoords = Geometry.projectPointToCircle(this, slide, this.board);
254                 newPos = Geometry.rad([slide.center.X() + 1.0, slide.center.Y()], slide.center, this) / delta;
255             } else if (slide.elementClass === Const.OBJECT_CLASS_LINE) {
256                 /*
257                  * onPolygon==true: the point is a slider on a segment and this segment is one of the
258                  * "borders" of a polygon.
259                  * This is a GEONExT feature.
260                  */
261                 if (this.onPolygon) {
262                     p1c = slide.point1.coords.usrCoords;
263                     p2c = slide.point2.coords.usrCoords;
264                     i = 1;
265                     d = p2c[i] - p1c[i];
266 
267                     if (Math.abs(d) < Mat.eps) {
268                         i = 2;
269                         d = p2c[i] - p1c[i];
270                     }
271 
272                     cc = Geometry.projectPointToLine(this, slide, this.board);
273                     pos = (cc.usrCoords[i] - p1c[i]) / d;
274                     poly = slide.parentPolygon;
275 
276                     if (pos < 0) {
277                         for (i = 0; i < poly.borders.length; i++) {
278                             if (slide === poly.borders[i]) {
279                                 slide = poly.borders[(i - 1 + poly.borders.length) % poly.borders.length];
280                                 break;
281                             }
282                         }
283                     } else if (pos > 1.0) {
284                         for (i = 0; i < poly.borders.length; i++) {
285                             if (slide === poly.borders[i]) {
286                                 slide = poly.borders[(i + 1 + poly.borders.length) % poly.borders.length];
287                                 break;
288                             }
289                         }
290                     }
291 
292                     // If the slide object has changed, save the change to the glider.
293                     if (slide.id !== this.slideObject.id) {
294                         this.slideObject = slide;
295                     }
296                 }
297 
298                 p1c = slide.point1.coords;
299                 p2c = slide.point2.coords;
300 
301                 // Distance between the two defining points
302                 d = p1c.distance(Const.COORDS_BY_USER, p2c);
303 
304                 // The defining points are identical
305                 if (d < Mat.eps) {
306                     //this.coords.setCoordinates(Const.COORDS_BY_USER, p1c);
307                     newCoords = p1c;
308                     doRound = true;
309                     newPos = 0.0;
310                 } else {
311                     //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToLine(this, slide, this.board).usrCoords, false);
312                     newCoords = Geometry.projectPointToLine(this, slide, this.board);
313                     p1c = p1c.usrCoords.slice(0);
314                     p2c = p2c.usrCoords.slice(0);
315 
316                     // The second point is an ideal point
317                     if (Math.abs(p2c[0]) < Mat.eps) {
318                         i = 1;
319                         d = p2c[i];
320 
321                         if (Math.abs(d) < Mat.eps) {
322                             i = 2;
323                             d = p2c[i];
324                         }
325 
326                         d = (newCoords.usrCoords[i] - p1c[i]) / d;
327                         sgn = (d >= 0) ? 1 : -1;
328                         d = Math.abs(d);
329                         newPos = sgn * d / (d + 1);
330 
331                     // The first point is an ideal point
332                     } else if (Math.abs(p1c[0]) < Mat.eps) {
333                         i = 1;
334                         d = p1c[i];
335 
336                         if (Math.abs(d) < Mat.eps) {
337                             i = 2;
338                             d = p1c[i];
339                         }
340 
341                         d = (newCoords.usrCoords[i] - p2c[i]) / d;
342 
343                         // 1.0 - d/(1-d);
344                         if (d < 0.0) {
345                             newPos = (1 - 2.0 * d) / (1.0 - d);
346                         } else {
347                             newPos = 1 / (d + 1);
348                         }
349                     } else {
350                         i = 1;
351                         d = p2c[i] - p1c[i];
352 
353                         if (Math.abs(d) < Mat.eps) {
354                             i = 2;
355                             d = p2c[i] - p1c[i];
356                         }
357                         newPos = (newCoords.usrCoords[i] - p1c[i]) / d;
358                     }
359                 }
360 
361                 // Snap the glider point of the slider into its appropiate position
362                 // First, recalculate the new value of this.position
363                 // Second, call update(fromParent==true) to make the positioning snappier.
364                 if (this.visProp.snapwidth > 0.0 && Math.abs(this._smax - this._smin) >= Mat.eps) {
365                     newPos = Math.max(Math.min(newPos, 1), 0);
366 
367                     v = newPos * (this._smax - this._smin) + this._smin;
368                     v = Math.round(v / this.visProp.snapwidth) * this.visProp.snapwidth;
369                     newPos = (v - this._smin) / (this._smax - this._smin);
370                     this.update(true);
371                 }
372 
373                 p1c = slide.point1.coords;
374                 if (!slide.visProp.straightfirst && Math.abs(p1c.usrCoords[0]) > Mat.eps && newPos < 0) {
375                     //this.coords.setCoordinates(Const.COORDS_BY_USER, p1c);
376                     newCoords = p1c;
377                     doRound = true;
378                     newPos = 0;
379                 }
380 
381                 p2c = slide.point2.coords;
382                 if (!slide.visProp.straightlast && Math.abs(p2c.usrCoords[0]) > Mat.eps && newPos > 1) {
383                     //this.coords.setCoordinates(Const.COORDS_BY_USER, p2c);
384                     newCoords = p2c;
385                     doRound = true;
386                     newPos = 1;
387                 }
388             } else if (slide.type === Const.OBJECT_TYPE_TURTLE) {
389                 // In case, the point is a constrained glider.
390                 // side-effect: this.position is overwritten
391                 this.updateConstraint();
392                 //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToTurtle(this, slide, this.board).usrCoords, false);
393                 newCoords = Geometry.projectPointToTurtle(this, slide, this.board);
394                 newPos = this.position;     // save position for the overwriting below
395             } else if (slide.elementClass === Const.OBJECT_CLASS_CURVE) {
396                 if ((slide.type === Const.OBJECT_TYPE_ARC ||
397                         slide.type === Const.OBJECT_TYPE_SECTOR)) {
398                     newCoords = Geometry.projectPointToCircle(this, slide, this.board);
399 
400                     angle = Geometry.rad(slide.radiuspoint, slide.center, this);
401                     alpha = 0.0;
402                     beta = Geometry.rad(slide.radiuspoint, slide.center, slide.anglepoint);
403                     newPos = angle;
404 
405                     if ((slide.visProp.selection === 'minor' && beta > Math.PI) ||
406                             (slide.visProp.selection === 'major' && beta < Math.PI)) {
407                         alpha = beta;
408                         beta = 2 * Math.PI;
409                     }
410 
411                     // Correct the position if we are outside of the sector/arc
412                     if (angle < alpha || angle > beta) {
413                         newPos = beta;
414 
415                         if ((angle < alpha && angle > alpha * 0.5) || (angle > beta && angle > beta * 0.5 + Math.PI)) {
416                             newPos = alpha;
417                         }
418 
419                         this.needsUpdateFromParent = true;
420                         this.updateGliderFromParent();
421                     }
422 
423                     delta = beta - alpha;
424                     if (this.visProp.isgeonext) {
425                         delta = 1.0;
426                     }
427                     if (Math.abs(delta) > Mat.eps) {
428                         newPos /= delta;
429                     }
430                 } else {
431                     // In case, the point is a constrained glider.
432                     this.updateConstraint();
433 
434                     if (slide.transformations.length > 0) {
435                         slide.updateTransformMatrix();
436                         invMat = Mat.inverse(slide.transformMat);
437                         c = Mat.matVecMult(invMat, this.coords.usrCoords);
438 
439                         cp = (new Coords(Const.COORDS_BY_USER, c, this.board)).usrCoords;
440                         c = Geometry.projectCoordsToCurve(cp[1], cp[2], this.position || 0, slide, this.board);
441 
442                         newCoords = c[0];
443                         newPos = c[1];
444                     } else {
445                         // side-effect: this.position is overwritten
446                         //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToCurve(this, slide, this.board).usrCoords, false);
447                         newCoords = Geometry.projectPointToCurve(this, slide, this.board);
448                         newPos = this.position; // save position for the overwriting below
449                     }
450                 }
451             } else if (Type.isPoint(slide)) {
452                 //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToPoint(this, slide, this.board).usrCoords, false);
453                 newCoords = Geometry.projectPointToPoint(this, slide, this.board);
454                 newPos = this.position; // save position for the overwriting below
455             }
456 
457             this.coords.setCoordinates(Const.COORDS_BY_USER, newCoords.usrCoords, doRound);
458             this.position = newPos;
459         },
460 
461         /**
462          * Update of a glider in case a parent element has been updated. That means the
463          * relative position of the glider stays the same.
464          * @private
465          */
466         updateGliderFromParent: function () {
467             var p1c, p2c, r, lbda, c,
468                 slide = this.slideObject,
469                 baseangle, alpha, angle, beta,
470                 delta = 2.0 * Math.PI,
471                 newPos;
472 
473             if (!this.needsUpdateFromParent) {
474                 this.needsUpdateFromParent = true;
475                 return;
476             }
477 
478             if (slide.elementClass === Const.OBJECT_CLASS_CIRCLE) {
479                 r = slide.Radius();
480                 if (this.visProp.isgeonext) {
481                     delta = 1.0;
482                 }
483                 c = [
484                     slide.center.X() + r * Math.cos(this.position * delta),
485                     slide.center.Y() + r * Math.sin(this.position * delta)
486                 ];
487             } else if (slide.elementClass === Const.OBJECT_CLASS_LINE) {
488                 p1c = slide.point1.coords.usrCoords;
489                 p2c = slide.point2.coords.usrCoords;
490 
491                 // If one of the defining points of the line does not exist,
492                 // the glider should disappear
493                 if ((p1c[0] === 0 && p1c[1] === 0 && p1c[2] === 0) ||
494                     (p2c[0] === 0 && p2c[1] === 0 && p2c[2] === 0)) {
495                     c = [0, 0, 0];
496                 // The second point is an ideal point
497                 } else if (Math.abs(p2c[0]) < Mat.eps) {
498                     lbda = Math.min(Math.abs(this.position), 1 - Mat.eps);
499                     lbda /= (1.0 - lbda);
500 
501                     if (this.position < 0) {
502                         lbda = -lbda;
503                     }
504 
505                     c = [
506                         p1c[0] + lbda * p2c[0],
507                         p1c[1] + lbda * p2c[1],
508                         p1c[2] + lbda * p2c[2]
509                     ];
510                 // The first point is an ideal point
511                 } else if (Math.abs(p1c[0]) < Mat.eps) {
512                     lbda = Math.max(this.position, Mat.eps);
513                     lbda = Math.min(lbda, 2 - Mat.eps);
514 
515                     if (lbda > 1) {
516                         lbda = (lbda - 1) / (lbda - 2);
517                     } else {
518                         lbda = (1 - lbda) / lbda;
519                     }
520 
521                     c = [
522                         p2c[0] + lbda * p1c[0],
523                         p2c[1] + lbda * p1c[1],
524                         p2c[2] + lbda * p1c[2]
525                     ];
526                 } else {
527                     lbda = this.position;
528                     c = [
529                         p1c[0] + lbda * (p2c[0] - p1c[0]),
530                         p1c[1] + lbda * (p2c[1] - p1c[1]),
531                         p1c[2] + lbda * (p2c[2] - p1c[2])
532                     ];
533                 }
534             } else if (slide.type === Const.OBJECT_TYPE_TURTLE) {
535                 this.coords.setCoordinates(Const.COORDS_BY_USER, [slide.Z(this.position), slide.X(this.position), slide.Y(this.position)]);
536                 // In case, the point is a constrained glider.
537                 // side-effect: this.position is overwritten:
538                 this.updateConstraint();
539                 c  = Geometry.projectPointToTurtle(this, slide, this.board).usrCoords;
540             } else if (slide.elementClass === Const.OBJECT_CLASS_CURVE) {
541                 this.coords.setCoordinates(Const.COORDS_BY_USER, [slide.Z(this.position), slide.X(this.position), slide.Y(this.position)]);
542 
543                 if (slide.type === Const.OBJECT_TYPE_ARC || slide.type === Const.OBJECT_TYPE_SECTOR) {
544                     baseangle = Geometry.rad([slide.center.X() + 1, slide.center.Y()], slide.center, slide.radiuspoint);
545 
546                     alpha = 0.0;
547                     beta = Geometry.rad(slide.radiuspoint, slide.center, slide.anglepoint);
548 
549                     if ((slide.visProp.selection === 'minor' && beta > Math.PI) ||
550                             (slide.visProp.selection === 'major' && beta < Math.PI)) {
551                         alpha = beta;
552                         beta = 2 * Math.PI;
553                     }
554 
555                     delta = beta - alpha;
556                     if (this.visProp.isgeonext) {
557                         delta = 1.0;
558                     }
559                     angle = this.position * delta;
560 
561                     // Correct the position if we are outside of the sector/arc
562                     if (angle < alpha || angle > beta) {
563                         angle = beta;
564 
565                         if ((angle < alpha && angle > alpha * 0.5) ||
566                                 (angle > beta && angle > beta * 0.5 + Math.PI)) {
567                             angle = alpha;
568                         }
569 
570                         this.position = angle;
571                         if (Math.abs(delta) > Mat.eps) {
572                             this.position /= delta;
573                         }
574                     }
575 
576                     r = slide.Radius();
577                     c = [
578                         slide.center.X() + r * Math.cos(this.position * delta + baseangle),
579                         slide.center.Y() + r * Math.sin(this.position * delta + baseangle)
580                     ];
581                 } else {
582                     // In case, the point is a constrained glider.
583                     // side-effect: this.position is overwritten
584                     this.updateConstraint();
585                     c = Geometry.projectPointToCurve(this, slide, this.board).usrCoords;
586                 }
587 
588             } else if (Type.isPoint(slide)) {
589                 c = Geometry.projectPointToPoint(this, slide, this.board).usrCoords;
590             }
591 
592             this.coords.setCoordinates(Const.COORDS_BY_USER, c, false);
593         },
594 
595         updateRendererGeneric: function (rendererMethod) {
596             var wasReal;
597 
598             if (!this.needsUpdate) {
599                 return this;
600             }
601 
602             /* Call the renderer only if point is visible. */
603             if (this.visProp.visible) {
604                 wasReal = this.isReal;
605                 this.isReal = (!isNaN(this.coords.usrCoords[1] + this.coords.usrCoords[2]));
606                 //Homogeneous coords: ideal point
607                 this.isReal = (Math.abs(this.coords.usrCoords[0]) > Mat.eps) ? this.isReal : false;
608 
609                 if (this.isReal) {
610                     if (wasReal !== this.isReal) {
611                         this.board.renderer.show(this);
612 
613                         if (this.hasLabel && this.label.visProp.visible) {
614                             this.board.renderer.show(this.label);
615                         }
616                     }
617                     this.board.renderer[rendererMethod](this);
618                 } else {
619                     if (wasReal !== this.isReal) {
620                         this.board.renderer.hide(this);
621 
622                         if (this.hasLabel && this.label.visProp.visible) {
623                             this.board.renderer.hide(this.label);
624                         }
625                     }
626                 }
627             }
628 
629             /* Update the label if visible. */
630             if (this.hasLabel && this.visProp.visible && this.label && this.label.visProp.visible && this.isReal) {
631                 this.label.update();
632                 this.board.renderer.updateText(this.label);
633             }
634 
635             this.needsUpdate = false;
636 
637             return this;
638         },
639 
640         /**
641          * Getter method for x, this is used by for CAS-points to access point coordinates.
642          * @returns {Number} User coordinate of point in x direction.
643          */
644         X: function () {
645             return this.coords.usrCoords[1];
646         },
647 
648         /**
649          * Getter method for y, this is used by CAS-points to access point coordinates.
650          * @returns {Number} User coordinate of point in y direction.
651          */
652         Y: function () {
653             return this.coords.usrCoords[2];
654         },
655 
656         /**
657          * Getter method for z, this is used by CAS-points to access point coordinates.
658          * @returns {Number} User coordinate of point in z direction.
659          */
660         Z: function () {
661             return this.coords.usrCoords[0];
662         },
663 
664         /**
665          * New evaluation of the function term.
666          * This is required for CAS-points: Their XTerm() method is
667          * overwritten in {@link JXG.CoordsElement#addConstraint}.
668          *
669          * @returns {Number} User coordinate of point in x direction.
670          * @private
671          */
672         XEval: function () {
673             return this.coords.usrCoords[1];
674         },
675 
676         /**
677          * New evaluation of the function term.
678          * This is required for CAS-points: Their YTerm() method is overwritten
679          * in {@link JXG.CoordsElement#addConstraint}.
680          *
681          * @returns {Number} User coordinate of point in y direction.
682          * @private
683          */
684         YEval: function () {
685             return this.coords.usrCoords[2];
686         },
687 
688         /**
689          * New evaluation of the function term.
690          * This is required for CAS-points: Their ZTerm() method is overwritten in
691          * {@link JXG.CoordsElement#addConstraint}.
692          *
693          * @returns {Number} User coordinate of point in z direction.
694          * @private
695          */
696         ZEval: function () {
697             return this.coords.usrCoords[0];
698         },
699 
700         /**
701          * Getter method for the distance to a second point, this is required for CAS-elements.
702          * Here, function inlining seems to be worthwile  (for plotting).
703          * @param {JXG.Point} point2 The point to which the distance shall be calculated.
704          * @returns {Number} Distance in user coordinate to the given point
705          */
706         Dist: function (point2) {
707             if (this.isReal && point2.isReal) {
708                 return this.coords.distance(Const.COORDS_BY_USER, point2.coords);
709             }
710             return NaN;
711         },
712 
713         /**
714          * Alias for {@link JXG.Element#handleSnapToGrid}
715          * @param {Boolean} force force snapping independent from what the snaptogrid attribute says
716          * @returns {JXG.Point} Reference to this element
717          */
718         snapToGrid: function (force) {
719             return this.handleSnapToGrid(force);
720         },
721 
722         /**
723          * Let a point snap to the nearest point in distance of
724          * {@link JXG.Point#attractorDistance}.
725          * The function uses the coords object of the point as
726          * its actual position.
727          * @param {Boolean} force force snapping independent from what the snaptogrid attribute says
728          * @returns {JXG.Point} Reference to this element
729          */
730         handleSnapToPoints: function (force) {
731             var i, pEl, pCoords,
732                 d = 0,
733                 len,
734                 dMax = Infinity,
735                 c = null,
736                 len2, j, ignore = false;
737 
738             len = this.board.objectsList.length;
739 
740             if (this.visProp.ignoredsnaptopoints) {
741                 len2 = this.visProp.ignoredsnaptopoints.length;
742             }
743 
744             if (this.visProp.snaptopoints || force) {
745                 for (i = 0; i < len; i++) {
746                     pEl = this.board.objectsList[i];
747 
748                     if (this.visProp.ignoredsnaptopoints) {
749                         ignore = false;
750                         for (j = 0; j < len2; j++) {
751                             if (pEl == this.board.select(this.visProp.ignoredsnaptopoints[j])) {
752                                 ignore = true;
753                                 break;
754                             }
755                         }
756                         if (ignore) {
757                             continue;
758                         }
759                     }
760 
761                     if (Type.isPoint(pEl) && pEl !== this && pEl.visProp.visible) {
762                         pCoords = Geometry.projectPointToPoint(this, pEl, this.board);
763                         if (this.visProp.attractorunit === 'screen') {
764                             d = pCoords.distance(Const.COORDS_BY_SCREEN, this.coords);
765                         } else {
766                             d = pCoords.distance(Const.COORDS_BY_USER, this.coords);
767                         }
768 
769                         if (d < this.visProp.attractordistance && d < dMax) {
770                             dMax = d;
771                             c = pCoords;
772                         }
773                     }
774                 }
775 
776                 if (c !== null) {
777                     this.coords.setCoordinates(Const.COORDS_BY_USER, c.usrCoords);
778                 }
779             }
780 
781             return this;
782         },
783 
784         /**
785          * Alias for {@link JXG.CoordsElement#handleSnapToPoints}.
786          *
787          * @param {Boolean} force force snapping independent from what the snaptogrid attribute says
788          * @returns {JXG.Point} Reference to this element
789          */
790         snapToPoints: function (force) {
791             return this.handleSnapToPoints(force);
792         },
793 
794         /**
795          * A point can change its type from free point to glider
796          * and vice versa. If it is given an array of attractor elements
797          * (attribute attractors) and the attribute attractorDistance
798          * then the point will be made a glider if it less than attractorDistance
799          * apart from one of its attractor elements.
800          * If attractorDistance is equal to zero, the point stays in its
801          * current form.
802          * @returns {JXG.Point} Reference to this element
803          */
804         handleAttractors: function () {
805             var i, el, projCoords,
806                 d = 0.0,
807                 projection,
808                 len = this.visProp.attractors.length;
809 
810             if (this.visProp.attractordistance === 0.0) {
811                 return;
812             }
813 
814             for (i = 0; i < len; i++) {
815                 el = this.board.select(this.visProp.attractors[i]);
816 
817                 if (Type.exists(el) && el !== this) {
818                     if (Type.isPoint(el)) {
819                         projCoords = Geometry.projectPointToPoint(this, el, this.board);
820                     } else if (el.elementClass === Const.OBJECT_CLASS_LINE) {
821                         projection = Geometry.projectCoordsToSegment(
822                                     this.coords.usrCoords,
823                                     el.point1.coords.usrCoords,
824                                     el.point2.coords.usrCoords);
825                         if (!el.visProp.straightfirst && projection[1] < 0.0) {
826                             projCoords = el.point1.coords;
827                         } else if (!el.visProp.straightlast && projection[1] > 1.0) {
828                             projCoords = el.point2.coords;
829                         } else {
830                             projCoords = new Coords(Const.COORDS_BY_USER, projection[0], this.board);
831                         }
832                     } else if (el.elementClass === Const.OBJECT_CLASS_CIRCLE) {
833                         projCoords = Geometry.projectPointToCircle(this, el, this.board);
834                     } else if (el.elementClass === Const.OBJECT_CLASS_CURVE) {
835                         projCoords = Geometry.projectPointToCurve(this, el, this.board);
836                     } else if (el.type === Const.OBJECT_TYPE_TURTLE) {
837                         projCoords = Geometry.projectPointToTurtle(this, el, this.board);
838                     }
839 
840                     if (this.visProp.attractorunit === 'screen') {
841                         d = projCoords.distance(Const.COORDS_BY_SCREEN, this.coords);
842                     } else {
843                         d = projCoords.distance(Const.COORDS_BY_USER, this.coords);
844                     }
845 
846                     if (d < this.visProp.attractordistance) {
847                         if (!(this.type === Const.OBJECT_TYPE_GLIDER && this.slideObject === el)) {
848                             this.makeGlider(el);
849                         }
850 
851                         break;       // bind the point to the first attractor in its list.
852                     } else {
853                         if (el === this.slideObject && d >= this.visProp.snatchdistance) {
854                             this.popSlideObject();
855                         }
856                     }
857                 }
858             }
859 
860             return this;
861         },
862 
863         /**
864          * Sets coordinates and calls the point's update() method.
865          * @param {Number} method The type of coordinates used here.
866          * Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
867          * @param {Array} coords coordinates <tt>([z], x, y)</tt> in screen/user units
868          * @returns {JXG.Point} this element
869          */
870         setPositionDirectly: function (method, coords) {
871             var i, c, dc,
872                 oldCoords = this.coords,
873                 newCoords;
874 
875             if (this.relativeCoords) {
876                 c = new Coords(method, coords, this.board);
877                 if (this.visProp.islabel) {
878                     dc = Statistics.subtract(c.scrCoords, oldCoords.scrCoords);
879                     this.relativeCoords.scrCoords[1] += dc[1];
880                     this.relativeCoords.scrCoords[2] += dc[2];
881                 } else {
882                     dc = Statistics.subtract(c.usrCoords, oldCoords.usrCoords);
883                     this.relativeCoords.usrCoords[1] += dc[1];
884                     this.relativeCoords.usrCoords[2] += dc[2];
885                 }
886 
887                 return this;
888             }
889 
890             this.coords.setCoordinates(method, coords);
891             this.handleSnapToGrid();
892             this.handleSnapToPoints();
893             this.handleAttractors();
894 
895             // Update the initial coordinates. This is needed for free points
896             // that have a transformation bound to it.
897             for (i = this.transformations.length - 1; i >= 0; i--) {
898                 if (method === Const.COORDS_BY_SCREEN) {
899                     newCoords = (new Coords(method, coords, this.board)).usrCoords;
900                 } else {
901                     if (coords.length === 2) {
902                         coords = [1].concat(coords);
903                     }
904                     newCoords = coords;
905                 }
906                 this.initialCoords.setCoordinates(Const.COORDS_BY_USER, Mat.matVecMult(Mat.inverse(this.transformations[i].matrix), newCoords));
907             }
908             this.prepareUpdate().update();
909 
910             // If the user suspends the board updates we need to recalculate the relative position of
911             // the point on the slide object. this is done in updateGlider() which is NOT called during the
912             // update process triggered by unsuspendUpdate.
913             if (this.board.isSuspendedUpdate && this.type === Const.OBJECT_TYPE_GLIDER) {
914                 this.updateGlider();
915             }
916 
917             return this;
918         },
919 
920         /**
921          * Translates the point by <tt>tv = (x, y)</tt>.
922          * @param {Number} method The type of coordinates used here.
923          * Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
924          * @param {Array} tv (x, y)
925          * @returns {JXG.Point}
926          */
927         setPositionByTransform: function (method, tv) {
928             var t;
929 
930             tv = new Coords(method, tv, this.board);
931             t = this.board.create('transform', tv.usrCoords.slice(1), {type: 'translate'});
932 
933             if (this.transformations.length > 0 &&
934                     this.transformations[this.transformations.length - 1].isNumericMatrix) {
935                 this.transformations[this.transformations.length - 1].melt(t);
936             } else {
937                 this.addTransform(this, t);
938             }
939 
940             this.prepareUpdate().update();
941 
942             return this;
943         },
944 
945         /**
946          * Sets coordinates and calls the point's update() method.
947          * @param {Number} method The type of coordinates used here.
948          * Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
949          * @param {Array} coords coordinates in screen/user units
950          * @returns {JXG.Point}
951          */
952         setPosition: function (method, coords) {
953             return this.setPositionDirectly(method, coords);
954         },
955 
956         /**
957          * Sets the position of a glider relative to the defining elements
958          * of the {@link JXG.Point#slideObject}.
959          * @param {Number} x
960          * @returns {JXG.Point} Reference to the point element.
961          */
962         setGliderPosition: function (x) {
963             if (this.type === Const.OBJECT_TYPE_GLIDER) {
964                 this.position = x;
965                 this.board.update();
966             }
967 
968             return this;
969         },
970 
971         /**
972          * Convert the point to glider and update the construction.
973          * To move the point visual onto the glider, a call of board update is necessary.
974          * @param {String|Object} slide The object the point will be bound to.
975          */
976         makeGlider: function (slide) {
977             var slideobj = this.board.select(slide),
978                 onPolygon = false,
979                 min,
980                 i,
981                 dist;
982 
983             if (slideobj.type === Const.OBJECT_TYPE_POLYGON){
984                 // Search for the closest side of the polygon.
985                 min = Number.MAX_VALUE;
986                 for (i = 0; i < slideobj.borders.length; i++){
987                     dist = JXG.Math.Geometry.distPointLine(this.coords.usrCoords, slideobj.borders[i].stdform);
988                     if (dist < min){
989                         min = dist;
990                         slide = slideobj.borders[i];
991                     }
992                 }
993             	slideobj = this.board.select(slide);
994             	onPolygon = true;
995             }
996 
997             /* Gliders on Ticks are forbidden */
998             if (!Type.exists(slideobj)) {
999                 throw new Error("JSXGraph: slide object undefined.");
1000             } else if (slideobj.type === Const.OBJECT_TYPE_TICKS) {
1001                 throw new Error("JSXGraph: gliders on ticks are not possible.");
1002             }
1003 
1004             this.slideObject = this.board.select(slide);
1005             this.slideObjects.push(this.slideObject);
1006             this.addParents(slide);
1007 
1008             this.type = Const.OBJECT_TYPE_GLIDER;
1009             this.elType = 'glider';
1010             this.visProp.snapwidth = -1;          // By default, deactivate snapWidth
1011             this.slideObject.addChild(this);
1012             this.isDraggable = true;
1013             this.onPolygon = onPolygon;
1014 
1015             this.generatePolynomial = function () {
1016                 return this.slideObject.generatePolynomial(this);
1017             };
1018 
1019             // Determine the initial value of this.position
1020             this.updateGlider();
1021             this.needsUpdateFromParent = true;
1022             this.updateGliderFromParent();
1023 
1024             return this;
1025         },
1026 
1027         /**
1028          * Remove the last slideObject. If there are more than one elements the point is bound to,
1029          * the second last element is the new active slideObject.
1030          */
1031         popSlideObject: function () {
1032             if (this.slideObjects.length > 0) {
1033                 this.slideObjects.pop();
1034 
1035                 // It may not be sufficient to remove the point from
1036                 // the list of childElement. For complex dependencies
1037                 // one may have to go to the list of ancestor and descendants.  A.W.
1038                 // yes indeed, see #51 on github bugtracker
1039                 //delete this.slideObject.childElements[this.id];
1040                 this.slideObject.removeChild(this);
1041 
1042                 if (this.slideObjects.length === 0) {
1043                     this.type = this._org_type;
1044                     if (this.type === Const.OBJECT_TYPE_POINT) {
1045                         this.elType = 'point';
1046                     } else if (this.elementClass === Const.OBJECT_CLASS_TEXT) {
1047                         this.elType = 'text';
1048                     } else if (this.type === Const.OBJECT_TYPE_IMAGE) {
1049                         this.elType = 'image';
1050                     }
1051 
1052                     this.slideObject = null;
1053                 } else {
1054                     this.slideObject = this.slideObjects[this.slideObjects.length - 1];
1055                 }
1056             }
1057         },
1058 
1059         /**
1060          * Converts a calculated element into a free element,
1061          * i.e. it will delete all ancestors and transformations and,
1062          * if the element is currently a glider, will remove the slideObject reference.
1063          */
1064         free: function () {
1065             var ancestorId, ancestor, child;
1066 
1067             if (this.type !== Const.OBJECT_TYPE_GLIDER) {
1068                 // remove all transformations
1069                 this.transformations.length = 0;
1070 
1071                 if (!this.isDraggable) {
1072                     this.isDraggable = true;
1073 
1074                     if (this.elementClass === Const.OBJECT_CLASS_POINT) {
1075                         this.type = Const.OBJECT_TYPE_POINT;
1076                         this.elType = 'point';
1077                     }
1078 
1079                     this.XEval = function () {
1080                         return this.coords.usrCoords[1];
1081                     };
1082 
1083                     this.YEval = function () {
1084                         return this.coords.usrCoords[2];
1085                     };
1086 
1087                     this.ZEval = function () {
1088                         return this.coords.usrCoords[0];
1089                     };
1090 
1091                     this.Xjc = null;
1092                     this.Yjc = null;
1093                 } else {
1094                     return;
1095                 }
1096             }
1097 
1098             // a free point does not depend on anything. And instead of running through tons of descendants and ancestor
1099             // structures, where we eventually are going to visit a lot of objects twice or thrice with hard to read and
1100             // comprehend code, just run once through all objects and delete all references to this point and its label.
1101             for (ancestorId in this.board.objects) {
1102                 if (this.board.objects.hasOwnProperty(ancestorId)) {
1103                     ancestor = this.board.objects[ancestorId];
1104 
1105                     if (ancestor.descendants) {
1106                         delete ancestor.descendants[this.id];
1107                         delete ancestor.childElements[this.id];
1108 
1109                         if (this.hasLabel) {
1110                             delete ancestor.descendants[this.label.id];
1111                             delete ancestor.childElements[this.label.id];
1112                         }
1113                     }
1114                 }
1115             }
1116 
1117             // A free point does not depend on anything. Remove all ancestors.
1118             this.ancestors = {}; // only remove the reference
1119 
1120             // Completely remove all slideObjects of the element
1121             this.slideObject = null;
1122             this.slideObjects = [];
1123             if (this.elementClass === Const.OBJECT_CLASS_POINT) {
1124                 this.type = Const.OBJECT_TYPE_POINT;
1125                 this.elType = 'point';
1126             } else if (this.elementClass === Const.OBJECT_CLASS_TEXT) {
1127                 this.type = this._org_type;
1128                 this.elType = 'text';
1129             } else if (this.elementClass === Const.OBJECT_CLASS_OTHER) {
1130                 this.type = this._org_type;
1131                 this.elType = 'image';
1132             }
1133         },
1134 
1135         /**
1136          * Convert the point to CAS point and call update().
1137          * @param {Array} terms [[zterm], xterm, yterm] defining terms for the z, x and y coordinate.
1138          * The z-coordinate is optional and it is used for homogeneous coordinates.
1139          * The coordinates may be either <ul>
1140          *   <li>a JavaScript function,</li>
1141          *   <li>a string containing GEONExT syntax. This string will be converted into a JavaScript
1142          *     function here,</li>
1143          *   <li>a Number</li>
1144          *   <li>a pointer to a slider object. This will be converted into a call of the Value()-method
1145          *     of this slider.</li>
1146          *   </ul>
1147          * @see JXG.GeonextParser#geonext2JS
1148          */
1149         addConstraint: function (terms) {
1150             var fs, i, v, t,
1151                 newfuncs = [],
1152                 what = ['X', 'Y'],
1153 
1154                 makeConstFunction = function (z) {
1155                     return function () {
1156                         return z;
1157                     };
1158                 },
1159 
1160                 makeSliderFunction = function (a) {
1161                     return function () {
1162                         return a.Value();
1163                     };
1164                 };
1165 
1166             if (this.elementClass === Const.OBJECT_CLASS_POINT) {
1167                 this.type = Const.OBJECT_TYPE_CAS;
1168             }
1169 
1170             this.isDraggable = false;
1171 
1172             for (i = 0; i < terms.length; i++) {
1173                 v = terms[i];
1174 
1175                 if (Type.isString(v)) {
1176                     // Convert GEONExT syntax into JavaScript syntax
1177                     //t  = JXG.GeonextParser.geonext2JS(v, this.board);
1178                     //newfuncs[i] = new Function('','return ' + t + ';');
1179                     //v = GeonextParser.replaceNameById(v, this.board);
1180                     newfuncs[i] = this.board.jc.snippet(v, true, null, true);
1181 
1182                     if (terms.length === 2) {
1183                         this[what[i] + 'jc'] = terms[i];
1184                     }
1185                 } else if (Type.isFunction(v)) {
1186                     newfuncs[i] = v;
1187                 } else if (Type.isNumber(v)) {
1188                     newfuncs[i] = makeConstFunction(v);
1189                 // Slider
1190             } else if (Type.isObject(v) && Type.isFunction(v.Value)) {
1191                     newfuncs[i] = makeSliderFunction(v);
1192                 }
1193 
1194                 newfuncs[i].origin = v;
1195             }
1196 
1197             // Intersection function
1198             if (terms.length === 1) {
1199                 this.updateConstraint = function () {
1200                     var c = newfuncs[0]();
1201 
1202                     // Array
1203                     if (Type.isArray(c)) {
1204                         this.coords.setCoordinates(Const.COORDS_BY_USER, c);
1205                     // Coords object
1206                     } else {
1207                         this.coords = c;
1208                     }
1209                 };
1210             // Euclidean coordinates
1211             } else if (terms.length === 2) {
1212                 this.XEval = newfuncs[0];
1213                 this.YEval = newfuncs[1];
1214 
1215                 this.setParents([newfuncs[0].origin, newfuncs[1].origin]);
1216 
1217                 this.updateConstraint = function () {
1218                     this.coords.setCoordinates(Const.COORDS_BY_USER, [this.XEval(), this.YEval()]);
1219                 };
1220             // Homogeneous coordinates
1221             } else {
1222                 this.ZEval = newfuncs[0];
1223                 this.XEval = newfuncs[1];
1224                 this.YEval = newfuncs[2];
1225 
1226                 this.setParents([newfuncs[0].origin, newfuncs[1].origin, newfuncs[2].origin]);
1227 
1228                 this.updateConstraint = function () {
1229                     this.coords.setCoordinates(Const.COORDS_BY_USER, [this.ZEval(), this.XEval(), this.YEval()]);
1230                 };
1231             }
1232 
1233             /**
1234             * We have to do an update. Otherwise, elements relying on this point will receive NaN.
1235             */
1236             this.prepareUpdate().update();
1237 
1238             if (!this.board.isSuspendedUpdate) {
1239                 this.updateRenderer();
1240             }
1241 
1242             return this;
1243         },
1244 
1245         /**
1246          * In case there is an attribute "anchor", the element is bound to
1247          * this anchor element.
1248          * This is handled with this.relativeCoords. If the element is a label
1249          * relativeCoords are given in scrCoords, otherwise in usrCoords.
1250          * @param{Array} coordinates Offset from th anchor element. These are the values for this.relativeCoords.
1251          * In case of a label, coordinates are screen coordinates. Otherwise, coordinates are user coordinates.
1252          * @param{Boolean} isLabel Yes/no
1253          * @private
1254          */
1255         addAnchor: function (coordinates, isLabel) {
1256             if (isLabel) {
1257                 this.relativeCoords = new Coords(Const.COORDS_BY_SCREEN, coordinates.slice(0, 2), this.board);
1258             } else {
1259                 this.relativeCoords = new Coords(Const.COORDS_BY_USER, coordinates, this.board);
1260             }
1261             this.element.addChild(this);
1262             if (isLabel) {
1263                 this.addParents(this.element);
1264             }
1265 
1266             this.XEval = function () {
1267                 var sx, coords, anchor;
1268 
1269                 if (this.visProp.islabel) {
1270                     sx =  parseFloat(this.visProp.offset[0]);
1271                     anchor = this.element.getLabelAnchor();
1272                     coords = new Coords(Const.COORDS_BY_SCREEN,
1273                         [sx + this.relativeCoords.scrCoords[1] + anchor.scrCoords[1], 0], this.board);
1274 
1275                     return coords.usrCoords[1];
1276                 }
1277 
1278                 anchor = this.element.getTextAnchor();
1279                 return this.relativeCoords.usrCoords[1] + anchor.usrCoords[1];
1280             };
1281 
1282             this.YEval = function () {
1283                 var sy, coords, anchor;
1284 
1285                 if (this.visProp.islabel) {
1286                     sy = -parseFloat(this.visProp.offset[1]);
1287                     anchor = this.element.getLabelAnchor();
1288                     coords = new Coords(Const.COORDS_BY_SCREEN,
1289                         [0, sy + this.relativeCoords.scrCoords[2] + anchor.scrCoords[2]], this.board);
1290 
1291                     return coords.usrCoords[2];
1292                 }
1293 
1294                 anchor = this.element.getTextAnchor();
1295                 return this.relativeCoords.usrCoords[2] + anchor.usrCoords[2];
1296             };
1297 
1298             this.ZEval = Type.createFunction(1, this.board, '');
1299 
1300             this.updateConstraint = function () {
1301                 this.coords.setCoordinates(Const.COORDS_BY_USER, [this.ZEval(), this.XEval(), this.YEval()]);
1302             };
1303 
1304             this.coords = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this.board);
1305         },
1306 
1307         /**
1308          * Applies the transformations of the element.
1309          * This method applies to text and images. Point transformations are handled differently.
1310          * @returns {JXG.CoordsElement} Reference to this object.
1311          */
1312         updateTransform: function () {
1313             var i;
1314 
1315             if (this.transformations.length === 0) {
1316                 return this;
1317             }
1318 
1319             for (i = 0; i < this.transformations.length; i++) {
1320                 this.transformations[i].update();
1321             }
1322 
1323             return this;
1324         },
1325 
1326         /**
1327          * Add transformations to this point.
1328          * @param {JXG.GeometryElement} el
1329          * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation}
1330          * or an array of {@link JXG.Transformation}s.
1331          * @returns {JXG.Point} Reference to this point object.
1332          */
1333         addTransform: function (el, transform) {
1334             var i,
1335                 list = Type.isArray(transform) ? transform : [transform],
1336                 len = list.length;
1337 
1338             // There is only one baseElement possible
1339             if (this.transformations.length === 0) {
1340                 this.baseElement = el;
1341             }
1342 
1343             for (i = 0; i < len; i++) {
1344                 this.transformations.push(list[i]);
1345             }
1346 
1347             return this;
1348         },
1349 
1350         /**
1351          * Animate the point.
1352          * @param {Number} direction The direction the glider is animated. Can be +1 or -1.
1353          * @param {Number} stepCount The number of steps.
1354          * @name Glider#startAnimation
1355          * @see Glider#stopAnimation
1356          * @function
1357          */
1358         startAnimation: function (direction, stepCount) {
1359             var that = this;
1360 
1361             if ((this.type === Const.OBJECT_TYPE_GLIDER) && !Type.exists(this.intervalCode)) {
1362                 this.intervalCode = window.setInterval(function () {
1363                     that._anim(direction, stepCount);
1364                 }, 250);
1365 
1366                 if (!Type.exists(this.intervalCount)) {
1367                     this.intervalCount = 0;
1368                 }
1369             }
1370             return this;
1371         },
1372 
1373         /**
1374          * Stop animation.
1375          * @name Glider#stopAnimation
1376          * @see Glider#startAnimation
1377          * @function
1378          */
1379         stopAnimation: function () {
1380             if (Type.exists(this.intervalCode)) {
1381                 window.clearInterval(this.intervalCode);
1382                 delete this.intervalCode;
1383             }
1384 
1385             return this;
1386         },
1387 
1388         /**
1389          * Starts an animation which moves the point along a given path in given time.
1390          * @param {Array|function} path The path the point is moved on.
1391          * This can be either an array of arrays or containing x and y values of the points of
1392          * the path, or an array of points, or a function taking the amount of elapsed time since the animation
1393          * has started and returns an array containing a x and a y value or NaN.
1394          * In case of NaN the animation stops.
1395          * @param {Number} time The time in milliseconds in which to finish the animation
1396          * @param {Object} [options] Optional settings for the animation.
1397          * @param {function} [options.callback] A function that is called as soon as the animation is finished.
1398          * @param {Boolean} [options.interpolate=true] If <tt>path</tt> is an array moveAlong()
1399          * will interpolate the path
1400          * using {@link JXG.Math.Numerics.Neville}. Set this flag to false if you don't want to use interpolation.
1401          * @returns {JXG.Point} Reference to the point.
1402          */
1403         moveAlong: function (path, time, options) {
1404             options = options || {};
1405 
1406             var i, neville,
1407                 interpath = [],
1408                 p = [],
1409                 delay = this.board.attr.animationdelay,
1410                 steps = time / delay,
1411                 len, pos, part,
1412 
1413                 makeFakeFunction = function (i, j) {
1414                     return function () {
1415                         return path[i][j];
1416                     };
1417                 };
1418 
1419             if (Type.isArray(path)) {
1420                 len = path.length;
1421                 for (i = 0; i < len; i++) {
1422                     if (Type.isPoint(path[i])) {
1423                         p[i] = path[i];
1424                     } else {
1425                         p[i] = {
1426                             elementClass: Const.OBJECT_CLASS_POINT,
1427                             X: makeFakeFunction(i, 0),
1428                             Y: makeFakeFunction(i, 1)
1429                         };
1430                     }
1431                 }
1432 
1433                 time = time || 0;
1434                 if (time === 0) {
1435                     this.setPosition(Const.COORDS_BY_USER, [p[p.length - 1].X(), p[p.length - 1].Y()]);
1436                     return this.board.update(this);
1437                 }
1438 
1439                 if (!Type.exists(options.interpolate) || options.interpolate) {
1440                     neville = Numerics.Neville(p);
1441                     for (i = 0; i < steps; i++) {
1442                         interpath[i] = [];
1443                         interpath[i][0] = neville[0]((steps - i) / steps * neville[3]());
1444                         interpath[i][1] = neville[1]((steps - i) / steps * neville[3]());
1445                     }
1446                 } else {
1447                     len = path.length - 1;
1448                     for (i = 0; i < steps; ++i) {
1449                         pos = Math.floor(i / steps * len);
1450                         part = i / steps * len - pos;
1451 
1452                         interpath[i] = [];
1453                         interpath[i][0] = (1.0 - part) * p[pos].X() + part * p[pos + 1].X();
1454                         interpath[i][1] = (1.0 - part) * p[pos].Y() + part * p[pos + 1].Y();
1455                     }
1456                     interpath.push([p[len].X(), p[len].Y()]);
1457                     interpath.reverse();
1458                     /*
1459                     for (i = 0; i < steps; i++) {
1460                         interpath[i] = [];
1461                         interpath[i][0] = path[Math.floor((steps - i) / steps * (path.length - 1))][0];
1462                         interpath[i][1] = path[Math.floor((steps - i) / steps * (path.length - 1))][1];
1463                     }
1464                     */
1465                 }
1466 
1467                 this.animationPath = interpath;
1468             } else if (Type.isFunction(path)) {
1469                 this.animationPath = path;
1470                 this.animationStart = new Date().getTime();
1471             }
1472 
1473             this.animationCallback = options.callback;
1474             this.board.addAnimation(this);
1475 
1476             return this;
1477         },
1478 
1479         /**
1480          * Starts an animated point movement towards the given coordinates <tt>where</tt>.
1481          * The animation is done after <tt>time</tt> milliseconds.
1482          * If the second parameter is not given or is equal to 0, setPosition() is called, see #setPosition.
1483          * @param {Array} where Array containing the x and y coordinate of the target location.
1484          * @param {Number} [time] Number of milliseconds the animation should last.
1485          * @param {Object} [options] Optional settings for the animation
1486          * @param {function} [options.callback] A function that is called as soon as the animation is finished.
1487          * @param {String} [options.effect='<>'] animation effects like speed fade in and out. possible values are
1488          * '<>' for speed increase on start and slow down at the end (default) and '--' for constant speed during
1489          * the whole animation.
1490          * @returns {JXG.Point} Reference to itself.
1491          * @see #animate
1492          */
1493         moveTo: function (where, time, options) {
1494             options = options || {};
1495             where = new Coords(Const.COORDS_BY_USER, where, this.board);
1496 
1497             var i,
1498                 delay = this.board.attr.animationdelay,
1499                 steps = Math.ceil(time / delay),
1500                 coords = [],
1501                 X = this.coords.usrCoords[1],
1502                 Y = this.coords.usrCoords[2],
1503                 dX = (where.usrCoords[1] - X),
1504                 dY = (where.usrCoords[2] - Y),
1505 
1506                 /** @ignore */
1507                 stepFun = function (i) {
1508                     if (options.effect && options.effect === '<>') {
1509                         return Math.pow(Math.sin((i / steps) * Math.PI / 2), 2);
1510                     }
1511                     return i / steps;
1512                 };
1513 
1514             if (!Type.exists(time) || time === 0 || (Math.abs(where.usrCoords[0] - this.coords.usrCoords[0]) > Mat.eps)) {
1515                 this.setPosition(Const.COORDS_BY_USER, where.usrCoords);
1516                 return this.board.update(this);
1517             }
1518 
1519             // In case there is no callback and we are already at the endpoint we can stop here
1520             if (!Type.exists(options.callback) && Math.abs(dX) < Mat.eps && Math.abs(dY) < Mat.eps) {
1521                 return this;
1522             }
1523 
1524             for (i = steps; i >= 0; i--) {
1525                 coords[steps - i] = [where.usrCoords[0], X + dX * stepFun(i), Y + dY * stepFun(i)];
1526             }
1527 
1528             this.animationPath = coords;
1529             this.animationCallback = options.callback;
1530             this.board.addAnimation(this);
1531 
1532             return this;
1533         },
1534 
1535         /**
1536          * Starts an animated point movement towards the given coordinates <tt>where</tt>. After arriving at
1537          * <tt>where</tt> the point moves back to where it started. The animation is done after <tt>time</tt>
1538          * milliseconds.
1539          * @param {Array} where Array containing the x and y coordinate of the target location.
1540          * @param {Number} time Number of milliseconds the animation should last.
1541          * @param {Object} [options] Optional settings for the animation
1542          * @param {function} [options.callback] A function that is called as soon as the animation is finished.
1543          * @param {String} [options.effect='<>'] animation effects like speed fade in and out. possible values are
1544          * '<>' for speed increase on start and slow down at the end (default) and '--' for constant speed during
1545          * the whole animation.
1546          * @param {Number} [options.repeat=1] How often this animation should be repeated.
1547          * @returns {JXG.Point} Reference to itself.
1548          * @see #animate
1549          */
1550         visit: function (where, time, options) {
1551             where = new Coords(Const.COORDS_BY_USER, where, this.board);
1552 
1553             var i, j, steps,
1554                 delay = this.board.attr.animationdelay,
1555                 coords = [],
1556                 X = this.coords.usrCoords[1],
1557                 Y = this.coords.usrCoords[2],
1558                 dX = (where.usrCoords[1] - X),
1559                 dY = (where.usrCoords[2] - Y),
1560 
1561                 /** @ignore */
1562                 stepFun = function (i) {
1563                     var x = (i < steps / 2 ? 2 * i / steps : 2 * (steps - i) / steps);
1564 
1565                     if (options.effect && options.effect === '<>') {
1566                         return Math.pow(Math.sin(x * Math.PI / 2), 2);
1567                     }
1568 
1569                     return x;
1570                 };
1571 
1572             // support legacy interface where the third parameter was the number of repeats
1573             if (Type.isNumber(options)) {
1574                 options = {repeat: options};
1575             } else {
1576                 options = options || {};
1577                 if (!Type.exists(options.repeat)) {
1578                     options.repeat = 1;
1579                 }
1580             }
1581 
1582             steps = Math.ceil(time / (delay * options.repeat));
1583 
1584             for (j = 0; j < options.repeat; j++) {
1585                 for (i = steps; i >= 0; i--) {
1586                     coords[j * (steps + 1) + steps - i] = [where.usrCoords[0], X + dX * stepFun(i), Y + dY * stepFun(i)];
1587                 }
1588             }
1589             this.animationPath = coords;
1590             this.animationCallback = options.callback;
1591             this.board.addAnimation(this);
1592 
1593             return this;
1594         },
1595 
1596         /**
1597          * Animates a glider. Is called by the browser after startAnimation is called.
1598          * @param {Number} direction The direction the glider is animated.
1599          * @param {Number} stepCount The number of steps.
1600          * @see #startAnimation
1601          * @see #stopAnimation
1602          * @private
1603          */
1604         _anim: function (direction, stepCount) {
1605             var distance, slope, dX, dY, alpha, startPoint, newX, radius,
1606                 factor = 1;
1607 
1608             this.intervalCount += 1;
1609             if (this.intervalCount > stepCount) {
1610                 this.intervalCount = 0;
1611             }
1612 
1613             if (this.slideObject.elementClass === Const.OBJECT_CLASS_LINE) {
1614                 distance = this.slideObject.point1.coords.distance(Const.COORDS_BY_SCREEN, this.slideObject.point2.coords);
1615                 slope = this.slideObject.getSlope();
1616                 if (slope !== Infinity) {
1617                     alpha = Math.atan(slope);
1618                     dX = Math.round((this.intervalCount / stepCount) * distance * Math.cos(alpha));
1619                     dY = Math.round((this.intervalCount / stepCount) * distance * Math.sin(alpha));
1620                 } else {
1621                     dX = 0;
1622                     dY = Math.round((this.intervalCount / stepCount) * distance);
1623                 }
1624 
1625                 if (direction < 0) {
1626                     startPoint = this.slideObject.point2;
1627 
1628                     if (this.slideObject.point2.coords.scrCoords[1] - this.slideObject.point1.coords.scrCoords[1] > 0) {
1629                         factor = -1;
1630                     } else if (this.slideObject.point2.coords.scrCoords[1] - this.slideObject.point1.coords.scrCoords[1] === 0) {
1631                         if (this.slideObject.point2.coords.scrCoords[2] - this.slideObject.point1.coords.scrCoords[2] > 0) {
1632                             factor = -1;
1633                         }
1634                     }
1635                 } else {
1636                     startPoint = this.slideObject.point1;
1637 
1638                     if (this.slideObject.point1.coords.scrCoords[1] - this.slideObject.point2.coords.scrCoords[1] > 0) {
1639                         factor = -1;
1640                     } else if (this.slideObject.point1.coords.scrCoords[1] - this.slideObject.point2.coords.scrCoords[1] === 0) {
1641                         if (this.slideObject.point1.coords.scrCoords[2] - this.slideObject.point2.coords.scrCoords[2] > 0) {
1642                             factor = -1;
1643                         }
1644                     }
1645                 }
1646 
1647                 this.coords.setCoordinates(Const.COORDS_BY_SCREEN, [
1648                     startPoint.coords.scrCoords[1] + factor * dX,
1649                     startPoint.coords.scrCoords[2] + factor * dY
1650                 ]);
1651             } else if (this.slideObject.elementClass === Const.OBJECT_CLASS_CURVE) {
1652                 if (direction > 0) {
1653                     newX = Math.round(this.intervalCount / stepCount * this.board.canvasWidth);
1654                 } else {
1655                     newX = Math.round((stepCount - this.intervalCount) / stepCount * this.board.canvasWidth);
1656                 }
1657 
1658                 this.coords.setCoordinates(Const.COORDS_BY_SCREEN, [newX, 0]);
1659                 this.coords = Geometry.projectPointToCurve(this, this.slideObject, this.board);
1660             } else if (this.slideObject.elementClass === Const.OBJECT_CLASS_CIRCLE) {
1661                 if (direction < 0) {
1662                     alpha = this.intervalCount / stepCount * 2 * Math.PI;
1663                 } else {
1664                     alpha = (stepCount - this.intervalCount) / stepCount * 2 * Math.PI;
1665                 }
1666 
1667                 radius = this.slideObject.Radius();
1668 
1669                 this.coords.setCoordinates(Const.COORDS_BY_USER, [
1670                     this.slideObject.center.coords.usrCoords[1] + radius * Math.cos(alpha),
1671                     this.slideObject.center.coords.usrCoords[2] + radius * Math.sin(alpha)
1672                 ]);
1673             }
1674 
1675             this.board.update(this);
1676             return this;
1677         },
1678 
1679         // documented in GeometryElement
1680         getTextAnchor: function () {
1681             return this.coords;
1682         },
1683 
1684         // documented in GeometryElement
1685         getLabelAnchor: function () {
1686             return this.coords;
1687         },
1688 
1689         // documented in element.js
1690         getParents: function () {
1691             var p = [this.Z(), this.X(), this.Y()];
1692 
1693             if (this.parents.length !== 0) {
1694                 p = this.parents;
1695             }
1696 
1697             if (this.type === Const.OBJECT_TYPE_GLIDER) {
1698                 p = [this.X(), this.Y(), this.slideObject.id];
1699             }
1700 
1701             return p;
1702         }
1703 
1704     });
1705 
1706     /**
1707      * Generic method to create point, text or image.
1708      * Determines the type of the construction, i.e. free, or constrained by function,
1709      * transformation or of glider type.
1710      * @param{Object} Callback Object type, e.g. JXG.Point, JXG.Text or JXG.Image
1711      * @param{Object} board Link to the board object
1712      * @param{Array} coords Array with coordinates. This may be: array of numbers, function
1713      * returning an array of numbers, array of functions returning a number, object and transformation.
1714      * If the attribute "slideObject" exists, a glider element is constructed.
1715      * @param{Object} attr Attributes object
1716      * @param{Object} arg1 Optional argument 1: in case of text this is the text content,
1717      * in case of an image this is the url.
1718      * @param{Array} arg2 Optional argument 2: in case of image this is an array containing the size of
1719      * the image.
1720      * @returns{Object} returns the created object or false.
1721      */
1722     JXG.CoordsElement.create = function (Callback, board, coords, attr, arg1, arg2) {
1723         var el, isConstrained = false, i;
1724 
1725         for (i = 0; i < coords.length; i++) {
1726             if (Type.isFunction(coords[i]) || Type.isString(coords[i])) {
1727                 isConstrained = true;
1728             }
1729         }
1730 
1731         if (!isConstrained) {
1732             if (Type.isNumber(coords[0]) && Type.isNumber(coords[1])) {
1733                 el = new Callback(board, coords, attr, arg1, arg2);
1734 
1735                 if (Type.exists(attr.slideobject)) {
1736                     el.makeGlider(attr.slideobject);
1737                 } else {
1738                     // Free element
1739                     el.baseElement = el;
1740                 }
1741                 el.isDraggable = true;
1742             } else if (Type.isObject(coords[0]) &&
1743                 (Type.isObject(coords[1]) || // Transformation
1744                  (Type.isArray(coords[1]) && coords[1].length > 0 && Type.isObject(coords[1][0]))
1745                 )) { // Array of transformations
1746 
1747                 // Transformation
1748                 el = new Callback(board, [0, 0], attr, arg1, arg2);
1749                 el.addTransform(coords[0], coords[1]);
1750                 el.isDraggable = false;
1751             } else {
1752                 return false;
1753             }
1754         } else {
1755             el = new Callback(board, [0, 0], attr, arg1, arg2);
1756             el.addConstraint(coords);
1757         }
1758 
1759         el.handleSnapToGrid();
1760         el.handleSnapToPoints();
1761         el.handleAttractors();
1762 
1763         el.addParents(coords);
1764         return el;
1765     };
1766 
1767     return JXG.CoordsElement;
1768 
1769 });
1770