      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