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