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