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 this.constructor(board, attributes, JXG.OBJECT_TYPE_TURTLE, JXG.OBJECT_CLASS_OTHER); 49 50 var x,y,dir; 51 this.turtleIsHidden = false; 52 this.board = board; 53 this.visProp.curveType = 'plot'; 54 55 // Save visProp in this._attributes. 56 // this._attributes is overwritten by setPenSize, setPenColor... 57 // Setting the color or size affects the turtle from the time of 58 // calling the method, 59 // whereas Turtle.setProperty affects all turtle curves. 60 this._attributes = JXG.copyAttributes(this.visProp, board.options, 'turtle'); 61 delete(this._attributes['id']); 62 63 x = 0; 64 y = 0; 65 dir = 90; 66 if (parents.length!=0) { 67 if (parents.length==3) { // [x,y,dir] 68 // Only numbers are accepted at the moment 69 x = parents[0]; 70 y = parents[1]; 71 dir = parents[2]; 72 } else if (parents.length==2) { 73 if (JXG.isArray(parents[0])) { // [[x,y],dir] 74 x = parents[0][0]; 75 y = parents[0][1]; 76 dir = parents[1]; 77 } else { // [x,y] 78 x = parents[0]; 79 y = parents[1]; 80 } 81 } else { // [[x,y]] 82 x = parents[0][0]; 83 y = parents[0][1]; 84 } 85 } 86 87 this.init(x,y,dir); 88 return this; 89 }; 90 JXG.Turtle.prototype = new JXG.GeometryElement; 91 92 JXG.extend(JXG.Turtle.prototype, /** @lends JXG.Turtle.prototype */ { 93 /** 94 * Initialize a new turtle or reinitialize a turtle after {@link #clearscreen}. 95 * @private 96 */ 97 init: function(x,y,dir) { 98 this.arrowLen = 20.0/Math.sqrt(this.board.unitX*this.board.unitX+this.board.unitY*this.board.unitY); 99 100 this.pos = [x,y]; 101 this.isPenDown = true; 102 this.dir = 90; 103 this.stack = []; 104 this.objects = []; 105 this.curve = this.board.create('curve',[[this.pos[0]],[this.pos[1]]], this._attributes); 106 this.objects.push(this.curve); 107 108 this.turtle = this.board.create('point',this.pos,{fixed:true, name:' ', visible:false, withLabel:false}); 109 this.objects.push(this.turtle); 110 111 this.turtle2 = this.board.create('point',[this.pos[0],this.pos[1]+this.arrowLen], 112 {fixed:true, name:' ', visible:false, withLabel:false}); 113 this.objects.push(this.turtle2); 114 115 this.visProp.arrow['lastArrow'] = true; 116 this.visProp.arrow['straightFirst'] = false; 117 this.visProp.arrow['straightLast'] = false; 118 this.arrow = this.board.create('line', [this.turtle,this.turtle2], this.visProp.arrow); 119 this.objects.push(this.arrow); 120 121 this.right(90-dir); 122 this.board.update(); 123 }, 124 125 /** 126 * Move the turtle forward. 127 * @param {float} length of forward move in user coordinates 128 * @type {JXG.Turtle} 129 * @return pointer to the turtle object 130 */ 131 forward: function(len) { 132 if (len === 0) { 133 return this; 134 } 135 136 var dx = len*Math.cos(this.dir*Math.PI/180.0), 137 dy = len*Math.sin(this.dir*Math.PI/180.0); 138 139 if (!this.turtleIsHidden) { 140 var t = this.board.create('transform', [dx,dy], {type:'translate'}); 141 t.applyOnce(this.turtle); 142 t.applyOnce(this.turtle2); 143 } 144 if (this.isPenDown) if (this.curve.dataX.length>=8192) { // IE workaround 145 this.curve = this.board.create('curve', 146 [[this.pos[0]],[this.pos[1]]], this._attributes); 147 this.objects.push(this.curve); 148 } 149 this.pos[0] += dx; 150 this.pos[1] += dy; 151 if (this.isPenDown) { 152 this.curve.dataX.push(this.pos[0]); 153 this.curve.dataY.push(this.pos[1]); 154 } 155 156 this.board.update(); 157 return this; 158 }, 159 160 /** 161 * Move the turtle backwards. 162 * @param {float} length of backwards move in user coordinates 163 * @type {JXG.Turtle} 164 * @return pointer to the turtle object 165 */ 166 back: function(len) { 167 return this.forward(-len); 168 }, 169 170 /** 171 * Rotate the turtle direction to the right 172 * @param {float} angle of the rotation in degrees 173 * @type {JXG.Turtle} 174 * @return pointer to the turtle object 175 */ 176 right: function(angle) { 177 this.dir -= angle; 178 this.dir %= 360.0; 179 if (!this.turtleIsHidden) { 180 var t = this.board.create('transform', [-angle*Math.PI/180.0,this.turtle], {type:'rotate'}); 181 t.applyOnce(this.turtle2); 182 } 183 this.board.update(); 184 return this; 185 }, 186 187 /** 188 * Rotate the turtle direction to the right. 189 * @param {float} angle of the rotation in degrees 190 * @type {JXG.Turtle} 191 * @return pointer to the turtle object 192 */ 193 left: function(angle) { 194 return this.right(-angle); 195 }, 196 197 /** 198 * Pen up, stops visible drawing 199 * @type {JXG.Turtle} 200 * @return pointer to the turtle object 201 */ 202 penUp: function() { 203 this.isPenDown = false; 204 return this; 205 }, 206 207 /** 208 * Pen down, continues visible drawing 209 * @type {JXG.Turtle} 210 * @return pointer to the turtle object 211 */ 212 penDown: function() { 213 this.isPenDown = true; 214 this.curve = this.board.create('curve',[[this.pos[0]],[this.pos[1]]], this._attributes); 215 this.objects.push(this.curve); 216 217 return this; 218 }, 219 220 /** 221 * Removes the turtle curve from the board. The turtle stays in its position. 222 * @type {JXG.Turtle} 223 * @return pointer to the turtle object 224 */ 225 clean: function() { 226 for(var i=0;i<this.objects.length;i++) { 227 var el = this.objects[i]; 228 if (el.type==JXG.OBJECT_TYPE_CURVE) { 229 this.board.removeObject(el); 230 this.objects.splice(i,1); 231 } 232 } 233 this.curve = this.board.create('curve', 234 [[this.pos[0]],[this.pos[1]]], this._attributes); 235 this.objects.push(this.curve); 236 this.board.update(); 237 return this; 238 }, 239 240 /** 241 * Removes the turtle completely and resets it to its initial position and direction. 242 * @type {JXG.Turtle} 243 * @return pointer to the turtle object 244 */ 245 clearScreen: function() { 246 var i, el, len = this.objects.length; 247 for(i=0; i<len; i++) { 248 el = this.objects[i]; 249 this.board.removeObject(el); 250 } 251 this.init(0,0, 90); 252 return this; 253 }, 254 255 /** 256 * Moves the turtle without drawing to a new position 257 * @param {float} x new x- coordinate 258 * @param {float} y new y- coordinate 259 * @type {JXG.Turtle} 260 * @return pointer to the turtle object 261 */ 262 setPos: function(x,y) { 263 if (JXG.isArray(x)) { 264 this.pos = x; 265 } else { 266 this.pos = [x,y]; 267 } 268 if (!this.turtleIsHidden) { 269 this.turtle.setPositionDirectly(JXG.COORDS_BY_USER, [x, y]); 270 this.turtle2.setPositionDirectly(JXG.COORDS_BY_USER, [x, y + this.arrowLen]); 271 var t = this.board.create('transform', 272 [-(this.dir-90)*Math.PI/180.0,this.turtle], {type:'rotate'}); 273 t.applyOnce(this.turtle2); 274 } 275 this.curve = this.board.create('curve',[[this.pos[0]],[this.pos[1]]], this._attributes); 276 this.objects.push(this.curve); 277 this.board.update(); 278 return this; 279 }, 280 281 /** 282 * Sets the pen size. Equivalent to setProperty({strokeWidth:size}) 283 * but affects only the future turtle. 284 * @param {float} size 285 * @type {JXG.Turtle} 286 * @return pointer to the turtle object 287 */ 288 setPenSize: function(size) { 289 //this.visProp.strokewidth = size; 290 this.curve = this.board.create('curve',[[this.pos[0]],[this.pos[1]]], this.copyAttr('strokeWidth', size)); 291 this.objects.push(this.curve); 292 return this; 293 }, 294 295 /** 296 * Sets the pen color. Equivalent to setProperty({strokeColor:color}) 297 * but affects only the future turtle. 298 * @param {string} color 299 * @type {JXG.Turtle} 300 * @return pointer to the turtle object 301 */ 302 setPenColor: function(colStr) { 303 //this.visProp.strokecolor = colStr; 304 this.curve = this.board.create('curve',[[this.pos[0]],[this.pos[1]]], this.copyAttr('strokeColor', colStr)); 305 this.objects.push(this.curve); 306 return this; 307 }, 308 309 /** 310 * Sets the highlight pen color. Equivalent to setProperty({highlightStrokeColor:color}) 311 * but affects only the future turtle. 312 * @param {string} color 313 * @type {JXG.Turtle} 314 * @return pointer to the turtle object 315 */ 316 setHighlightPenColor: function(colStr) { 317 //this.visProp.highlightstrokecolor = colStr; 318 this.curve = this.board.create('curve',[[this.pos[0]],[this.pos[1]]], this.copyAttr('highlightStrokeColor', colStr)); 319 this.objects.push(this.curve); 320 return this; 321 }, 322 323 /** 324 * Sets properties of the turtle, see also {@link JXG.GeometryElement#setProperty}. 325 * Sets the property for all curves of the turtle in the past and in the future. 326 * @param {Object} key:value pairs 327 * @type {JXG.Turtle} 328 * @return pointer to the turtle object 329 */ 330 setProperty: function(attributes) { 331 var i, el, len = this.objects.length, tmp; 332 for (i=0; i<len; i++) { 333 el = this.objects[i]; 334 if (el.type==JXG.OBJECT_TYPE_CURVE) { 335 el.setProperty(attributes); 336 } 337 } 338 // Set visProp of turtle 339 tmp = this.visProp['id']; 340 this.visProp = JXG.deepCopy(this.curve.visProp); 341 this.visProp['id'] = tmp; 342 this._attributes = JXG.deepCopy(this.visProp); 343 delete(this._attributes['id']); 344 return this; 345 }, 346 347 /** 348 * Set a future attribute of the turtle. 349 * @private 350 * @param {String} key 351 * @param {Object} value (number, string) 352 * @type {Object} 353 * @return pointer to an attributes object 354 */ 355 copyAttr: function(key, val) { 356 this._attributes[key.toLowerCase()] = val; 357 return this._attributes; 358 }, 359 360 /** 361 * Sets the visibility of the turtle head to true, 362 * @type {JXG.Turtle} 363 * @return pointer to the turtle object 364 */ 365 showTurtle: function() { 366 this.turtleIsHidden = false; 367 this.arrow.setProperty({visible:true}); 368 this.visProp.arrow['visible'] = false; 369 this.setPos(this.pos[0],this.pos[1]); 370 this.board.update(); 371 return this; 372 }, 373 374 /** 375 * Sets the visibility of the turtle head to false, 376 * @type {JXG.Turtle} 377 * @return pointer to the turtle object 378 */ 379 hideTurtle: function() { 380 this.turtleIsHidden = true; 381 this.arrow.setProperty({visible:false}); 382 this.visProp.arrow['visible'] = false; 383 //this.setPos(this.pos[0],this.pos[1]); 384 this.board.update(); 385 return this; 386 }, 387 388 /** 389 * Moves the turtle to position [0,0]. 390 * @type {JXG.Turtle} 391 * @return pointer to the turtle object 392 */ 393 home: function() { 394 this.pos = [0,0]; 395 this.setPos(this.pos[0],this.pos[1]); 396 return this; 397 }, 398 399 /** 400 * Pushes the position of the turtle on the stack. 401 * @type {JXG.Turtle} 402 * @return pointer to the turtle object 403 */ 404 pushTurtle: function() { 405 this.stack.push([this.pos[0],this.pos[1],this.dir]); 406 return this; 407 }, 408 409 /** 410 * Gets the last position of the turtle on the stack, sets the turtle to this position and removes this 411 * position from the stack. 412 * @type {JXG.Turtle} 413 * @return pointer to the turtle object 414 */ 415 popTurtle: function() { 416 var status = this.stack.pop(); 417 this.pos[0] = status[0]; 418 this.pos[1] = status[1]; 419 this.dir = status[2]; 420 this.setPos(this.pos[0],this.pos[1]); 421 return this; 422 }, 423 424 /** 425 * Rotates the turtle into a new direction. 426 * There are two possibilities: 427 * @param {float} angle New direction to look to 428 * or 429 * @param {float} x New x coordinate to look to 430 * @param {float} y New y coordinate to look to 431 * @type {JXG.Turtle} 432 * @return pointer to the turtle object 433 */ 434 lookTo: function(target) { 435 if (JXG.isArray(target)) { 436 var ax = this.pos[0]; 437 var ay = this.pos[1]; 438 var bx = target[0]; 439 var by = target[1]; 440 var beta; 441 // Rotate by the slope of the line [this.pos, target] 442 /* 443 var sgn = (bx-ax>0)?1:-1; 444 if (Math.abs(bx-ax)>0.0000001) { 445 beta = Math.atan2(by-ay,bx-ax)+((sgn<0)?Math.PI:0); 446 } else { 447 beta = ((by-ay>0)?0.5:-0.5)*Math.PI; 448 } 449 */ 450 beta = Math.atan2(by-ay,bx-ax); 451 this.right(this.dir-(beta*180/Math.PI)); 452 } else if (JXG.isNumber(target)) { 453 this.right(this.dir-(target)); 454 } 455 return this; 456 }, 457 458 /** 459 * Moves the turtle to a given coordinate pair. 460 * The direction is not changed. 461 * @param {float} x New x coordinate to look to 462 * @param {float} y New y coordinate to look to 463 * @type {JXG.Turtle} 464 * @return pointer to the turtle object 465 */ 466 moveTo: function(target) { 467 if (JXG.isArray(target)) { 468 var dx = target[0]-this.pos[0]; 469 var dy = target[1]-this.pos[1]; 470 if (!this.turtleIsHidden) { 471 var t = this.board.create('transform', [dx,dy], {type:'translate'}); 472 t.applyOnce(this.turtle); 473 t.applyOnce(this.turtle2); 474 } 475 if (this.isPenDown) if (this.curve.dataX.length>=8192) { // IE workaround 476 this.curve = this.board.create('curve', 477 [[this.pos[0]],[this.pos[1]]], this._attributes); 478 this.objects.push(this.curve); 479 } 480 this.pos[0] = target[0]; 481 this.pos[1] = target[1]; 482 if (this.isPenDown) { 483 this.curve.dataX.push(this.pos[0]); 484 this.curve.dataY.push(this.pos[1]); 485 } 486 this.board.update(); 487 } 488 return this; 489 }, 490 491 /** 492 * Alias for {@link #forward} 493 */ 494 fd: function(len) { return this.forward(len); }, 495 /** 496 * Alias for {@link #back} 497 */ 498 bk: function(len) { return this.back(len); }, 499 /** 500 * Alias for {@link #left} 501 */ 502 lt: function(angle) { return this.left(angle); }, 503 /** 504 * Alias for {@link #right} 505 */ 506 rt: function(angle) { return this.right(angle); }, 507 /** 508 * Alias for {@link #penUp} 509 */ 510 pu: function() { return this.penUp(); }, 511 /** 512 * Alias for {@link #penDown} 513 */ 514 pd: function() { return this.penDown(); }, 515 /** 516 * Alias for {@link #hideTurtle} 517 */ 518 ht: function() { return this.hideTurtle(); }, 519 /** 520 * Alias for {@link #showTurtle} 521 */ 522 st: function() { return this.showTurtle(); }, 523 /** 524 * Alias for {@link #clearScreen} 525 */ 526 cs: function() { return this.clearScreen(); }, 527 /** 528 * Alias for {@link #pushTurtle} 529 */ 530 push: function() { return this.pushTurtle(); }, 531 /** 532 * Alias for {@link #popTurtle} 533 */ 534 pop: function() { return this.popTurtle(); }, 535 536 /** 537 * the "co"-coordinate of the turtle curve at position t is returned. 538 * @param {float} t parameter 539 * @param {string} coordinate. Either 'X' or 'Y'. 540 * @return {float} x-coordinate of the turtle position or x-coordinate of turtle at position t 541 */ 542 evalAt: function(/** float */ t, /** string */ co) /** float */ { 543 var i, j, el, tc, len = this.objects.length; 544 for (i=0, j=0; i<len; i++) { 545 el = this.objects[i]; 546 if (el.elementClass == JXG.OBJECT_CLASS_CURVE) { 547 if (j<=t && t<j+el.numberPoints) { 548 tc = (t-j); 549 return el[co](tc); 550 } 551 j += el.numberPoints; 552 } 553 } 554 return this[co](); 555 }, 556 557 /** 558 * if t is not supplied the x-coordinate of the turtle is returned. Otherwise 559 * the x-coordinate of the turtle curve at position t is returned. 560 * @param {float} t parameter 561 * @return {float} x-coordinate of the turtle position or x-coordinate of turtle at position t 562 */ 563 X: function(/** float */ t) /** float */ { 564 if (typeof t == 'undefined' ) { 565 return this.pos[0]; //this.turtle.X(); 566 } else { 567 return this.evalAt(t, 'X'); 568 } 569 }, 570 571 /** 572 * if t is not supplied the y-coordinate of the turtle is returned. Otherwise 573 * the y-coordinate of the turtle curve at position t is returned. 574 * @param {float} t parameter 575 * @return {float} x-coordinate of the turtle position or x-coordinate of turtle at position t 576 */ 577 Y: function(/** float */ t) /** float */ { 578 if (typeof t == 'undefined' ) { 579 return this.pos[1]; //this.turtle.Y(); 580 } else { 581 return this.evalAt(t, 'Y'); 582 } 583 }, 584 585 /** 586 * @return z-coordinate of the turtle position 587 * @type {float} 588 */ 589 Z: function(/** float */ t) /** float */ { 590 return 1.0; 591 }, 592 593 /** 594 * Gives the lower bound of the parameter if the the turtle is treated as parametric curve. 595 */ 596 minX: function () { 597 return 0; 598 }, 599 600 /** 601 * Gives the upper bound of the parameter if the the turtle is treated as parametric curve. 602 * May be overwritten in @see generateTerm. 603 */ 604 maxX: function () { 605 var np = 0, i, len = this.objects.length, el; 606 for (i=0; i <len; i++) { 607 el = this.objects[i]; 608 if (el.elementClass == JXG.OBJECT_CLASS_CURVE) { 609 np += this.objects[i].numberPoints; 610 } 611 } 612 return np; 613 }, 614 615 /** 616 * Checks whether (x,y) is near the curve. 617 * @param {int} x Coordinate in x direction, screen coordinates. 618 * @param {int} y Coordinate in y direction, screen coordinates. 619 * @param {y} Find closest point on the curve to (x,y) 620 * @return {bool} True if (x,y) is near the curve, False otherwise. 621 */ 622 hasPoint: function (x,y) { 623 var i, el; 624 for(i=0;i<this.objects.length;i++) { // run through all curves of this turtle 625 el = this.objects[i]; 626 if (el.type==JXG.OBJECT_TYPE_CURVE) { 627 if (el.hasPoint(x,y)) { 628 return true; // So what??? All other curves have to be notified now (for highlighting) 629 // This has to be done, yet. 630 } 631 } 632 } 633 return false; 634 } 635 }); 636 637 /** 638 * Creates a new turtle 639 * @param {JXG.Board} board The board the turtle is put on. 640 * @param {Array} parents 641 * @param {Object} attributs Object containing properties for the element such as stroke-color and visibility. See {@link JXG.GeometryElement#setProperty} 642 * @type JXG.Turtle 643 * @return Reference to the created turtle object. 644 */ 645 JXG.createTurtle = function(board, parents, attributes) { 646 var attr; 647 parents = parents || []; 648 649 attr = JXG.copyAttributes(attributes, board.options, 'turtle'); 650 return new JXG.Turtle(board, parents, attr); 651 }; 652 653 JXG.JSXGraph.registerElement('turtle', JXG.createTurtle); 654