1 /* 2 Copyright 2008-2017 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.subs = { 200 arrow: this.arrow 201 }; 202 this.inherits.push(this.arrow); 203 204 this.right(90 - dir); 205 this.board.update(); 206 }, 207 208 /** 209 * Move the turtle forward. 210 * @param {Number} len of forward move in user coordinates 211 * @returns {JXG.Turtle} pointer to the turtle object 212 */ 213 forward: function (len) { 214 if (len === 0) { 215 return this; 216 } 217 218 var t, 219 dx = len * Math.cos(this.dir * Math.PI / 180), 220 dy = len * Math.sin(this.dir * Math.PI / 180); 221 222 if (!this.turtleIsHidden) { 223 t = this.board.create('transform', [dx, dy], {type: 'translate'}); 224 225 t.applyOnce(this.turtle); 226 t.applyOnce(this.turtle2); 227 } 228 229 if (this.isPenDown) { 230 // IE workaround 231 if (this.curve.dataX.length >= 8192) { 232 this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this._attributes); 233 this.objects.push(this.curve); 234 } 235 } 236 237 this.pos[0] += dx; 238 this.pos[1] += dy; 239 240 if (this.isPenDown) { 241 this.curve.dataX.push(this.pos[0]); 242 this.curve.dataY.push(this.pos[1]); 243 } 244 245 this.board.update(); 246 return this; 247 }, 248 249 /** 250 * Move the turtle backwards. 251 * @param {Number} len of backwards move in user coordinates 252 * @returns {JXG.Turtle} pointer to the turtle object 253 */ 254 back: function (len) { 255 return this.forward(-len); 256 }, 257 258 /** 259 * Rotate the turtle direction to the right 260 * @param {Number} angle of the rotation in degrees 261 * @returns {JXG.Turtle} pointer to the turtle object 262 */ 263 right: function (angle) { 264 this.dir -= angle; 265 this.dir %= 360; 266 267 if (!this.turtleIsHidden) { 268 var t = this.board.create('transform', [-angle * Math.PI / 180, this.turtle], {type: 'rotate'}); 269 t.applyOnce(this.turtle2); 270 } 271 272 this.board.update(); 273 return this; 274 }, 275 276 /** 277 * Rotate the turtle direction to the right. 278 * @param {Number} angle of the rotation in degrees 279 * @returns {JXG.Turtle} pointer to the turtle object 280 */ 281 left: function (angle) { 282 return this.right(-angle); 283 }, 284 285 /** 286 * Pen up, stops visible drawing 287 * @returns {JXG.Turtle} pointer to the turtle object 288 */ 289 penUp: function () { 290 this.isPenDown = false; 291 return this; 292 }, 293 294 /** 295 * Pen down, continues visible drawing 296 * @returns {JXG.Turtle} pointer to the turtle object 297 */ 298 penDown: function () { 299 this.isPenDown = true; 300 this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this._attributes); 301 this.objects.push(this.curve); 302 303 return this; 304 }, 305 306 /** 307 * Removes the turtle curve from the board. The turtle stays in its position. 308 * @returns {JXG.Turtle} pointer to the turtle object 309 */ 310 clean: function () { 311 var i, el; 312 313 for (i = 0; i < this.objects.length; i++) { 314 el = this.objects[i]; 315 if (el.type === Const.OBJECT_TYPE_CURVE) { 316 this.board.removeObject(el); 317 this.objects.splice(i, 1); 318 } 319 } 320 321 this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this._attributes); 322 this.objects.push(this.curve); 323 this.board.update(); 324 325 return this; 326 }, 327 328 /** 329 * Removes the turtle completely and resets it to its initial position and direction. 330 * @returns {JXG.Turtle} pointer to the turtle object 331 */ 332 clearScreen: function () { 333 var i, el, 334 len = this.objects.length; 335 336 for (i = 0; i < len; i++) { 337 el = this.objects[i]; 338 this.board.removeObject(el); 339 } 340 341 this.init(0, 0, 90); 342 return this; 343 }, 344 345 /** 346 * Moves the turtle without drawing to a new position 347 * @param {Number} x new x- coordinate 348 * @param {Number} y new y- coordinate 349 * @returns {JXG.Turtle} pointer to the turtle object 350 */ 351 setPos: function (x, y) { 352 var t; 353 354 if (Type.isArray(x)) { 355 this.pos = x; 356 } else { 357 this.pos = [x, y]; 358 } 359 360 if (!this.turtleIsHidden) { 361 this.turtle.setPositionDirectly(Const.COORDS_BY_USER, [x, y]); 362 this.turtle2.setPositionDirectly(Const.COORDS_BY_USER, [x, y + this.arrowLen]); 363 t = this.board.create('transform', [-(this.dir - 90) * Math.PI / 180, this.turtle], {type: 'rotate'}); 364 t.applyOnce(this.turtle2); 365 } 366 367 this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this._attributes); 368 this.objects.push(this.curve); 369 this.board.update(); 370 371 return this; 372 }, 373 374 /** 375 * Sets the pen size. Equivalent to setAttribute({strokeWidth:size}) 376 * but affects only the future turtle. 377 * @param {Number} size 378 * @returns {JXG.Turtle} pointer to the turtle object 379 */ 380 setPenSize: function (size) { 381 //this.visProp.strokewidth = size; 382 this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this.copyAttr('strokeWidth', size)); 383 this.objects.push(this.curve); 384 return this; 385 }, 386 387 /** 388 * Sets the pen color. Equivalent to setAttribute({strokeColor:color}) 389 * but affects only the future turtle. 390 * @param {String} color 391 * @returns {JXG.Turtle} pointer to the turtle object 392 */ 393 setPenColor: function (color) { 394 this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this.copyAttr('strokeColor', color)); 395 this.objects.push(this.curve); 396 397 return this; 398 }, 399 400 /** 401 * Sets the highlight pen color. Equivalent to setAttribute({highlightStrokeColor:color}) 402 * but affects only the future turtle. 403 * @param {String} color 404 * @returns {JXG.Turtle} pointer to the turtle object 405 */ 406 setHighlightPenColor: function (color) { 407 //this.visProp.highlightstrokecolor = colStr; 408 this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this.copyAttr('highlightStrokeColor', color)); 409 this.objects.push(this.curve); 410 return this; 411 }, 412 413 /** 414 * Sets properties of the turtle, see also {@link JXG.GeometryElement#setAttribute}. 415 * Sets the property for all curves of the turtle in the past and in the future. 416 * @param {Object} attributes key:value pairs 417 * @returns {JXG.Turtle} pointer to the turtle object 418 */ 419 setAttribute: function (attributes) { 420 var i, el, tmp, 421 len = this.objects.length; 422 423 for (i = 0; i < len; i++) { 424 el = this.objects[i]; 425 if (el.type === Const.OBJECT_TYPE_CURVE) { 426 el.setAttribute(attributes); 427 } 428 } 429 430 // Set visProp of turtle 431 tmp = this.visProp.id; 432 this.visProp = Type.deepCopy(this.curve.visProp); 433 this.visProp.id = tmp; 434 this._attributes = Type.deepCopy(this.visProp); 435 delete this._attributes.id; 436 437 return this; 438 }, 439 440 /** 441 * Set a future attribute of the turtle. 442 * @private 443 * @param {String} key 444 * @param {Number|String} val 445 * @returns {Object} pointer to the attributes object 446 */ 447 copyAttr: function (key, val) { 448 this._attributes[key.toLowerCase()] = val; 449 return this._attributes; 450 }, 451 452 /** 453 * Sets the visibility of the turtle head to true, 454 * @returns {JXG.Turtle} pointer to the turtle object 455 */ 456 showTurtle: function () { 457 this.turtleIsHidden = false; 458 this.arrow.setAttribute({visible: true}); 459 this.visProp.arrow.visible = false; 460 this.setPos(this.pos[0], this.pos[1]); 461 this.board.update(); 462 463 return this; 464 }, 465 466 /** 467 * Sets the visibility of the turtle head to false, 468 * @returns {JXG.Turtle} pointer to the turtle object 469 */ 470 hideTurtle: function () { 471 this.turtleIsHidden = true; 472 this.arrow.setAttribute({visible: false}); 473 this.visProp.arrow.visible = false; 474 this.board.update(); 475 476 return this; 477 }, 478 479 /** 480 * Moves the turtle to position [0,0]. 481 * @returns {JXG.Turtle} pointer to the turtle object 482 */ 483 home: function () { 484 this.pos = [0, 0]; 485 this.setPos(this.pos[0], this.pos[1]); 486 487 return this; 488 }, 489 490 /** 491 * Pushes the position of the turtle on the stack. 492 * @returns {JXG.Turtle} pointer to the turtle object 493 */ 494 pushTurtle: function () { 495 this.stack.push([this.pos[0], this.pos[1], this.dir]); 496 497 return this; 498 }, 499 500 /** 501 * Gets the last position of the turtle on the stack, sets the turtle to this position and removes this 502 * position from the stack. 503 * @returns {JXG.Turtle} pointer to the turtle object 504 */ 505 popTurtle: function () { 506 var status = this.stack.pop(); 507 this.pos[0] = status[0]; 508 this.pos[1] = status[1]; 509 this.dir = status[2]; 510 this.setPos(this.pos[0], this.pos[1]); 511 512 return this; 513 }, 514 515 /** 516 * Rotates the turtle into a new direction. 517 * There are two possibilities: 518 * @param {Number|Array} target If a number is given, it is interpreted as the new direction to look to; If an array 519 * consisting of two Numbers is given targeted is used as a pair coordinates. 520 * @returns {JXG.Turtle} pointer to the turtle object 521 */ 522 lookTo: function (target) { 523 var ax, ay, bx, by, beta; 524 525 if (Type.isArray(target)) { 526 ax = this.pos[0]; 527 ay = this.pos[1]; 528 bx = target[0]; 529 by = target[1]; 530 531 // Rotate by the slope of the line [this.pos, target] 532 beta = Math.atan2(by - ay, bx - ax); 533 this.right(this.dir - (beta * 180 / Math.PI)); 534 } else if (Type.isNumber(target)) { 535 this.right(this.dir - target); 536 } 537 return this; 538 }, 539 540 /** 541 * Moves the turtle to a given coordinate pair. 542 * The direction is not changed. 543 * @param {Array} target Coordinates of the point where the turtle looks to. 544 * @returns {JXG.Turtle} pointer to the turtle object 545 */ 546 moveTo: function (target) { 547 var dx, dy, t; 548 549 if (Type.isArray(target)) { 550 dx = target[0] - this.pos[0]; 551 dy = target[1] - this.pos[1]; 552 553 if (!this.turtleIsHidden) { 554 t = this.board.create('transform', [dx, dy], {type: 'translate'}); 555 t.applyOnce(this.turtle); 556 t.applyOnce(this.turtle2); 557 } 558 559 if (this.isPenDown) { 560 // IE workaround 561 if (this.curve.dataX.length >= 8192) { 562 this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this._attributes); 563 this.objects.push(this.curve); 564 } 565 } 566 567 this.pos[0] = target[0]; 568 this.pos[1] = target[1]; 569 570 if (this.isPenDown) { 571 this.curve.dataX.push(this.pos[0]); 572 this.curve.dataY.push(this.pos[1]); 573 } 574 this.board.update(); 575 } 576 577 return this; 578 }, 579 580 /** 581 * Alias for {@link JXG.Turtle#forward} 582 */ 583 fd: function (len) { return this.forward(len); }, 584 /** 585 * Alias for {@link JXG.Turtle#back} 586 */ 587 bk: function (len) { return this.back(len); }, 588 /** 589 * Alias for {@link JXG.Turtle#left} 590 */ 591 lt: function (angle) { return this.left(angle); }, 592 /** 593 * Alias for {@link JXG.Turtle#right} 594 */ 595 rt: function (angle) { return this.right(angle); }, 596 /** 597 * Alias for {@link JXG.Turtle#penUp} 598 */ 599 pu: function () { return this.penUp(); }, 600 /** 601 * Alias for {@link JXG.Turtle#penDown} 602 */ 603 pd: function () { return this.penDown(); }, 604 /** 605 * Alias for {@link JXG.Turtle#hideTurtle} 606 */ 607 ht: function () { return this.hideTurtle(); }, 608 /** 609 * Alias for {@link JXG.Turtle#showTurtle} 610 */ 611 st: function () { return this.showTurtle(); }, 612 /** 613 * Alias for {@link JXG.Turtle#clearScreen} 614 */ 615 cs: function () { return this.clearScreen(); }, 616 /** 617 * Alias for {@link JXG.Turtle#pushTurtle} 618 */ 619 push: function () { return this.pushTurtle(); }, 620 /** 621 * Alias for {@link JXG.Turtle#popTurtle} 622 */ 623 pop: function () { return this.popTurtle(); }, 624 625 /** 626 * The "co"-coordinate of the turtle curve at position t is returned. 627 * 628 * @param {Number} t parameter 629 * @param {String} co. Either 'X' or 'Y'. 630 * @returns {Number} x-coordinate of the turtle position or x-coordinate of turtle at position t 631 */ 632 evalAt: function (t, co) { 633 var i, j, el, tc, 634 len = this.objects.length; 635 636 for (i = 0, j = 0; i < len; i++) { 637 el = this.objects[i]; 638 639 if (el.elementClass === Const.OBJECT_CLASS_CURVE) { 640 if (j <= t && t < j + el.numberPoints) { 641 tc = (t - j); 642 return el[co](tc); 643 } 644 j += el.numberPoints; 645 } 646 } 647 648 return this[co](); 649 }, 650 651 /** 652 * if t is not supplied the x-coordinate of the turtle is returned. Otherwise 653 * the x-coordinate of the turtle curve at position t is returned. 654 * @param {Number} t parameter 655 * @returns {Number} x-coordinate of the turtle position or x-coordinate of turtle at position t 656 */ 657 X: function (t) { 658 if (!Type.exists(t)) { 659 return this.pos[0]; 660 } 661 662 return this.evalAt(t, 'X'); 663 }, 664 665 /** 666 * if t is not supplied the y-coordinate of the turtle is returned. Otherwise 667 * the y-coordinate of the turtle curve at position t is returned. 668 * @param {Number} t parameter 669 * @returns {Number} x-coordinate of the turtle position or x-coordinate of turtle at position t 670 */ 671 Y: function (t) { 672 if (!Type.exists(t)) { 673 return this.pos[1]; 674 } 675 return this.evalAt(t, 'Y'); 676 }, 677 678 /** 679 * @returns {Number} z-coordinate of the turtle position 680 */ 681 Z: function (t) { 682 return 1.0; 683 }, 684 685 /** 686 * Gives the lower bound of the parameter if the the turtle is treated as parametric curve. 687 */ 688 minX: function () { 689 return 0; 690 }, 691 692 /** 693 * Gives the upper bound of the parameter if the the turtle is treated as parametric curve. 694 * May be overwritten in @see generateTerm. 695 */ 696 maxX: function () { 697 var i, el, 698 len = this.objects.length, 699 np = 0; 700 701 for (i = 0; i < len; i++) { 702 el = this.objects[i]; 703 if (el.elementClass === Const.OBJECT_CLASS_CURVE) { 704 np += this.objects[i].numberPoints; 705 } 706 } 707 return np; 708 }, 709 710 /** 711 * Checks whether (x,y) is near the curve. 712 * @param {Number} x Coordinate in x direction, screen coordinates. 713 * @param {Number} y Coordinate in y direction, screen coordinates. 714 * @returns {Boolean} True if (x,y) is near the curve, False otherwise. 715 */ 716 hasPoint: function (x, y) { 717 var i, el; 718 719 // run through all curves of this turtle 720 for (i = 0; i < this.objects.length; i++) { 721 el = this.objects[i]; 722 723 if (el.type === Const.OBJECT_TYPE_CURVE) { 724 if (el.hasPoint(x, y)) { 725 // So what??? All other curves have to be notified now (for highlighting) 726 return true; 727 // This has to be done, yet. 728 } 729 } 730 } 731 return false; 732 } 733 }); 734 735 /** 736 * @class This element is used to provide a constructor for a turtle. 737 * @pseudo 738 * @description Creates a new turtle 739 * @name Turtle 740 * @augments JXG.Turtle 741 * @constructor 742 * @type JXG.Turtle 743 * 744 * @param {JXG.Board} board The board the turtle is put on. 745 * @param {Array} parents 746 * @param {Object} attributes Object containing properties for the element such as stroke-color and visibility. See {@link JXG.GeometryElement#setAttribute} 747 * @returns {JXG.Turtle} Reference to the created turtle object. 748 */ 749 JXG.createTurtle = function (board, parents, attributes) { 750 var attr; 751 parents = parents || []; 752 753 attr = Type.copyAttributes(attributes, board.options, 'turtle'); 754 return new JXG.Turtle(board, parents, attr); 755 }; 756 757 JXG.registerElement('turtle', JXG.createTurtle); 758 759 return { 760 Turtle: JXG.Turtle, 761 createTurtle: JXG.createTurtle 762 }; 763 }); 764