1 /*
  2  turtle.js - a turtle library for canvas
  3  [The "BSD licence"]
  4  Copyright (c) 2008, John Snyders
  5  All rights reserved.
  6 
  7  Redistribution and use in source and binary forms, with or without
  8  modification, are permitted provided that the following conditions
  9  are met:
 10  1. Redistributions of source code must retain the above copyright
 11     notice, this list of conditions and the following disclaimer.
 12  2. Redistributions in binary form must reproduce the above copyright
 13     notice, this list of conditions and the following disclaimer in the
 14     documentation and/or other materials provided with the distribution.
 15  3. The name of the author may not be used to endorse or promote products
 16     derived from this software without specific prior written permission.
 17 
 18  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 19  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 20  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 21  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 22  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 23  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 24  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 25  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 26  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 27  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28 */
 29 var jjs = window.jjs || {}; // my namespace
 30 jjs.Turtle = (function ()
 31 {
 32 
 33 /**
 34  * @fileoverview
 35  * Object Turtle is a LOGO style turtle for drawing on a HTML canvas 2d context
 36  *
 37  * @name Turtle
 38  * @author John Snyders
 39  * @license BSD
 40  * @version 0.1
 41  */
 42 
 43 /**
 44  * @class
 45  * Construct a Turtle object.
 46  * <p>The turtle starts off at the origin (0,0) heading 0 degrees which is east.
 47  * The initial pen color (style) is black and solid and the initial width is 2.
 48  * There are 360 degrees to a circle. The units for distance and pen width are
 49  * the same as for the 2d context.</p>
 50  *
 51  * <p>The turtle will use whatever context settings are in effect such as
 52  * lineCap, globalAlpha etc. except for penWidth and penStyle
 53  * which are maintained independently by the turtle.
 54  * lineJoin will have no effect because each linear motion of the turtle
 55  * is a seperate path.</p>
 56  *
 57  * <p>Any transformations on the canvas will confuse the clear method.</p>
 58  *
 59  * <p>All turtle methods return the turtle so that you can chain method calls.</p>
 60  * <h3>Example:<h3>
 61  * @example
 62  * <pre>
 63  * var t = new jjs.Turtle(canvas);
 64  * t.penDown().forward(10).right(90).forward(10).right(90).forward(10)
 65  *   .right(90).forward(10);
 66  * </pre>
 67  *
 68  * @param canvas a canvas element. The caller must make sure the canvas supports a 2d context.
 69  * @constructor
 70  */
 71 function Turtle(canvas) 
 72 {
 73   this.ctx = canvas.getContext('2d');
 74   this.canvasWidth = canvas.clientWidth;
 75   this.canvasHeight = canvas.clientHeight;
 76   /** current x position. OK to read use goto to set. */
 77   this.x = 0.0;
 78   /** current y position. OK to read use goto to set.  */
 79   this.y = 0.0;
 80   /** current heading. OK to read use heading to set. */ 
 81   this.h = 0.0;
 82   /** true when the pen is down, false otherwise */
 83   this.penIsDown = false;
 84   /** pen width. OK to read use width to set. */ 
 85   this.penWidth = 2;
 86   /** pen style. OK to read use style to set. */ 
 87   this.penStyle = 'black';
 88   /* implementation */
 89   this.stack = []; // to save and restore turtle state
 90 }
 91 
 92 /**
 93  * Sets up the normal turtle drawing world with 0,0 in the center
 94  * of the canvas and positive values of y going up.
 95  *
 96  * @param canvas a canvas element. The caller must make sure the canvas supports a 2d context.
 97  * @static
 98  */
 99 Turtle.init = function(canvas)
100 {
101   var ctx = canvas.getContext('2d');
102   ctx.translate(canvas.clientWidth / 2, canvas.clientHeight / 2); // center origin
103   ctx.scale(1, -1); // reverse up and down
104 };
105 
106 Turtle.prototype = {
107   deg2rad: Math.PI / 180.0,
108 
109   /**
110    * Move forward d units in the current direction. 
111    * If the pen is down a line is drawn in the current style and width.
112    * @param d the distance to move
113    * @return this
114    */
115   forward: function(d) 
116   {
117     var x1 = this.x + (d * Math.cos(this.h));
118     var y1 = this.y + (d * Math.sin(this.h));
119     if (this.penIsDown)
120     {
121       this.ctx.lineWidth = this.penWidth;
122       this.ctx.strokeStyle = this.penStyle;
123       this.ctx.beginPath();
124       this.ctx.moveTo(this.x, this.y);
125       this.ctx.lineTo(x1, y1);
126       this.ctx.stroke();
127     }
128     this.x = x1;
129     this.y = y1;
130     return this;
131   },
132 
133   /**
134    * Move backward d units in the current direction. 
135    * If the pen is down a line is drawn in the current style and width.
136    * @param d the distance to move
137    * @return this
138    */
139   back: function(d)
140   {
141     return this.forward(-d);
142   },
143 
144   /**
145    * Turn left a degrees. The current heading is updated. Right and left are determined
146    * by facing in the forward direction given by the current heading.
147    * @param a the angle to turn by
148    * @return this
149    */
150   left: function(a)
151   {
152     this.h += a * this.deg2rad;
153     return this;
154   },
155 
156   /**
157    * Turn right a degrees. The current heading is updated. Right and left are determined
158    * by facing in the forward direction given by the current heading.
159    * @param a the angle to turn by
160    * @return this
161    */
162   right: function(a)
163   {
164     return this.left(-a);
165   },
166 
167   /**
168    * Pick the pen up. With the pen up moving forward or back leaves no line.
169    * @return this
170    */
171   penUp: function()
172   {
173     this.penIsDown = false;
174     return this;
175   },
176 
177   /**
178    * Put the pen down. With the pen down moving forward or back leaves a line.
179    * @return this
180    */
181   penDown: function()
182   {
183     this.penIsDown = true;
184     return this;
185   },
186 
187   /**
188    * Set the heading to a degrees. Zero degrees is east.
189    * @param a the angle to turn by
190    * @return this
191    */
192   heading: function(a)
193   {
194     this.h = a * this.deg2rad;
195     return this;
196   },
197 
198   /**
199    * Move the turtle to given x, y coordinates without drawing a line
200    * regardless of the pen setting.
201    * @param x x position
202    * @param y y position
203    * @return this
204    */
205   moveTo: function(x, y)
206   {
207     this.x = x;
208     this.y = y;
209     return this;
210   },
211 
212   /**
213    * Move the turtle home. This is the same as goto(0,0), heading(0).
214    * @return this
215    */
216   home: function()
217   {
218     this.moveTo(0,0);
219     return this.heading(0);
220   },
221 
222   /**
223    * Clear the turtle canvas
224    * @param color optional background color. If not specified the background will be transparent.
225    * @return this
226    */
227   clear: function(color)
228   {
229     var x = -this.canvasWidth/2;
230     var y = -this.canvasHeight/2;
231     var prevColor;
232     if (color)
233     {
234       prevColor = this.ctx.fillStyle;
235       this.ctx.fillStyle = color;
236       this.ctx.fillRect(x, y, this.canvasWidth, this.canvasHeight);
237       this.ctx.fillStyle = prevColor;
238     }
239     else
240     {
241       this.ctx.clearRect(x, y, this.canvasWidth, this.canvasHeight);
242     }
243     return this;
244   },
245 
246   /**
247    * Set the pen width. pw is any legal value for context lineWidth.
248    * The lineWidth is not set until there is drawing to do.
249    * @param pw the pen width
250    * @return this
251    */
252   width: function(pw)
253   {
254     this.penWidth = pw;
255     return this;
256   },
257 
258   /**
259    * Set the pen style. s is any legal value for context strokeStyle.
260    * The strokeStyle is not set until there is drawing to do.
261    * @param s a canvas style. 
262    * @return this
263    */
264   style: function(s)
265   {
266     this.penStyle = s;
267     return this;
268   },
269 
270   /**
271    * Save the current turtle state and context state on the stack.
272    * This does a save on the context.
273    * @return this
274    */
275   push: function()
276   {
277     this.stack.push({x: this.x, y: this.y, 
278                      h: this.h, penIsDown: this.penIsDown, 
279                      penWidth: this.penWidth, penStyle: this.penStyle });
280     this.ctx.save();
281     return this;
282   },
283 
284   /**
285    * Restore the current turtle state and context state from the stack.
286    * This does a restore on the context.
287    * @return this
288    */
289   pop: function()
290   {
291     var state = this.stack.pop();
292     this.x = state.x;
293     this.y = state.y;
294     this.h = state.h;
295     this.penIsDown = state.penIsDown;
296     this.penWidth = state.penWidth;
297     this.penStyle = state.penStyle;
298     this.ctx.restore();
299     return this;
300   },
301 
302   /**
303    * Format the turtle state as a string.
304    */
305   toString: function()
306   {
307     var out = "{x: " + this.x + ", y: " + this.y + ", pen: ";
308     out += this.penIsDown ? "down" : "up";
309     out += ", penWidth: " + this.penWidth;
310     out += ", penStyle: " + this.penStyle;
311     out += ", stack depth: " + this.stack.length;
312     out += "}";
313     return out;
314   }
315 };
316   return Turtle;
317 })();
318