1 /*
  2     Copyright 2008-2021
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Bianca Valentin,
  7         Alfred Wassermann,
  8         Peter Wilfahrt
  9 
 10     This file is part of JSXGraph.
 11 
 12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 13 
 14     You can redistribute it and/or modify it under the terms of the
 15 
 16       * GNU Lesser General Public License as published by
 17         the Free Software Foundation, either version 3 of the License, or
 18         (at your option) any later version
 19       OR
 20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 21 
 22     JSXGraph is distributed in the hope that it will be useful,
 23     but WITHOUT ANY WARRANTY; without even the implied warranty of
 24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 25     GNU Lesser General Public License for more details.
 26 
 27     You should have received a copy of the GNU Lesser General Public License and
 28     the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
 29     and <http://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 
 33 /*global JXG: true, define: true*/
 34 /*jslint nomen: true, plusplus: true*/
 35 
 36 /* depends:
 37  jxg
 38  base/constants
 39  base/element
 40  utils/type
 41   elements:
 42    curve
 43    point
 44    line
 45    transform
 46  */
 47 
 48 /**
 49  * @fileoverview The JSXGraph object Turtle is defined. It acts like
 50  * "turtle graphics".
 51  * @author A.W.
 52  */
 53 
 54 define([
 55     'jxg', 'base/constants', 'base/element', 'utils/type', 'base/curve', 'base/point', 'base/line', 'base/transformation'
 56 ], function (JXG, Const, GeometryElement, Type, Curve, Point, Line, Transform) {
 57 
 58     "use strict";
 59 
 60     /**
 61      * Constructs a new Turtle object.
 62      * @class This is the Turtle class.
 63      * It is derived from {@link JXG.GeometryElement}.
 64      * It stores all properties required
 65      * to move a turtle.
 66      * @constructor
 67      * @param {JXG.Board} board The board the new turtle is drawn on.
 68      * @param {Array} parents Start position and start direction of the turtle. Possible values are
 69      * [x, y, angle]
 70      * [[x, y], angle]
 71      * [x, y]
 72      * [[x, y]]
 73      * @param {Object} attributes Attributes to change the visual properties of the turtle object
 74      * All angles are in degrees.
 75      *
 76      * @example
 77      *
 78      * //creates a figure 8 animation
 79      * var board = JXG.JSXGraph.initBoard('jxgbox',{boundingbox: [-250, 250, 250, -250]});
 80      * var t = board.create('turtle',[0, 0], {strokeOpacity:0.5});
 81      * t.setPenSize(3);
 82      * t.right(90);
 83      * var alpha = 0;
 84      *
 85      * var run = function() {
 86      *  t.forward(2);
 87      *  if (Math.floor(alpha / 360) % 2 === 0) {
 88      *   t.left(1);        // turn left by 1 degree
 89      *  } else {
 90      *   t.right(1);       // turn right by 1 degree
 91      *  }
 92      *  alpha += 1;
 93      *
 94      *  if (alpha < 1440) {  // stop after two rounds
 95      *   setTimeout(run, 20);
 96      *  }
 97      * }
 98      *
 99      *run();
100      *
101      * </pre><div class="jxgbox" id="JXG14167b1c-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
102      * <script type="text/javascript">
103      *     (function() {
104      *         var brd = JXG.JSXGraph.initBoard('JXG14167b1c-2ad3-11e5-8dd9-901b0e1b8723',
105      *             {boundingbox: [-250, 250, 250, -250], axis: true, showcopyright: false, shownavigation: false});
106      *               var t = brd.create('turtle',[0, 0], {strokeOpacity:0.5});
107      *               t.setPenSize(3);
108      *               t.right(90);
109      *               var alpha = 0;
110      *
111      *              var run = function() {
112      *              t.forward(2);
113      *             if (Math.floor(alpha / 360) % 2 === 0) {
114      *                t.left(1);        // turn left by 1 degree
115      *              } else {
116      *                   t.right(1);       // turn right by 1 degree
117      *             }
118      *             alpha += 1;
119      *
120      *             if (alpha < 1440) {  // stop after two rounds
121      *                 setTimeout(run, 20);
122      *               }
123      *             }
124      *
125      *          run();
126      *
127      *     })();
128      *
129      * </script><pre>
130      */
131     JXG.Turtle = function (board, parents, attributes) {
132         var x, y, dir;
133 
134         this.constructor(board, attributes, Const.OBJECT_TYPE_TURTLE, Const.OBJECT_CLASS_OTHER);
135 
136         this.turtleIsHidden = false;
137         this.board = board;
138         this.visProp.curveType = 'plot';
139 
140         // Save visProp in this._attributes.
141         // this._attributes is overwritten by setPenSize, setPenColor...
142         // Setting the color or size affects the turtle from the time of
143         // calling the method,
144         // whereas Turtle.setAttribute affects all turtle curves.
145         this._attributes = Type.copyAttributes(this.visProp, board.options, 'turtle');
146         delete this._attributes.id;
147 
148         x = 0;
149         y = 0;
150         dir = 90;
151 
152         if (parents.length !== 0) {
153             // [x,y,dir]
154             if (parents.length === 3) {
155                 // Only numbers are accepted at the moment
156                 x = parents[0];
157                 y = parents[1];
158                 dir = parents[2];
159             } else if (parents.length === 2) {
160                 // [[x,y],dir]
161                 if (Type.isArray(parents[0])) {
162                     x = parents[0][0];
163                     y = parents[0][1];
164                     dir = parents[1];
165                 // [x,y]
166                 } else {
167                     x = parents[0];
168                     y = parents[1];
169                 }
170             // [[x,y]]
171             } else {
172                 x = parents[0][0];
173                 y = parents[0][1];
174             }
175         }
176 
177         this.init(x, y, dir);
178 
179         this.methodMap = Type.deepCopy(this.methodMap, {
180             forward: 'forward',
181             fd: 'forward',
182             back: 'back',
183             bk: 'back',
184             right: 'right',
185             rt: 'right',
186             left: 'left',
187             lt: 'left',
188             penUp: 'penUp',
189             pu: 'penUp',
190             penDown: 'penDown',
191             pd: 'penDown',
192             clearScreen: 'clearScreen',
193             cs: 'clearScreen',
194             clean: 'clean',
195             setPos: 'setPos',
196             home: 'home',
197             hideTurtle: 'hideTurtle',
198             ht: 'hideTurtle',
199             showTurtle: 'showTurtle',
200             st: 'showTurtle',
201             penSize: 'setPenSize',
202             penColor: 'setPenColor',
203             pushTurtle: 'pushTurtle',
204             push: 'pushTurtle',
205             popTurtle: 'popTurtle',
206             pop: 'popTurtle',
207             lookTo: 'lookTo',
208             pos: 'pos',
209             moveTo: 'moveTo',
210             X: 'X',
211             Y: 'Y'
212         });
213 
214         return this;
215     };
216 
217     JXG.Turtle.prototype = new GeometryElement();
218 
219     JXG.extend(JXG.Turtle.prototype, /** @lends JXG.Turtle.prototype */ {
220         /**
221          * Initialize a new turtle or reinitialize a turtle after {@link JXG.Turtle#clearScreen}.
222          * @private
223          */
224         init: function (x, y, dir) {
225             var hiddenPointAttr = {
226                     fixed: true,
227                     name: '',
228                     visible: false,
229                     withLabel: false
230                 };
231 
232             this.arrowLen = 20 / Math.sqrt(this.board.unitX * this.board.unitX + this.board.unitY * this.board.unitY);
233 
234             this.pos = [x, y];
235             this.isPenDown = true;
236             this.dir = 90;
237             this.stack = [];
238             this.objects = [];
239             this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this._attributes);
240             this.objects.push(this.curve);
241 
242             this.turtle = this.board.create('point', this.pos, hiddenPointAttr);
243             this.objects.push(this.turtle);
244 
245             this.turtle2 = this.board.create('point', [this.pos[0], this.pos[1] + this.arrowLen], hiddenPointAttr);
246             this.objects.push(this.turtle2);
247 
248             this.visProp.arrow.lastArrow = true;
249             this.visProp.arrow.straightFirst = false;
250             this.visProp.arrow.straightLast = false;
251             this.arrow = this.board.create('line', [this.turtle, this.turtle2], this.visProp.arrow);
252             this.objects.push(this.arrow);
253 
254             this.subs = {
255                 arrow: this.arrow
256             };
257             this.inherits.push(this.arrow);
258 
259             this.right(90 - dir);
260             this.board.update();
261         },
262 
263         /**
264          * Move the turtle forward.
265          * @param {Number} len of forward move in user coordinates
266          * @returns {JXG.Turtle} pointer to the turtle object
267          */
268         forward: function (len) {
269             if (len === 0) {
270                 return this;
271             }
272 
273             var t,
274                 dx = len * Math.cos(this.dir * Math.PI / 180),
275                 dy = len * Math.sin(this.dir * Math.PI / 180);
276 
277             if (!this.turtleIsHidden) {
278                 t = this.board.create('transform', [dx, dy], {type: 'translate'});
279 
280                 t.applyOnce(this.turtle);
281                 t.applyOnce(this.turtle2);
282             }
283 
284             if (this.isPenDown) {
285                 // IE workaround
286                 if (this.curve.dataX.length >= 8192) {
287                     this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this._attributes);
288                     this.objects.push(this.curve);
289                 }
290             }
291 
292             this.pos[0] += dx;
293             this.pos[1] += dy;
294 
295             if (this.isPenDown) {
296                 this.curve.dataX.push(this.pos[0]);
297                 this.curve.dataY.push(this.pos[1]);
298             }
299 
300             this.board.update();
301             return this;
302         },
303 
304         /**
305          * Move the turtle backwards.
306          * @param {Number} len of backwards move in user coordinates
307          * @returns {JXG.Turtle} pointer to the turtle object
308          */
309         back: function (len) {
310             return this.forward(-len);
311         },
312 
313         /**
314          * Rotate the turtle direction to the right
315          * @param {Number} angle of the rotation in degrees
316          * @returns {JXG.Turtle} pointer to the turtle object
317          */
318         right: function (angle) {
319             this.dir -= angle;
320             this.dir %= 360;
321 
322             if (!this.turtleIsHidden) {
323                 var t = this.board.create('transform', [-angle * Math.PI / 180, this.turtle], {type: 'rotate'});
324                 t.applyOnce(this.turtle2);
325             }
326 
327             this.board.update();
328             return this;
329         },
330 
331         /**
332          * Rotate the turtle direction to the right.
333          * @param {Number} angle of the rotation in degrees
334          * @returns {JXG.Turtle} pointer to the turtle object
335          */
336         left: function (angle) {
337             return this.right(-angle);
338         },
339 
340         /**
341          * Pen up, stops visible drawing
342          * @returns {JXG.Turtle} pointer to the turtle object
343          */
344         penUp: function () {
345             this.isPenDown = false;
346             return this;
347         },
348 
349         /**
350          * Pen down, continues visible drawing
351          * @returns {JXG.Turtle} pointer to the turtle object
352          */
353         penDown: function () {
354             this.isPenDown = true;
355             this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this._attributes);
356             this.objects.push(this.curve);
357 
358             return this;
359         },
360 
361         /**
362          * Removes the turtle curve from the board. The turtle stays in its position.
363          * @returns {JXG.Turtle} pointer to the turtle object
364          */
365         clean: function () {
366             var i, el;
367 
368             for (i = 0; i < this.objects.length; i++) {
369                 el = this.objects[i];
370                 if (el.type === Const.OBJECT_TYPE_CURVE) {
371                     this.board.removeObject(el);
372                     this.objects.splice(i, 1);
373                 }
374             }
375 
376             this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this._attributes);
377             this.objects.push(this.curve);
378             this.board.update();
379 
380             return this;
381         },
382 
383         /**
384          *  Removes the turtle completely and resets it to its initial position and direction.
385          * @returns {JXG.Turtle} pointer to the turtle object
386          */
387         clearScreen: function () {
388             var i, el,
389                 len = this.objects.length;
390 
391             for (i = 0; i < len; i++) {
392                 el = this.objects[i];
393                 this.board.removeObject(el);
394             }
395 
396             this.init(0, 0, 90);
397             return this;
398         },
399 
400         /**
401          *  Moves the turtle without drawing to a new position
402          * @param {Number} x new x- coordinate
403          * @param {Number} y new y- coordinate
404          * @returns {JXG.Turtle} pointer to the turtle object
405          */
406         setPos: function (x, y) {
407             var t;
408 
409             if (Type.isArray(x)) {
410                 this.pos = x;
411             } else {
412                 this.pos = [x, y];
413             }
414 
415             if (!this.turtleIsHidden) {
416                 this.turtle.setPositionDirectly(Const.COORDS_BY_USER, [x, y]);
417                 this.turtle2.setPositionDirectly(Const.COORDS_BY_USER, [x, y + this.arrowLen]);
418                 t = this.board.create('transform', [-(this.dir - 90) * Math.PI / 180, this.turtle], {type: 'rotate'});
419                 t.applyOnce(this.turtle2);
420             }
421 
422             this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this._attributes);
423             this.objects.push(this.curve);
424             this.board.update();
425 
426             return this;
427         },
428 
429         /**
430          *  Sets the pen size. Equivalent to setAttribute({strokeWidth:size})
431          * but affects only the future turtle.
432          * @param {Number} size
433          * @returns {JXG.Turtle} pointer to the turtle object
434          */
435         setPenSize: function (size) {
436             //this.visProp.strokewidth = size;
437             this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this.copyAttr('strokeWidth', size));
438             this.objects.push(this.curve);
439             return this;
440         },
441 
442         /**
443          *  Sets the pen color. Equivalent to setAttribute({strokeColor:color})
444          * but affects only the future turtle.
445          * @param {String} color
446          * @returns {JXG.Turtle} pointer to the turtle object
447          */
448         setPenColor: function (color) {
449             this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this.copyAttr('strokeColor', color));
450             this.objects.push(this.curve);
451 
452             return this;
453         },
454 
455         /**
456          *  Sets the highlight pen color. Equivalent to setAttribute({highlightStrokeColor:color})
457          * but affects only the future turtle.
458          * @param {String} color
459          * @returns {JXG.Turtle} pointer to the turtle object
460          */
461         setHighlightPenColor: function (color) {
462             //this.visProp.highlightstrokecolor = colStr;
463             this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this.copyAttr('highlightStrokeColor', color));
464             this.objects.push(this.curve);
465             return this;
466         },
467 
468         /**
469          * Sets properties of the turtle, see also {@link JXG.GeometryElement#setAttribute}.
470          * Sets the property for all curves of the turtle in the past and in the future.
471          * @param {Object} attributes key:value pairs
472          * @returns {JXG.Turtle} pointer to the turtle object
473          */
474         setAttribute: function (attributes) {
475             var i, el, tmp,
476                 len = this.objects.length;
477 
478             for (i = 0; i < len; i++) {
479                 el = this.objects[i];
480                 if (el.type === Const.OBJECT_TYPE_CURVE) {
481                     el.setAttribute(attributes);
482                 }
483             }
484 
485             // Set visProp of turtle
486             tmp = this.visProp.id;
487             this.visProp = Type.deepCopy(this.curve.visProp);
488             this.visProp.id = tmp;
489             this._attributes = Type.deepCopy(this.visProp);
490             delete this._attributes.id;
491 
492             return this;
493         },
494 
495         /**
496          * Set a future attribute of the turtle.
497          * @private
498          * @param {String} key
499          * @param {Number|String} val
500          * @returns {Object} pointer to the attributes object
501          */
502         copyAttr: function (key, val) {
503             this._attributes[key.toLowerCase()] = val;
504             return this._attributes;
505         },
506 
507         /**
508          * Sets the visibility of the turtle head to true,
509          * @returns {JXG.Turtle} pointer to the turtle object
510          */
511         showTurtle: function () {
512             this.turtleIsHidden = false;
513             this.arrow.setAttribute({visible: true});
514             this.visProp.arrow.visible = false;
515             this.setPos(this.pos[0], this.pos[1]);
516             this.board.update();
517 
518             return this;
519         },
520 
521         /**
522          * Sets the visibility of the turtle head to false,
523          * @returns {JXG.Turtle} pointer to the turtle object
524          */
525         hideTurtle: function () {
526             this.turtleIsHidden = true;
527             this.arrow.setAttribute({visible: false});
528             this.visProp.arrow.visible = false;
529             this.board.update();
530 
531             return this;
532         },
533 
534         /**
535          * Moves the turtle to position [0,0].
536          * @returns {JXG.Turtle} pointer to the turtle object
537          */
538         home: function () {
539             this.pos = [0, 0];
540             this.setPos(this.pos[0], this.pos[1]);
541 
542             return this;
543         },
544 
545         /**
546          *  Pushes the position of the turtle on the stack.
547          * @returns {JXG.Turtle} pointer to the turtle object
548          */
549         pushTurtle: function () {
550             this.stack.push([this.pos[0], this.pos[1], this.dir]);
551 
552             return this;
553         },
554 
555         /**
556          *  Gets the last position of the turtle on the stack, sets the turtle to this position and removes this
557          * position from the stack.
558          * @returns {JXG.Turtle} pointer to the turtle object
559          */
560         popTurtle: function () {
561             var status = this.stack.pop();
562             this.pos[0] = status[0];
563             this.pos[1] = status[1];
564             this.dir = status[2];
565             this.setPos(this.pos[0], this.pos[1]);
566 
567             return this;
568         },
569 
570         /**
571          * Rotates the turtle into a new direction.
572          * There are two possibilities:
573          * @param {Number|Array} target If a number is given, it is interpreted as the new direction to look to; If an array
574          * consisting of two Numbers is given targeted is used as a pair coordinates.
575          * @returns {JXG.Turtle} pointer to the turtle object
576          */
577         lookTo: function (target) {
578             var ax, ay, bx, by, beta;
579 
580             if (Type.isArray(target)) {
581                 ax = this.pos[0];
582                 ay = this.pos[1];
583                 bx = target[0];
584                 by = target[1];
585 
586                 // Rotate by the slope of the line [this.pos, target]
587                 beta = Math.atan2(by - ay, bx - ax);
588                 this.right(this.dir - (beta * 180 / Math.PI));
589             } else if (Type.isNumber(target)) {
590                 this.right(this.dir - target);
591             }
592             return this;
593         },
594 
595         /**
596          * Moves the turtle to a given coordinate pair.
597          * The direction is not changed.
598          * @param {Array} target Coordinates of the point where the turtle looks to.
599          * @returns {JXG.Turtle} pointer to the turtle object
600          */
601         moveTo: function (target) {
602             var dx, dy, t;
603 
604             if (Type.isArray(target)) {
605                 dx = target[0] - this.pos[0];
606                 dy = target[1] - this.pos[1];
607 
608                 if (!this.turtleIsHidden) {
609                     t = this.board.create('transform', [dx, dy], {type: 'translate'});
610                     t.applyOnce(this.turtle);
611                     t.applyOnce(this.turtle2);
612                 }
613 
614                 if (this.isPenDown) {
615                     // IE workaround
616                     if (this.curve.dataX.length >= 8192) {
617                         this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this._attributes);
618                         this.objects.push(this.curve);
619                     }
620                 }
621 
622                 this.pos[0] = target[0];
623                 this.pos[1] = target[1];
624 
625                 if (this.isPenDown) {
626                     this.curve.dataX.push(this.pos[0]);
627                     this.curve.dataY.push(this.pos[1]);
628                 }
629                 this.board.update();
630             }
631 
632             return this;
633         },
634 
635         /**
636          * Alias for {@link JXG.Turtle#forward}
637          */
638         fd: function (len) { return this.forward(len); },
639         /**
640          * Alias for {@link JXG.Turtle#back}
641          */
642         bk: function (len) { return this.back(len); },
643         /**
644          * Alias for {@link JXG.Turtle#left}
645          */
646         lt: function (angle) { return this.left(angle); },
647         /**
648          * Alias for {@link JXG.Turtle#right}
649          */
650         rt: function (angle) { return this.right(angle); },
651         /**
652          * Alias for {@link JXG.Turtle#penUp}
653          */
654         pu: function () { return this.penUp(); },
655         /**
656          * Alias for {@link JXG.Turtle#penDown}
657          */
658         pd: function () { return this.penDown(); },
659         /**
660          * Alias for {@link JXG.Turtle#hideTurtle}
661          */
662         ht: function () { return this.hideTurtle(); },
663         /**
664          * Alias for {@link JXG.Turtle#showTurtle}
665          */
666         st: function () { return this.showTurtle(); },
667         /**
668          * Alias for {@link JXG.Turtle#clearScreen}
669          */
670         cs: function () { return this.clearScreen(); },
671         /**
672          * Alias for {@link JXG.Turtle#pushTurtle}
673          */
674         push: function () { return this.pushTurtle(); },
675         /**
676          * Alias for {@link JXG.Turtle#popTurtle}
677          */
678         pop: function () { return this.popTurtle(); },
679 
680         /**
681          * The "co"-coordinate of the turtle curve at position t is returned.
682          *
683          * @param {Number} t parameter
684          * @param {String} co. Either 'X' or 'Y'.
685          * @returns {Number} x-coordinate of the turtle position or x-coordinate of turtle at position t
686          */
687         evalAt: function (t, co) {
688             var i, j, el, tc,
689                 len = this.objects.length;
690 
691             for (i = 0, j = 0; i < len; i++) {
692                 el = this.objects[i];
693 
694                 if (el.elementClass === Const.OBJECT_CLASS_CURVE) {
695                     if (j <= t && t < j + el.numberPoints) {
696                         tc = (t - j);
697                         return el[co](tc);
698                     }
699                     j += el.numberPoints;
700                 }
701             }
702 
703             return this[co]();
704         },
705 
706         /**
707          * if t is not supplied the x-coordinate of the turtle is returned. Otherwise
708          * the x-coordinate of the turtle curve at position t is returned.
709          * @param {Number} t parameter
710          * @returns {Number} x-coordinate of the turtle position or x-coordinate of turtle at position t
711          */
712         X: function (t) {
713             if (!Type.exists(t)) {
714                 return this.pos[0];
715             }
716 
717             return this.evalAt(t, 'X');
718         },
719 
720         /**
721          * if t is not supplied the y-coordinate of the turtle is returned. Otherwise
722          * the y-coordinate of the turtle curve at position t is returned.
723          * @param {Number} t parameter
724          * @returns {Number} x-coordinate of the turtle position or x-coordinate of turtle at position t
725          */
726         Y: function (t) {
727             if (!Type.exists(t)) {
728                 return this.pos[1];
729             }
730             return this.evalAt(t, 'Y');
731         },
732 
733         /**
734          * @returns {Number} z-coordinate of the turtle position
735          */
736         Z: function (t) {
737             return 1.0;
738         },
739 
740         /**
741          * Gives the lower bound of the parameter if the the turtle is treated as parametric curve.
742          */
743         minX: function () {
744             return 0;
745         },
746 
747         /**
748          * Gives the upper bound of the parameter if the the turtle is treated as parametric curve.
749          * May be overwritten in @see generateTerm.
750          */
751         maxX: function () {
752             var i, el,
753                 len = this.objects.length,
754                 np = 0;
755 
756             for (i = 0; i < len; i++) {
757                 el = this.objects[i];
758                 if (el.elementClass === Const.OBJECT_CLASS_CURVE) {
759                     np += this.objects[i].numberPoints;
760                 }
761             }
762             return np;
763         },
764 
765         /**
766          * Checks whether (x,y) is near the curve.
767          * @param {Number} x Coordinate in x direction, screen coordinates.
768          * @param {Number} y Coordinate in y direction, screen coordinates.
769          * @returns {Boolean} True if (x,y) is near the curve, False otherwise.
770          */
771         hasPoint: function (x, y) {
772             var i, el;
773 
774             // run through all curves of this turtle
775             for (i = 0; i < this.objects.length; i++) {
776                 el = this.objects[i];
777 
778                 if (el.type === Const.OBJECT_TYPE_CURVE) {
779                     if (el.hasPoint(x, y)) {
780                         // So what??? All other curves have to be notified now (for highlighting)
781                         return true;
782                         // This has to be done, yet.
783                     }
784                 }
785             }
786             return false;
787         }
788     });
789 
790     /**
791      * @class This element is used to provide a constructor for a turtle.
792      * @pseudo
793      * @description  Creates a new turtle
794      * @name Turtle
795      * @augments JXG.Turtle
796      * @constructor
797      * @type JXG.Turtle
798      *
799      * @param {JXG.Board} board The board the turtle is put on.
800      * @param {Array} parents
801      * @param {Object} attributes Object containing properties for the element such as stroke-color and visibility. See {@link JXG.GeometryElement#setAttribute}
802      * @returns {JXG.Turtle} Reference to the created turtle object.
803      */
804     JXG.createTurtle = function (board, parents, attributes) {
805         var attr;
806         parents = parents || [];
807 
808         attr = Type.copyAttributes(attributes, board.options, 'turtle');
809         return new JXG.Turtle(board, parents, attr);
810     };
811 
812     JXG.registerElement('turtle', JXG.createTurtle);
813 
814     return {
815         Turtle: JXG.Turtle,
816         createTurtle: JXG.createTurtle
817     };
818 });
819