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.id); 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 for(var i=0;i<this.objects.length;i++) { 247 var el = this.objects[i]; 248 this.board.removeObject(el.id); 249 } 250 this.init(0,0,90); 251 return this; 252 }, 253 254 /** 255 * Moves the turtle without drawing to a new position 256 * @param {float} x new x- coordinate 257 * @param {float} y new y- coordinate 258 * @type {JXG.Turtle} 259 * @return pointer to the turtle object 260 */ 261 setPos: function(x,y) { 262 if (JXG.isArray(x)) { 263 this.pos = x; 264 } else { 265 this.pos = [x,y]; 266 } 267 if (!this.turtleIsHidden) { 268 this.turtle.setPositionDirectly(JXG.COORDS_BY_USER,x,y); 269 this.turtle2.setPositionDirectly(JXG.COORDS_BY_USER,x,y+this.arrowLen); 270 var t = this.board.create('transform', 271 [-(this.dir-90)*Math.PI/180.0,this.turtle], {type:'rotate'}); 272 t.applyOnce(this.turtle2); 273 } 274 this.curve = this.board.create('curve',[[this.pos[0]],[this.pos[1]]], this._attributes); 275 this.objects.push(this.curve); 276 this.board.update(); 277 return this; 278 }, 279 280 /** 281 * Sets the pen size. Equivalent to setProperty({strokeWidth:size}) 282 * but affects only the future turtle. 283 * @param {float} size 284 * @type {JXG.Turtle} 285 * @return pointer to the turtle object 286 */ 287 setPenSize: function(size) { 288 //this.visProp.strokewidth = size; 289 this.curve = this.board.create('curve',[[this.pos[0]],[this.pos[1]]], this.copyAttr('strokeWidth', size)); 290 this.objects.push(this.curve); 291 return this; 292 }, 293 294 /** 295 * Sets the pen color. Equivalent to setProperty({strokeColor:color}) 296 * but affects only the future turtle. 297 * @param {string} color 298 * @type {JXG.Turtle} 299 * @return pointer to the turtle object 300 */ 301 setPenColor: function(colStr) { 302 //this.visProp.strokecolor = colStr; 303 this.curve = this.board.create('curve',[[this.pos[0]],[this.pos[1]]], this.copyAttr('strokeColor', colStr)); 304 this.objects.push(this.curve); 305 return this; 306 }, 307 308 /** 309 * Sets the highlight pen color. Equivalent to setProperty({highlightStrokeColor:color}) 310 * but affects only the future turtle. 311 * @param {string} color 312 * @type {JXG.Turtle} 313 * @return pointer to the turtle object 314 */ 315 setHighlightPenColor: function(colStr) { 316 //this.visProp.highlightstrokecolor = colStr; 317 this.curve = this.board.create('curve',[[this.pos[0]],[this.pos[1]]], this.copyAttr('highlightStrokeColor', colStr)); 318 this.objects.push(this.curve); 319 return this; 320 }, 321 322 /** 323 * Sets properties of the turtle, see also {@link JXG.GeometryElement#setProperty}. 324 * Sets the property for all curves of the turtle in the past and in the future. 325 * @param {Object} key:value pairs 326 * @type {JXG.Turtle} 327 * @return pointer to the turtle object 328 */ 329 setProperty: function(attributes) { 330 var i, el, len = this.objects.length, tmp; 331 for (i=0; i<len; i++) { 332 el = this.objects[i]; 333 if (el.type==JXG.OBJECT_TYPE_CURVE) { 334 el.setProperty(attributes); 335 } 336 } 337 // Set visProp of turtle 338 tmp = this.visProp['id']; 339 this.visProp = JXG.deepCopy(this.curve.visProp); 340 this.visProp['id'] = tmp; 341 this._attributes = JXG.deepCopy(this.visProp); 342 delete(this._attributes['id']); 343 return this; 344 }, 345 346 /** 347 * Set a future attribute of the turtle. 348 * @private 349 * @param {String} key 350 * @param {Object} value (number, string) 351 * @type {Object} 352 * @return pointer to an attributes object 353 */ 354 copyAttr: function(key, val) { 355 this._attributes[key.toLowerCase()] = val; 356 return this._attributes; 357 }, 358 359 /** 360 * Sets the visibility of the turtle head to true, 361 * @type {JXG.Turtle} 362 * @return pointer to the turtle object 363 */ 364 showTurtle: function() { 365 this.turtleIsHidden = false; 366 this.arrow.setProperty('visible:true'); 367 this.setPos(this.pos[0],this.pos[1]); 368 this.board.update(); 369 return this; 370 }, 371 372 /** 373 * Sets the visibility of the turtle head to false, 374 * @type {JXG.Turtle} 375 * @return pointer to the turtle object 376 */ 377 hideTurtle: function() { 378 this.turtleIsHidden = true; 379 this.arrow.setProperty('visible:false'); 380 this.setPos(this.pos[0],this.pos[1]); 381 this.board.update(); 382 return this; 383 }, 384 385 /** 386 * Moves the turtle to position [0,0]. 387 * @type {JXG.Turtle} 388 * @return pointer to the turtle object 389 */ 390 home: function() { 391 this.pos = [0,0]; 392 this.setPos(this.pos[0],this.pos[1]); 393 return this; 394 }, 395 396 /** 397 * Pushes the position of the turtle on the stack. 398 * @type {JXG.Turtle} 399 * @return pointer to the turtle object 400 */ 401 pushTurtle: function() { 402 this.stack.push([this.pos[0],this.pos[1],this.dir]); 403 return this; 404 }, 405 406 /** 407 * Gets the last position of the turtle on the stack, sets the turtle to this position and removes this 408 * position from the stack. 409 * @type {JXG.Turtle} 410 * @return pointer to the turtle object 411 */ 412 popTurtle: function() { 413 var status = this.stack.pop(); 414 this.pos[0] = status[0]; 415 this.pos[1] = status[1]; 416 this.dir = status[2]; 417 this.setPos(this.pos[0],this.pos[1]); 418 return this; 419 }, 420 421 /** 422 * Rotates the turtle into a new direction. 423 * There are two possibilities: 424 * @param {float} angle New direction to look to 425 * or 426 * @param {float} x New x coordinate to look to 427 * @param {float} y New y coordinate to look to 428 * @type {JXG.Turtle} 429 * @return pointer to the turtle object 430 */ 431 lookTo: function(target) { 432 if (JXG.isArray(target)) { 433 var ax = this.pos[0]; 434 var ay = this.pos[1]; 435 var bx = target[0]; 436 var by = target[1]; 437 var beta; 438 // Rotate by the slope of the line [this.pos, target] 439 /* 440 var sgn = (bx-ax>0)?1:-1; 441 if (Math.abs(bx-ax)>0.0000001) { 442 beta = Math.atan2(by-ay,bx-ax)+((sgn<0)?Math.PI:0); 443 } else { 444 beta = ((by-ay>0)?0.5:-0.5)*Math.PI; 445 } 446 */ 447 beta = Math.atan2(by-ay,bx-ax); 448 this.right(this.dir-(beta*180/Math.PI)); 449 } else if (JXG.isNumber(target)) { 450 this.right(this.dir-(target)); 451 } 452 return this; 453 }, 454 455 /** 456 * Moves the turtle to a given coordinate pair. 457 * The direction is not changed. 458 * @param {float} x New x coordinate to look to 459 * @param {float} y New y coordinate to look to 460 * @type {JXG.Turtle} 461 * @return pointer to the turtle object 462 */ 463 moveTo: function(target) { 464 if (JXG.isArray(target)) { 465 var dx = target[0]-this.pos[0]; 466 var dy = target[1]-this.pos[1]; 467 if (!this.turtleIsHidden) { 468 var t = this.board.create('transform', [dx,dy], {type:'translate'}); 469 t.applyOnce(this.turtle); 470 t.applyOnce(this.turtle2); 471 } 472 if (this.isPenDown) if (this.curve.dataX.length>=8192) { // IE workaround 473 this.curve = this.board.create('curve', 474 [[this.pos[0]],[this.pos[1]]], this._attributes); 475 this.objects.push(this.curve); 476 } 477 this.pos[0] = target[0]; 478 this.pos[1] = target[1]; 479 if (this.isPenDown) { 480 this.curve.dataX.push(this.pos[0]); 481 this.curve.dataY.push(this.pos[1]); 482 } 483 this.board.update(); 484 } 485 return this; 486 }, 487 488 /** 489 * Alias for {@link #forward} 490 */ 491 fd: function(len) { return this.forward(len); }, 492 /** 493 * Alias for {@link #back} 494 */ 495 bk: function(len) { return this.back(len); }, 496 /** 497 * Alias for {@link #left} 498 */ 499 lt: function(angle) { return this.left(angle); }, 500 /** 501 * Alias for {@link #right} 502 */ 503 rt: function(angle) { return this.right(angle); }, 504 /** 505 * Alias for {@link #penUp} 506 */ 507 pu: function() { return this.penUp(); }, 508 /** 509 * Alias for {@link #penDown} 510 */ 511 pd: function() { return this.penDown(); }, 512 /** 513 * Alias for {@link #hideTurtle} 514 */ 515 ht: function() { return this.hideTurtle(); }, 516 /** 517 * Alias for {@link #showTurtle} 518 */ 519 st: function() { return this.showTurtle(); }, 520 /** 521 * Alias for {@link #clearScreen} 522 */ 523 cs: function() { return this.clearScreen(); }, 524 /** 525 * Alias for {@link #pushTurtle} 526 */ 527 push: function() { return this.pushTurtle(); }, 528 /** 529 * Alias for {@link #popTurtle} 530 */ 531 pop: function() { return this.popTurtle(); }, 532 533 /** 534 * the "co"-coordinate of the turtle curve at position t is returned. 535 * @param {float} t parameter 536 * @param {string} coordinate. Either 'X' or 'Y'. 537 * @return {float} x-coordinate of the turtle position or x-coordinate of turtle at position t 538 */ 539 evalAt: function(/** float */ t, /** string */ co) /** float */ { 540 var i, j, el, tc, len = this.objects.length; 541 for (i=0, j=0; i<len; i++) { 542 el = this.objects[i]; 543 if (el.elementClass == JXG.OBJECT_CLASS_CURVE) { 544 if (j<=t && t<j+el.numberPoints) { 545 tc = (t-j); 546 return el[co](tc); 547 } 548 j += el.numberPoints; 549 } 550 } 551 return this[co](); 552 }, 553 554 /** 555 * if t is not supplied the x-coordinate of the turtle is returned. Otherwise 556 * the x-coordinate of the turtle curve at position t is returned. 557 * @param {float} t parameter 558 * @return {float} x-coordinate of the turtle position or x-coordinate of turtle at position t 559 */ 560 X: function(/** float */ t) /** float */ { 561 if (typeof t == 'undefined' ) { 562 return this.pos[0]; //this.turtle.X(); 563 } else { 564 return this.evalAt(t, 'X'); 565 } 566 }, 567 568 /** 569 * if t is not supplied the y-coordinate of the turtle is returned. Otherwise 570 * the y-coordinate of the turtle curve at position t is returned. 571 * @param {float} t parameter 572 * @return {float} x-coordinate of the turtle position or x-coordinate of turtle at position t 573 */ 574 Y: function(/** float */ t) /** float */ { 575 if (typeof t == 'undefined' ) { 576 return this.pos[1]; //this.turtle.Y(); 577 } else { 578 return this.evalAt(t, 'Y'); 579 } 580 }, 581 582 /** 583 * @return z-coordinate of the turtle position 584 * @type {float} 585 */ 586 Z: function(/** float */ t) /** float */ { 587 return 1.0; 588 }, 589 590 /** 591 * Gives the lower bound of the parameter if the the turtle is treated as parametric curve. 592 */ 593 minX: function () { 594 return 0; 595 }, 596 597 /** 598 * Gives the upper bound of the parameter if the the turtle is treated as parametric curve. 599 * May be overwritten in @see generateTerm. 600 */ 601 maxX: function () { 602 var np = 0, i, len = this.objects.length, el; 603 for (i=0; i <len; i++) { 604 el = this.objects[i]; 605 if (el.elementClass == JXG.OBJECT_CLASS_CURVE) { 606 np += this.objects[i].numberPoints; 607 } 608 } 609 return np; 610 }, 611 612 /** 613 * Checks whether (x,y) is near the curve. 614 * @param {int} x Coordinate in x direction, screen coordinates. 615 * @param {int} y Coordinate in y direction, screen coordinates. 616 * @param {y} Find closest point on the curve to (x,y) 617 * @return {bool} True if (x,y) is near the curve, False otherwise. 618 */ 619 hasPoint: function (x,y) { 620 var i, el; 621 for(i=0;i<this.objects.length;i++) { // run through all curves of this turtle 622 el = this.objects[i]; 623 if (el.type==JXG.OBJECT_TYPE_CURVE) { 624 if (el.hasPoint(x,y)) { 625 return true; // So what??? All other curves have to be notified now (for highlighting) 626 // This has to be done, yet. 627 } 628 } 629 } 630 return false; 631 } 632 }); 633 634 /** 635 * Creates a new turtle 636 * @param {JXG.Board} board The board the turtle is put on. 637 * @param {Array} parents 638 * @param {Object} attributs Object containing properties for the element such as stroke-color and visibility. See {@link JXG.GeometryElement#setProperty} 639 * @type JXG.Turtle 640 * @return Reference to the created turtle object. 641 */ 642 JXG.createTurtle = function(board, parents, attributes) { 643 var attr; 644 parents = parents || []; 645 646 attr = JXG.copyAttributes(attributes, board.options, 'turtle'); 647 return new JXG.Turtle(board, parents, attr); 648 }; 649 650 JXG.JSXGraph.registerElement('turtle', JXG.createTurtle); 651