1  /*
  2     Copyright 2008,2009
  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: you can redistribute it and/or modify
 13     it under the terms of the GNU Lesser General Public License as published by
 14     the Free Software Foundation, either version 3 of the License, or
 15     (at your option) any later version.
 16 
 17     JSXGraph is distributed in the hope that it will be useful,
 18     but WITHOUT ANY WARRANTY; without even the implied warranty of
 19     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 20     GNU Lesser General Public License for more details.
 21 
 22     You should have received a copy of the GNU Lesser General Public License
 23     along with JSXGraph.  If not, see <http://www.gnu.org/licenses/>.
 24 */
 25 /**
 26  * @fileoverview The JSXGraph object Turtle is defined. It acts like
 27  * "turtle graphics".
 28  * @author A.W.
 29  */
 30 
 31 /**
 32  * Constructs a new Turtle object.
 33  * @class This is the Turtle class. 
 34  * It is derived from {@link JXG.GeometryElement}.
 35  * It stores all properties required
 36  * to move a turtle.
 37  * @constructor
 38  * @param {String} JXG.board The board the new turtle is drawn on.
 39  * @param {Array}  [x,y,angle] Start position and start direction of the turtle. Possible values are
 40  * [x,y,angle]
 41  * [[x,y],angle]
 42  * [x,y]
 43  * [[x,y]]
 44  * @param {Object} attributes Attributes to change the visual properties of the turtle object
 45  * All angles are in degrees.
 46   */
 47 JXG.Turtle = function (board, parents, attributes) {
 48     var x,y,dir;
 49     this.type = JXG.OBJECT_TYPE_TURTLE;
 50     this.turtleIsHidden = false;
 51     this.board = board;
 52     this.attributes = JXG.checkAttributes(attributes,{withLabel:false,layer:null});
 53     this.attributes.straightFirst = false;
 54     this.attributes.straightLast = false;
 55     x = 0;
 56     y = 0;
 57     dir = 90;
 58     if (parents.length!=0) {
 59         if (parents.length==3) {   // [x,y,dir]
 60             // Only numbers are accepted at the moment
 61             x = parents[0];
 62             y = parents[1];
 63             dir = parents[2];
 64         } else if (parents.length==2) {
 65             if (JXG.isArray(parents[0])) {  // [[x,y],dir]
 66                 x = parents[0][0];
 67                 y = parents[0][1];
 68                 dir = parents[1];
 69             } else {  // [x,y]
 70                 x = parents[0];
 71                 y = parents[1];
 72             }
 73         } else { // [[x,y]]
 74             x = parents[0][0];
 75             y = parents[0][1];
 76         }
 77     }
 78     
 79     this.init(x,y,dir);
 80     return this;
 81 };
 82 JXG.Turtle.prototype = new JXG.GeometryElement;
 83 
 84 /**
 85 * Initialize a new turtle or reinitialize  a turtle after {@link #clearscreen}.
 86 * @private
 87 */
 88 JXG.Turtle.prototype.init = function(x,y,dir) {
 89     this.arrowLen = 20.0/Math.sqrt(this.board.unitX*this.board.unitX+this.board.unitY*this.board.unitY);
 90 
 91     this.pos = [x,y];
 92     this.isPenDown = true;
 93     this.dir = 90;
 94     this.stack = [];
 95     this.objects = [];
 96     this.attributes.curveType = 'plot';
 97     this.curve = this.board.create('curve',[[this.pos[0]],[this.pos[1]]],this.attributes);
 98     this.objects.push(this.curve);
 99 
100     this.turtle = this.board.create('point',this.pos,{fixed:true,name:' ',visible:false,withLabel:false});
101     this.objects.push(this.turtle);
102     
103     this.turtle2 = this.board.create('point',[this.pos[0],this.pos[1]+this.arrowLen],
104             {fixed:true,name:' ',visible:false,withLabel:false});
105     this.objects.push(this.turtle2);
106     
107     var w = this.attributes.strokeWidth || this.attributes.strokewidth || 2;  // Attention; should be moved to Options.js
108     this.arrow = this.board.create('line',[this.turtle,this.turtle2],
109             {strokeColor:'#ff0000',straightFirst:false,straightLast:false,strokeWidth:w,withLabel:false,lastArrow:true});
110     this.objects.push(this.arrow);
111 
112     this.right(90-dir);
113     this.board.update();
114 };
115 
116 /**
117 * Move the turtle forward.
118 * @param {float} length of forward move in user coordinates
119 * @type {JXG.Turtle}
120 * @return pointer to the turtle object
121 */
122 JXG.Turtle.prototype.forward = function(len) {
123     if (len==0) { return; }
124     var dx = len*Math.cos(this.dir*Math.PI/180.0);
125     var dy = len*Math.sin(this.dir*Math.PI/180.0);
126     if (!this.turtleIsHidden) {
127         var t = this.board.create('transform', [dx,dy], {type:'translate'});
128         t.applyOnce(this.turtle);
129         t.applyOnce(this.turtle2);
130     }
131     if (this.isPenDown) if (this.curve.dataX.length>=8192) { // IE workaround
132         this.curve = this.board.create('curve',
133                [[this.pos[0]],[this.pos[1]]],this.attributes);
134         this.objects.push(this.curve);
135     }
136     this.pos[0] += dx;
137     this.pos[1] += dy;
138     if (this.isPenDown) {
139         this.curve.dataX.push(this.pos[0]);
140         this.curve.dataY.push(this.pos[1]);
141     }
142     this.board.update();
143     return this;
144 };
145      
146 /**
147 * Move the turtle backwards.
148 * @param {float} length of backwards move in user coordinates
149 * @type {JXG.Turtle}
150 * @return pointer to the turtle object
151 */
152 JXG.Turtle.prototype.back = function(len) {
153     return this.forward(-len);
154 };
155      
156 /**
157 * Rotate the turtle direction to the right
158 * @param {float} angle of the rotation in degrees
159 * @type {JXG.Turtle}
160 * @return pointer to the turtle object
161 */
162 JXG.Turtle.prototype.right = function(angle) {
163     this.dir -= angle;
164     this.dir %= 360.0;
165     if (!this.turtleIsHidden) {
166         var t = this.board.create('transform', [-angle*Math.PI/180.0,this.turtle], {type:'rotate'});
167         t.applyOnce(this.turtle2);
168     }
169     this.board.update();
170     return this;
171 };
172      
173 /**
174 * Rotate the turtle direction to the right.
175 * @param {float} angle of the rotation in degrees
176 * @type {JXG.Turtle}
177 * @return pointer to the turtle object
178 */
179 JXG.Turtle.prototype.left = function(angle) {
180     return this.right(-angle);
181 };
182 
183 /**
184 * Pen up, stops visible drawing
185 * @type {JXG.Turtle}
186 * @return pointer to the turtle object
187 */
188 JXG.Turtle.prototype.penUp = function() {
189     this.isPenDown = false;
190     return this;
191 };
192 
193 /**
194 * Pen down, continues visible drawing
195 * @type {JXG.Turtle}
196 * @return pointer to the turtle object
197 */
198 JXG.Turtle.prototype.penDown = function() {
199     this.isPenDown = true;
200     this.curve = this.board.create('curve',[[this.pos[0]],[this.pos[1]]],this.attributes);
201     this.objects.push(this.curve);
202             
203     return this;
204 };
205 
206 /**
207 *  Removes the turtle curve from the board. The turtle stays in its position.
208 * @type {JXG.Turtle}
209 * @return pointer to the turtle object
210 */
211 JXG.Turtle.prototype.clean = function() {
212     for(var i=0;i<this.objects.length;i++) {
213         var el = this.objects[i];
214         if (el.type==JXG.OBJECT_TYPE_CURVE) {
215             this.board.removeObject(el.id);
216             this.objects.splice(i,1);
217         }
218     }
219     this.curve = this.board.create('curve',
220               [[this.pos[0]],[this.pos[1]]],this.attributes);
221     this.objects.push(this.curve);
222     this.board.update();
223     return this;
224 };
225 
226 /**
227 *  Removes the turtle completely and resets it to its initial position and direction.
228 * @type {JXG.Turtle}
229 * @return pointer to the turtle object
230 */
231 JXG.Turtle.prototype.clearScreen = function() {
232     for(var i=0;i<this.objects.length;i++) {
233         var el = this.objects[i];
234         this.board.removeObject(el.id);
235     }
236     this.init(0,0,90);
237     return this;
238 };
239 
240 /**
241 *  Moves the turtle without drawing to a new position
242 * @param {float} x new x- coordinate 
243 * @param {float} y new y- coordinate 
244 * @type {JXG.Turtle}
245 * @return pointer to the turtle object
246 */
247 JXG.Turtle.prototype.setPos = function(x,y) {
248     if (JXG.isArray(x)) {
249         this.pos = x;
250     } else {
251         this.pos = [x,y];
252     }
253     if (!this.turtleIsHidden) {
254         this.turtle.setPositionDirectly(JXG.COORDS_BY_USER,x,y);
255         this.turtle2.setPositionDirectly(JXG.COORDS_BY_USER,x,y+this.arrowLen);
256         var t = this.board.create('transform', 
257                 [-(this.dir-90)*Math.PI/180.0,this.turtle], {type:'rotate'});
258         t.applyOnce(this.turtle2);
259     }
260     this.curve = this.board.create('curve',[[this.pos[0]],[this.pos[1]]],this.attributes);
261     this.objects.push(this.curve);
262     this.board.update();
263     return this;
264 };
265 
266 /**
267 *  Sets the pen size. Equivalent to setProperty({strokeWidth:size})
268 * @param {float} size
269 * @type {JXG.Turtle}
270 * @return pointer to the turtle object
271 */
272 JXG.Turtle.prototype.setPenSize = function(size) { 
273     this.attributes.strokeWidth = size; 
274     this.curve = this.board.create('curve',[[this.pos[0]],[this.pos[1]]],this.attributes);
275     this.objects.push(this.curve);
276     return this;
277 };
278 
279 /**
280 *  Sets the pen color. Equivalent to setProperty({strokeColor:color})
281 * @param {string} color
282 * @type {JXG.Turtle}
283 * @return pointer to the turtle object
284 */
285 JXG.Turtle.prototype.setPenColor = function(colStr) { 
286     this.attributes.strokeColor = colStr; 
287     this.curve = this.board.create('curve',[[this.pos[0]],[this.pos[1]]],this.attributes);
288     this.objects.push(this.curve);
289     return this;
290 };
291 
292 /**
293 *  Sets the highlight pen color. Equivalent to setProperty({highlightStrokeColor:color})
294 * @param {string} color
295 * @type {JXG.Turtle}
296 * @return pointer to the turtle object
297 */
298 JXG.Turtle.prototype.setHighlightPenColor = function(colStr) { 
299     this.attributes.highlightStrokeColor = colStr; 
300     this.curve = this.board.create('curve',[[this.pos[0]],[this.pos[1]]],this.attributes);
301     this.objects.push(this.curve);
302     return this;
303 };
304 
305 /**
306 * Sets properties of the turtle, see also {@link JXG.GeometryElement#setProperty}.
307 * Sets the property for all curves of the turtle.
308 * @param {Object} key:value pairs
309 * @type {JXG.Turtle}
310 * @return pointer to the turtle object
311 */
312 JXG.Turtle.prototype.setProperty = function() {
313     var pair;
314     var pairRaw;
315     var i, el;
316     var key;
317     for (i=0; i<arguments.length; i++) {
318         pairRaw = arguments[i];
319         if (typeof pairRaw == 'string') {    // pairRaw is string of the form 'key:value'
320             pair = pairRaw.split(':');
321         } else if (!JXG.isArray(pairRaw)) {    
322             // pairRaw consists of objects of the form {key1:value1,key2:value2,...}
323             for (var key in pairRaw) {
324                 this.setProperty([key,pairRaw[key]]);
325             }
326             return this;
327         } else {                             // pairRaw consists of array [key,value]
328             pair = pairRaw;
329         }
330         this.attributes[pair[0]] = pair[1];
331     }
332     for (i=0; i<this.objects.length; i++) {
333         el = this.objects[i];
334         if (el.type==JXG.OBJECT_TYPE_CURVE) {
335             el.setProperty(this.attributes);
336         }
337     }
338     //this.curve = this.board.create('curve',[[this.pos[0]],[this.pos[1]]],this.attributes);
339     //this.objects.push(this.curve);
340     return this;
341 };
342 
343 /**
344 *  Sets the visibility of the turtle head to true,
345 * @type {JXG.Turtle}
346 * @return pointer to the turtle object
347 */
348 JXG.Turtle.prototype.showTurtle = function() { 
349     this.turtleIsHidden = false; 
350     this.arrow.setProperty('visible:true');
351     this.setPos(this.pos[0],this.pos[1]);
352     this.board.update();
353     return this;
354 };
355 
356 /**
357 *  Sets the visibility of the turtle head to false,
358 * @type {JXG.Turtle}
359 * @return pointer to the turtle object
360 */
361 JXG.Turtle.prototype.hideTurtle = function() { 
362     this.turtleIsHidden = true;
363     this.arrow.setProperty('visible:false');
364     this.setPos(this.pos[0],this.pos[1]);
365     this.board.update();
366     return this;
367 };
368 
369 /**
370 *  Moves the turtle to position [0,0].
371 * @type {JXG.Turtle}
372 * @return pointer to the turtle object
373 */
374 JXG.Turtle.prototype.home = function() { 
375     this.pos = [0,0];
376     this.setPos(this.pos[0],this.pos[1]);
377     return this;
378 };
379 
380 /**
381 *  Pushes the position of the turtle on the stack.
382 * @type {JXG.Turtle}
383 * @return pointer to the turtle object
384 */
385 JXG.Turtle.prototype.pushTurtle = function() { 
386     this.stack.push([this.pos[0],this.pos[1],this.dir]);
387     return this;
388 };
389 
390 /**
391 *  Gets the last position of the turtle on the stack, sets the turtle to this position and removes this 
392 * position from the stack.
393 * @type {JXG.Turtle}
394 * @return pointer to the turtle object
395 */
396 JXG.Turtle.prototype.popTurtle = function() { 
397     var status = this.stack.pop();
398     this.pos[0] = status[0];
399     this.pos[1] = status[1];
400     this.dir = status[2];
401     this.setPos(this.pos[0],this.pos[1]);
402     return this;
403 };
404 
405 /**
406 * Rotates the turtle into a new direction.
407 * There are two possibilities:
408 * @param {float} angle New direction to look to
409 * or
410 * @param {float} x New x coordinate to look to
411 * @param {float} y New y coordinate to look to
412 * @type {JXG.Turtle}
413 * @return pointer to the turtle object
414 */
415 JXG.Turtle.prototype.lookTo = function(target) { 
416     if (JXG.isArray(target)) {
417         var ax = this.pos[0];
418         var ay = this.pos[1];
419         var bx = target[0];
420         var by = target[1];
421         var beta; 
422         // Rotate by the slope of the line [this.pos, target]
423         /*
424         var sgn = (bx-ax>0)?1:-1;
425         if (Math.abs(bx-ax)>0.0000001) {
426             beta = Math.atan2(by-ay,bx-ax)+((sgn<0)?Math.PI:0);  
427         } else {
428             beta = ((by-ay>0)?0.5:-0.5)*Math.PI;
429         }
430         */
431         beta = Math.atan2(by-ay,bx-ax);
432         this.right(this.dir-(beta*180/Math.PI));
433     } else if (JXG.isNumber(target)) {
434         this.right(this.dir-(target));
435     }
436     return this;
437 };
438 
439 /**
440 * Moves the turtle to a given coordinate pair.
441 * The direction is not changed.
442 * @param {float} x New x coordinate to look to
443 * @param {float} y New y coordinate to look to
444 * @type {JXG.Turtle}
445 * @return pointer to the turtle object
446 */
447 JXG.Turtle.prototype.moveTo = function(target) { 
448     if (JXG.isArray(target)) {
449         var dx = target[0]-this.pos[0];
450         var dy = target[1]-this.pos[1];
451         if (!this.turtleIsHidden) {
452             var t = this.board.create('transform', [dx,dy], {type:'translate'});
453             t.applyOnce(this.turtle);
454             t.applyOnce(this.turtle2);
455         }
456         if (this.isPenDown) if (this.curve.dataX.length>=8192) { // IE workaround
457             this.curve = this.board.create('curve',
458                [[this.pos[0]],[this.pos[1]]],this.attributes);
459             this.objects.push(this.curve);
460         }
461         this.pos[0] = target[0];
462         this.pos[1] = target[1];
463         if (this.isPenDown) {
464             this.curve.dataX.push(this.pos[0]);
465             this.curve.dataY.push(this.pos[1]);
466         }
467         this.board.update();
468     }
469     return this;
470 };
471 
472 /**
473   * Alias for {@link #forward}
474   */
475 JXG.Turtle.prototype.fd = function(len) { return this.forward(len); };
476 /**
477   * Alias for {@link #back}
478   */
479 JXG.Turtle.prototype.bk = function(len) { return this.back(len); };
480 /**
481   * Alias for {@link #left}
482   */
483 JXG.Turtle.prototype.lt = function(angle) { return this.left(angle); };
484 /**
485   * Alias for {@link #right}
486   */
487 JXG.Turtle.prototype.rt = function(angle) { return this.right(angle); };
488 /**
489   * Alias for {@link #penUp}
490   */
491 JXG.Turtle.prototype.pu = function() { return this.penUp(); };
492 /**
493   * Alias for {@link #penDown}
494   */
495 JXG.Turtle.prototype.pd = function() { return this.penDown(); };
496 /**
497   * Alias for {@link #hideTurtle}
498   */
499 JXG.Turtle.prototype.ht = function() { return this.hideTurtle(); };
500 /**
501   * Alias for {@link #showTurtle}
502   */
503 JXG.Turtle.prototype.st = function() { return this.showTurtle(); };
504 /**
505   * Alias for {@link #clearScreen}
506   */
507 JXG.Turtle.prototype.cs = function() { return this.clearScreen(); };
508 /**
509   * Alias for {@link #pushTurtle}
510   */
511 JXG.Turtle.prototype.push = function() { return this.pushTurtle(); };
512 /**
513   * Alias for {@link #popTurtle}
514   */
515 JXG.Turtle.prototype.pop = function() { return this.popTurtle(); };
516 
517 /**
518 * @return x-coordinate of the turtle position
519 * @type {float}
520 */
521 JXG.Turtle.prototype.X = function(target) { 
522     return this.pos[0]; //this.turtle.X();
523 };
524 
525 /**
526 * @return y-coordinate of the turtle position
527 * @type {float}
528 */
529 JXG.Turtle.prototype.Y = function(target) { 
530     return this.pos[1]; //this.turtle.Y();
531 };
532 
533 /**
534  * Checks whether (x,y) is near the curve.
535  * @param {int} x Coordinate in x direction, screen coordinates.
536  * @param {int} y Coordinate in y direction, screen coordinates.
537  * @param {y} Find closest point on the curve to (x,y)
538  * @return {bool} True if (x,y) is near the curve, False otherwise.
539  */
540 JXG.Turtle.prototype.hasPoint = function (x,y) {
541     var i, el;
542     for(i=0;i<this.objects.length;i++) {  // run through all curves of this turtle
543         el = this.objects[i];
544         if (el.type==JXG.OBJECT_TYPE_CURVE) {
545             if (el.hasPoint(x,y)) {
546                 return true;              // So what??? All other curves have to be notified now (for highlighting)
547                                           // This has to be done, yet.
548             }
549         }
550     }
551     return false;
552 };
553 
554 /**
555  * Creates a new turtle
556  * @param {JXG.Board} board The board the turtle is put on.
557  * @param {Array} parents 
558  * @param {Object} attributs Object containing properties for the element such as stroke-color and visibility. See {@link JXG.GeometryElement#setProperty}
559  * @type JXG.Turtle
560  * @return Reference to the created turtle object.
561  */
562 JXG.createTurtle = function(board, parents, attributes) {
563     if (parents==null) { parents = []; }
564     return new JXG.Turtle(board,parents,attributes);
565 };
566 
567 JXG.JSXGraph.registerElement('turtle', JXG.createTurtle);
568