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