// Copyright (c) 2014-16 Walter Bender // // This program is free software; you can redistribute it and/or // modify it under the terms of the The GNU Affero General Public // License as published by the Free Software Foundation; either // version 3 of the License, or (at your option) any later version. // // You should have received a copy of the GNU Affero General Public // License along with this library; if not, write to the Free Software // Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA // Turtles const DEFAULTCOLOR = 0; const DEFAULTVALUE = 50; const DEFAULTCHROMA = 100; const DEFAULTSTROKE = 5; const DEFAULTFONT = 'sans-serif'; // Turtle sprite const TURTLEBASEPATH = 'images/'; function Turtle (name, turtles, drum) { this.name = name; this.turtles = turtles; this.drum = drum; if (drum) { console.log('turtle ' + name + ' is a drum.'); } // Is the turtle running? this.running = false; // In the trash? this.trash = false; // Things used for drawing the turtle. this.container = null; this.x = 0; this.y = 0; this.bitmap = null; this.skinChanged = false; // Should we reskin the turtle on clear? this.blinkFinished = true; this.beforeBlinkSize = null; // Which start block is assocated with this turtle? this.startBlock = null; this.decorationBitmap = null; // Start block decoration. // Queue of blocks this turtle is executing. this.queue = []; // Listeners this.listeners = {}; // Things used for what the turtle draws. this.penstrokes = null; this.imageContainer = null; this.svgOutput = ''; // Are we currently drawing a path? this.svgPath = false; this.color = DEFAULTCOLOR; this.value = DEFAULTVALUE; this.chroma = DEFAULTCHROMA; this.stroke = DEFAULTSTROKE; this.canvasColor = 'rgba(255,0,49,1)'; // '#ff0031'; this.canvasAlpha = 1.0; this.orientation = 0; this.fillState = false; this.hollowState = false; this.penState = true; this.font = DEFAULTFONT; this.media = []; // Media (text, images) we need to remove on clear. var canvas = document.getElementById("overlayCanvas"); var ctx = canvas.getContext("2d"); // Simulate an arc with line segments since Tinkercad cannot // import SVG arcs reliably. this._svgArc = function(nsteps, cx, cy, radius, sa, ea) { var a = sa; if (ea == null) { var da = Math.PI / nsteps; } else { var da = (ea - sa) / nsteps; } for (var i = 0; i < nsteps; i++) { var nx = cx + radius * Math.cos(a); var ny = cy + radius * Math.sin(a); this.svgOutput += nx + ',' + ny + ' '; a += da; } }; this.doBezier = function(cp1x, cp1y, cp2x, cp2y, x2, y2) { // FIXME: Add SVG output if (this.penState && this.hollowState) { // Convert from turtle coordinates to screen coordinates. var nx = x2; var ny = y2; var ix = this.turtles.turtleX2screenX(this.x); var iy = this.turtles.turtleY2screenY(this.y); var fx = this.turtles.turtleX2screenX(x2); var fy = this.turtles.turtleY2screenY(y2); var cx1 = this.turtles.turtleX2screenX(cp1x); var cy1 = this.turtles.turtleY2screenY(cp1y); var cx2 = this.turtles.turtleX2screenX(cp2x); var cy2 = this.turtles.turtleY2screenY(cp2y); // First, we need to close the current SVG path. this.closeSVG(); // Save the current stroke width. var savedStroke = this.stroke; this.stroke = 1; ctx.lineWidth = this.stroke; ctx.lineCap = "round"; // Draw a hollow line. if (savedStroke < 3) { var step = 0.5; } else { var step = (savedStroke - 2) / 2.; } steps = Math.max(Math.floor(savedStroke, 1)); // We need both the initial and final headings. // The initial heading is the angle between (cp1x, cp1y) and (this.x, this.y). var degreesInitial = Math.atan2(cp1x - this.x, cp1y - this.y); degreesInitial = (180 * degreesInitial / Math.PI); if (degreesInitial < 0) { degreesInitial += 360; } // The final heading is the angle between (cp2x, cp2y) and (fx, fy). var degreesFinal = Math.atan2(nx - cp2x, ny - cp2y); degreesFinal = 180 * degreesFinal / Math.PI; if (degreesFinal < 0) { degreesFinal += 360; } // We also need to calculate the deltas for the "caps" at each end. var capAngleRadiansInitial = (degreesInitial - 90) * Math.PI / 180.0; var dxi = step * Math.sin(capAngleRadiansInitial); var dyi = -step * Math.cos(capAngleRadiansInitial); var capAngleRadiansFinal = (degreesFinal - 90) * Math.PI / 180.0; var dxf = step * Math.sin(capAngleRadiansFinal); var dyf = -step * Math.cos(capAngleRadiansFinal); // The four "corners" var ax = ix - dxi; var ay = iy - dyi; var axScaled = ax * this.turtles.scale; var ayScaled = ay * this.turtles.scale; var bx = fx - dxf; var by = fy - dyf; var bxScaled = bx * this.turtles.scale; var byScaled = by * this.turtles.scale; var cx = fx + dxf; var cy = fy + dyf; var cxScaled = cx * this.turtles.scale; var cyScaled = cy * this.turtles.scale; var dx = ix + dxi; var dy = iy + dyi; var dxScaled = dx * this.turtles.scale; var dyScaled = dy * this.turtles.scale; // Control points scaled for SVG output var cx1Scaled = (cx1 + dxi)* this.turtles.scale; var cy1Scaled = (cy1 + dyi) * this.turtles.scale; var cx2Scaled = (cx2 + dxf) * this.turtles.scale; var cy2Scaled = (cy2 + dyf) * this.turtles.scale; ctx.moveTo(ax, ay); this.svgPath = true; this.svgOutput += ' 0) { this._doArcPart(remainder * factor, radius); } }; this._doArcPart = function(angle, radius) { if (!this.fillState) { if (this.canvasColor[0] === "#") { this.canvasColor = hex2rgb(this.canvasColor.split("#")[1]); } var subrgb = this.canvasColor.substr(0, this.canvasColor.length-2); ctx.strokeStyle = subrgb + this.canvasAlpha + ")"; ctx.fillStyle = subrgb + this.canvasAlpha + ")"; ctx.lineWidth = this.stroke; ctx.lineCap = "round"; ctx.beginPath(); ctx.moveTo(this.container.x, this.container.y); } var adeg = Number(angle); var angleRadians = (adeg / 180) * Math.PI; var oAngleRadians = (this.orientation / 180) * Math.PI; var r = Number(radius); // old turtle point ox = this.turtles.screenX2turtleX(this.container.x); oy = this.turtles.screenY2turtleY(this.container.y); if( adeg < 0 ) { var anticlockwise = true; adeg = -adeg; // center point for arc var cx = ox - Math.cos(oAngleRadians) * r; var cy = oy + Math.sin(oAngleRadians) * r; // new position of turtle var nx = cx + Math.cos(oAngleRadians + angleRadians) * r; var ny = cy - Math.sin(oAngleRadians + angleRadians) * r; } else { var anticlockwise = false; // center point for arc var cx = ox + Math.cos(oAngleRadians) * r; var cy = oy - Math.sin(oAngleRadians) * r; // new position of turtle var nx = cx - Math.cos(oAngleRadians + angleRadians) * r; var ny = cy + Math.sin(oAngleRadians + angleRadians) * r; } this.arc(cx, cy, ox, oy, nx, ny, r, oAngleRadians, oAngleRadians + angleRadians, anticlockwise, true); if (anticlockwise) { this.doRight(-adeg); } else { this.doRight(adeg); } this.turtles.refreshCanvas(); }; this.doShowImage = function(size, myImage) { // Add an image object to the canvas // Is there a JS test for a valid image path? if (myImage === null) { return; } var image = new Image(); var turtle = this; image.onload = function() { var bitmap = new createjs.Bitmap(image); turtle.imageContainer.addChild(bitmap); turtle.media.push(bitmap); bitmap.scaleX = Number(size) / image.width; bitmap.scaleY = bitmap.scaleX; bitmap.scale = bitmap.scaleX; bitmap.x = turtle.container.x; bitmap.y = turtle.container.y; bitmap.regX = image.width / 2; bitmap.regY = image.height / 2; bitmap.rotation = turtle.orientation; turtle.turtles.refreshCanvas(); }; image.src = myImage; }; this.doShowURL = function(size, myURL) { // Add an image object from a URL to the canvas if (myURL === null) { return; } var image = new Image(); image.src = myURL; var turtle = this; image.onload = function() { var bitmap = new createjs.Bitmap(image); turtle.imageContainer.addChild(bitmap); turtle.media.push(bitmap); bitmap.scaleX = Number(size) / image.width; bitmap.scaleY = bitmap.scaleX; bitmap.scale = bitmap.scaleX; bitmap.x = turtle.container.x; bitmap.y = turtle.container.y; bitmap.regX = image.width / 2; bitmap.regY = image.height / 2; bitmap.rotation = turtle.orientation; turtle.turtles.refreshCanvas(); }; }; this.doTurtleShell = function(size, myImage) { // Add image to turtle if (myImage === null) { return; } var image = new Image(); image.src = myImage; var turtle = this; image.onload = function() { turtle.container.removeChild(turtle.bitmap); turtle.bitmap = new createjs.Bitmap(image); turtle.container.addChild(turtle.bitmap); turtle.bitmap.scaleX = Number(size) / image.width; turtle.bitmap.scaleY = turtle.bitmap.scaleX; turtle.bitmap.scale = turtle.bitmap.scaleX; turtle.bitmap.x = 0; turtle.bitmap.y = 0; turtle.bitmap.regX = image.width / 2; turtle.bitmap.regY = image.height / 2; turtle.bitmap.rotation = turtle.orientation; turtle.skinChanged = true; turtle.container.uncache(); var bounds = turtle.container.getBounds(); turtle.container.cache(bounds.x, bounds.y, bounds.width, bounds.height); // Recalculate the hit area as well. var hitArea = new createjs.Shape(); hitArea.graphics.beginFill('#FFF').drawRect(0, 0, bounds.width, bounds.height); hitArea.x = -bounds.width / 2; hitArea.y = -bounds.height / 2; turtle.container.hitArea = hitArea; if (turtle.startBlock != null) { turtle.startBlock.container.removeChild(turtle.decorationBitmap); turtle.decorationBitmap = new createjs.Bitmap(myImage); turtle.startBlock.container.addChild(turtle.decorationBitmap); turtle.decorationBitmap.name = 'decoration'; var bounds = turtle.startBlock.container.getBounds(); // FIXME: Why is the position off? Does it need a scale factor? turtle.decorationBitmap.x = bounds.width - 50 * turtle.startBlock.protoblock.scale / 2; turtle.decorationBitmap.y = 20 * turtle.startBlock.protoblock.scale / 2; turtle.decorationBitmap.scaleX = (27.5 / image.width) * turtle.startBlock.protoblock.scale / 2; turtle.decorationBitmap.scaleY = (27.5 / image.height) * turtle.startBlock.protoblock.scale / 2; turtle.decorationBitmap.scale = (27.5 / image.width) * turtle.startBlock.protoblock.scale / 2; turtle.startBlock.updateCache(); } turtle.turtles.refreshCanvas(); }; }; this.resizeDecoration = function(scale, width) { this.decorationBitmap.x = width - 30 * scale / 2; this.decorationBitmap.y = 35 * scale / 2; this.decorationBitmap.scaleX = this.decorationBitmap.scaleY = this.decorationBitmap.scale = 0.5 * scale / 2 }; this.doShowText = function(size, myText) { // Add a text or image object to the canvas var textSize = size.toString() + 'px ' + this.font; var text = new createjs.Text(myText.toString(), textSize, this.canvasColor); text.textAlign = 'left'; text.textBaseline = 'alphabetic'; this.turtles.stage.addChild(text); this.media.push(text); text.x = this.container.x; text.y = this.container.y; text.rotation = this.orientation; var xScaled = text.x * this.turtles.scale; var yScaled = text.y * this.turtles.scale; var sizeScaled = size * this.turtles.scale; this.svgOutput += '' + myText + ''; this.turtles.refreshCanvas(); }; this.doRight = function(degrees) { // Turn right and display corresponding turtle graphic. this.orientation += Number(degrees); this.orientation %= 360; this.bitmap.rotation = this.orientation; // We cannot update the cache during the 'tween'. if (this.blinkFinished) { this.updateCache(); } }; this.doSetHeading = function(degrees) { this.orientation = Number(degrees); this.orientation %= 360; this.bitmap.rotation = this.orientation; // We cannot update the cache during the 'tween'. if (this.blinkFinished) { this.updateCache(); } }; this.doSetFont = function(font) { this.font = font; this.updateCache(); }; this.doSetColor = function(color) { // Color sets hue but also selects maximum chroma. this.closeSVG(); this.color = Number(color); var results = getcolor(this.color); this.canvasValue = results[0]; this.canvasChroma = results[1]; this.canvasColor = results[2]; if (this.canvasColor[0] === "#") { this.canvasColor = hex2rgb(this.canvasColor.split("#")[1]); } var subrgb = this.canvasColor.substr(0, this.canvasColor.length-2); ctx.strokeStyle = subrgb + this.canvasAlpha + ")"; ctx.fillStyle = subrgb + this.canvasAlpha + ")"; }; this.doSetPenAlpha = function(alpha) { this.canvasAlpha = alpha; }; this.doSetHue = function(hue) { this.closeSVG(); this.color = Number(hue); this.canvasColor = getMunsellColor(this.color, this.value, this.chroma); if (this.canvasColor[0] === "#") { this.canvasColor = hex2rgb(this.canvasColor.split("#")[1]); } var subrgb = this.canvasColor.substr(0, this.canvasColor.length-2); ctx.strokeStyle = subrgb + this.canvasAlpha + ")"; ctx.fillStyle = subrgb + this.canvasAlpha + ")"; }; this.doSetValue = function(shade) { this.closeSVG(); this.value = Number(shade); this.canvasColor = getMunsellColor(this.color, this.value, this.chroma); if (this.canvasColor[0] === "#") { this.canvasColor = hex2rgb(this.canvasColor.split("#")[1]); } var subrgb = this.canvasColor.substr(0, this.canvasColor.length-2); ctx.strokeStyle = subrgb + this.canvasAlpha + ")"; ctx.fillStyle = subrgb + this.canvasAlpha + ")"; }; this.doSetChroma = function(chroma) { this.closeSVG(); this.chroma = Number(chroma); this.canvasColor = getMunsellColor(this.color, this.value, this.chroma); this.canvasColor = getMunsellColor(this.color, this.value, this.chroma); if (this.canvasColor[0] === "#") { this.canvasColor = hex2rgb(this.canvasColor.split("#")[1]); } var subrgb = this.canvasColor.substr(0, this.canvasColor.length-2); ctx.strokeStyle = subrgb + this.canvasAlpha + ")"; ctx.fillStyle = subrgb + this.canvasAlpha + ")"; }; this.doSetPensize = function(size) { this.closeSVG(); this.stroke = size; ctx.lineWidth = this.stroke; }; this.doPenUp = function() { this.closeSVG(); this.penState = false; }; this.doPenDown = function() { this.penState = true; }; this.doStartFill = function() { /// start tracking points here ctx.beginPath(); this.fillState = true; }; this.doEndFill = function() { /// redraw the points with fill enabled ctx.fill(); ctx.closePath(); this.closeSVG(); this.fillState = false; }; this.doStartHollowLine = function() { /// start tracking points here this.hollowState = true; }; this.doEndHollowLine = function() { /// redraw the points with fill enabled this.hollowState = false; }; this.closeSVG = function() { if (this.svgPath) { // For the SVG output, we need to replace rgba() with // rgb();fill-opacity:1 and rgb();stroke-opacity:1 var svgColor = this.canvasColor.replace(/rgba/g, 'rgb'); svgColor = svgColor.substr(0, this.canvasColor.length - 4) + ');'; this.svgOutput += '" style="stroke-linecap:round;fill:'; if (this.fillState) { this.svgOutput += svgColor + 'fill-opacity:' + this.canvasAlpha + ';'; } else { this.svgOutput += 'none;'; } this.svgOutput += 'stroke:' + svgColor + 'stroke-opacity:' + this.canvasAlpha + ';'; var strokeScaled = this.stroke * this.turtles.scale; this.svgOutput += 'stroke-width:' + strokeScaled + 'pt;" />'; this.svgPath = false; } }; // Internal function for creating cache. // Includes workaround for a race condition. this.createCache = function() { var myTurtle = this; myTurtle.bounds = myTurtle.container.getBounds(); if (myTurtle.bounds == null) { setTimeout(function() { myTurtle.createCache(); }, 200); } else { myTurtle.container.cache(myTurtle.bounds.x, myTurtle.bounds.y, myTurtle.bounds.width, myTurtle.bounds.height); } }; // Internal function for creating cache. // Includes workaround for a race condition. this.updateCache = function() { var myTurtle = this; if (myTurtle.bounds == null) { console.log('Block container for ' + myTurtle.name + ' not yet ready.'); setTimeout(function() { myTurtle.updateCache(); }, 300); } else { myTurtle.container.updateCache(); myTurtle.turtles.refreshCanvas(); } }; this.blink = function(duration,volume) { var turtle = this; var sizeinuse; if (this.blinkFinished == false){ sizeinuse = this.beforeBlinkSize; } else { sizeinuse = turtle.bitmap.scaleX; this.beforeBlinkSize = sizeinuse; } this.blinkFinished = false; turtle.container.uncache(); var scalefactor = 60 / 55; var volumescalefactor = 4 * (volume + 200) / 1000; //Conversion: volume of 1 = 0.804, volume of 50 = 1, volume of 100 = 1.1 turtle.bitmap.alpha = 0.5; turtle.bitmap.scaleX = sizeinuse * scalefactor * volumescalefactor; turtle.bitmap.scaleY = turtle.bitmap.scaleX; turtle.bitmap.scale = turtle.bitmap.scaleX; var isSkinChanged = turtle.skinChanged; turtle.skinChanged = true; createjs.Tween.get(turtle.bitmap).to({alpha: 1, scaleX: sizeinuse, scaleY: sizeinuse, scale: sizeinuse}, 500 / duration); setTimeout(function() { turtle.bitmap.scaleX = sizeinuse; turtle.bitmap.scaleY = turtle.bitmap.scaleX; turtle.bitmap.scale = turtle.bitmap.scaleX; turtle.bitmap.rotation = turtle.orientation; turtle.skinChanged = isSkinChanged; var bounds = turtle.container.getBounds(); turtle.container.cache(bounds.x, bounds.y, bounds.width, bounds.height); turtle.blinkFinished = true; }, 500 / duration); // 500 / duration == (1000 * (1 / duration)) / 2 }; }; function Turtles () { this.stage = null; this.refreshCanvas = null; this.scale = 1.0; this._canvas = null; this._rotating = false; this._drum = false; // The list of all of our turtles, one for each start block. this.turtleList = []; this.setCanvas = function (canvas) { this._canvas = canvas; return this; }; this.setStage = function (stage) { this.stage = stage; return this; }; this.setRefreshCanvas = function (refreshCanvas) { this.refreshCanvas = refreshCanvas; return this; }; this.setScale = function (scale) { this.scale = scale; return this; }; this.setBlocks = function (blocks) { this.blocks = blocks; return this; }; this.addDrum = function (startBlock, infoDict) { this._drum = true; this.add(startBlock, infoDict); }; this.addTurtle = function (startBlock, infoDict) { this._drum = false; this.add(startBlock, infoDict); }; this.add = function (startBlock, infoDict) { // Add a new turtle for each start block if (startBlock != null) { console.log('adding a new turtle ' + startBlock.name); if (startBlock.value !== this.turtleList.length) { startBlock.value = this.turtleList.length; console.log('turtle #' + startBlock.value); } } else { console.log('adding a new turtle startBlock is null'); } var blkInfoAvailable = false; if (typeof(infoDict) === 'object') { if (Object.keys(infoDict).length === 8) { blkInfoAvailable = true; } } var i = this.turtleList.length; var turtleName = i.toString(); var myTurtle = new Turtle(turtleName, this, this._drum); if (blkInfoAvailable) { myTurtle.x = infoDict['xcor']; myTurtle.y = infoDict['ycor']; } this.turtleList.push(myTurtle); // Each turtle needs its own canvas. myTurtle.imageContainer = new createjs.Container(); this.stage.addChild(myTurtle.imageContainer); myTurtle.penstrokes = new createjs.Bitmap(); this.stage.addChild(myTurtle.penstrokes); var turtleImage = new Image(); i %= 10; myTurtle.container = new createjs.Container(); this.stage.addChild(myTurtle.container); myTurtle.container.x = this.turtleX2screenX(myTurtle.x); myTurtle.container.y = this.turtleY2screenY(myTurtle.y); var hitArea = new createjs.Shape(); hitArea.graphics.beginFill('#FFF').drawEllipse(-27, -27, 55, 55); hitArea.x = 0; hitArea.y = 0; myTurtle.container.hitArea = hitArea; function __processTurtleBitmap(turtles, name, bitmap, startBlock) { myTurtle.bitmap = bitmap; myTurtle.bitmap.regX = 27 | 0; myTurtle.bitmap.regY = 27 | 0; myTurtle.bitmap.cursor = 'pointer'; myTurtle.container.addChild(myTurtle.bitmap); myTurtle.createCache(); myTurtle.startBlock = startBlock; if (startBlock != null) { startBlock.updateCache(); myTurtle.decorationBitmap = myTurtle.bitmap.clone(); startBlock.container.addChild(myTurtle.decorationBitmap); myTurtle.decorationBitmap.name = 'decoration'; var bounds = startBlock.container.getBounds(); // Race condition with collapse/expand bitmap generation. if (startBlock.expandBitmap == null) { var offset = 75; } else { var offset = 40; } myTurtle.decorationBitmap.x = bounds.width - offset * startBlock.protoblock.scale / 2; myTurtle.decorationBitmap.y = 35 * startBlock.protoblock.scale / 2; myTurtle.decorationBitmap.scaleX = myTurtle.decorationBitmap.scaleY = myTurtle.decorationBitmap.scale = 0.5 * startBlock.protoblock.scale / 2 startBlock.updateCache(); } turtles.refreshCanvas(); }; if (this._drum) { var artwork = DRUMSVG; } else { var artwork = TURTLESVG; } if (sugarizerCompatibility.isInsideSugarizer()) { this._makeTurtleBitmap(artwork.replace(/fill_color/g, sugarizerCompatibility.xoColor.fill).replace(/stroke_color/g, sugarizerCompatibility.xoColor.stroke), 'turtle', __processTurtleBitmap, startBlock); } else { this._makeTurtleBitmap(artwork.replace(/fill_color/g, FILLCOLORS[i]).replace(/stroke_color/g, STROKECOLORS[i]), 'turtle', __processTurtleBitmap, startBlock); } myTurtle.color = i * 10; myTurtle.canvasColor = getMunsellColor(myTurtle.color, DEFAULTVALUE, DEFAULTCHROMA); var turtles = this; myTurtle.container.on('mousedown', function (event) { if (turtles._rotating) { return; } var offset = { x: myTurtle.container.x - (event.stageX / turtles.scale), y: myTurtle.container.y - (event.stageY / turtles.scale) } myTurtle.container.on('pressmove', function (event) { if (myTurtle.running) { return; } myTurtle.container.x = (event.stageX / turtles.scale) + offset.x; myTurtle.container.y = (event.stageY / turtles.scale) + offset.y; myTurtle.x = turtles.screenX2turtleX(myTurtle.container.x); myTurtle.y = turtles.screenY2turtleY(myTurtle.container.y); turtles.refreshCanvas(); }); }); myTurtle.container.on('click', function (event) { // If turtles listen for clicks then they can be used as buttons. console.log('--> [click' + myTurtle.name + ']'); turtles.stage.dispatchEvent('click' + myTurtle.name); }); myTurtle.container.on('mouseover', function (event) { myTurtle.bitmap.scaleX = 1.2; myTurtle.bitmap.scaleY = 1.2; myTurtle.bitmap.scale = 1.2; turtles.refreshCanvas(); }); myTurtle.container.on('mouseout', function (event) { myTurtle.bitmap.scaleX = 1; myTurtle.bitmap.scaleY = 1; myTurtle.bitmap.scale = 1; turtles.refreshCanvas(); }); document.getElementById('loader').className = ''; setTimeout(function () { if (blkInfoAvailable) { myTurtle.doSetHeading(infoDict['heading']); myTurtle.doSetPensize(infoDict['pensize']); myTurtle.doSetChroma(infoDict['grey']); myTurtle.doSetValue(infoDict['shade']); myTurtle.doSetColor(infoDict['color']); } }, 1000); this.refreshCanvas(); }; this._makeTurtleBitmap = function (data, name, callback, extras) { // Async creation of bitmap from SVG data // Works with Chrome, Safari, Firefox (untested on IE) var img = new Image(); var turtles = this; img.onload = function () { complete = true; var bitmap = new createjs.Bitmap(img); callback(turtles, name, bitmap, extras); }; img.src = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(data))); }; this.screenX2turtleX = function (x) { return x - (this._canvas.width / (2.0 * this.scale)); }; this.screenY2turtleY = function (y) { return this.invertY(y); }; this.turtleX2screenX = function (x) { return (this._canvas.width / (2.0 * this.scale)) + x; }; this.turtleY2screenY = function (y) { return this.invertY(y); }; this.invertY = function (y) { return this._canvas.height / (2.0 * this.scale) - y; }; this.markAsStopped = function () { for (var turtle in this.turtleList) { this.turtleList[turtle].running = false; } }; this.running = function () { for (var turtle in this.turtleList) { if (this.turtleList[turtle].running) { return true; } } return false; }; }; // Queue entry for managing running blocks. function Queue (blk, count, parentBlk, args) { this.blk = blk; this.count = count; this.parentBlk = parentBlk; this.args = args }; function hex2rgb (hex) { var bigint = parseInt(hex, 16); var r = (bigint >> 16) & 255; var g = (bigint >> 8) & 255; var b = bigint & 255; return 'rgba(' + r + ',' + g + ',' + b + ',1)'; };