not really known
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1372 lines
49 KiB

  1. // Copyright (c) 2014-16 Walter Bender
  2. //
  3. // This program is free software; you can redistribute it and/or
  4. // modify it under the terms of the The GNU Affero General Public
  5. // License as published by the Free Software Foundation; either
  6. // version 3 of the License, or (at your option) any later version.
  7. //
  8. // You should have received a copy of the GNU Affero General Public
  9. // License along with this library; if not, write to the Free Software
  10. // Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA
  11. // Turtles
  12. const DEFAULTCOLOR = 0;
  13. const DEFAULTVALUE = 50;
  14. const DEFAULTCHROMA = 100;
  15. const DEFAULTSTROKE = 5;
  16. const DEFAULTFONT = 'sans-serif';
  17. // Turtle sprite
  18. const TURTLEBASEPATH = 'images/';
  19. function Turtle (name, turtles, drum) {
  20. this.name = name;
  21. this.turtles = turtles;
  22. this.drum = drum;
  23. if (drum) {
  24. console.log('turtle ' + name + ' is a drum.');
  25. }
  26. // Is the turtle running?
  27. this.running = false;
  28. // In the trash?
  29. this.trash = false;
  30. // Things used for drawing the turtle.
  31. this.container = null;
  32. this.x = 0;
  33. this.y = 0;
  34. this.bitmap = null;
  35. this.skinChanged = false; // Should we reskin the turtle on clear?
  36. this.blinkFinished = true;
  37. this.beforeBlinkSize = null;
  38. // Which start block is assocated with this turtle?
  39. this.startBlock = null;
  40. this.decorationBitmap = null; // Start block decoration.
  41. // Queue of blocks this turtle is executing.
  42. this.queue = [];
  43. // Listeners
  44. this.listeners = {};
  45. // Things used for what the turtle draws.
  46. this.penstrokes = null;
  47. this.imageContainer = null;
  48. this.svgOutput = '';
  49. // Are we currently drawing a path?
  50. this.svgPath = false;
  51. this.color = DEFAULTCOLOR;
  52. this.value = DEFAULTVALUE;
  53. this.chroma = DEFAULTCHROMA;
  54. this.stroke = DEFAULTSTROKE;
  55. this.canvasColor = 'rgba(255,0,49,1)'; // '#ff0031';
  56. this.canvasAlpha = 1.0;
  57. this.orientation = 0;
  58. this.fillState = false;
  59. this.hollowState = false;
  60. this.penState = true;
  61. this.font = DEFAULTFONT;
  62. this.media = []; // Media (text, images) we need to remove on clear.
  63. var canvas = document.getElementById("overlayCanvas");
  64. var ctx = canvas.getContext("2d");
  65. // Simulate an arc with line segments since Tinkercad cannot
  66. // import SVG arcs reliably.
  67. this._svgArc = function(nsteps, cx, cy, radius, sa, ea) {
  68. var a = sa;
  69. if (ea == null) {
  70. var da = Math.PI / nsteps;
  71. } else {
  72. var da = (ea - sa) / nsteps;
  73. }
  74. for (var i = 0; i < nsteps; i++) {
  75. var nx = cx + radius * Math.cos(a);
  76. var ny = cy + radius * Math.sin(a);
  77. this.svgOutput += nx + ',' + ny + ' ';
  78. a += da;
  79. }
  80. };
  81. this.doBezier = function(cp1x, cp1y, cp2x, cp2y, x2, y2) {
  82. // FIXME: Add SVG output
  83. if (this.penState && this.hollowState) {
  84. // Convert from turtle coordinates to screen coordinates.
  85. var nx = x2;
  86. var ny = y2;
  87. var ix = this.turtles.turtleX2screenX(this.x);
  88. var iy = this.turtles.turtleY2screenY(this.y);
  89. var fx = this.turtles.turtleX2screenX(x2);
  90. var fy = this.turtles.turtleY2screenY(y2);
  91. var cx1 = this.turtles.turtleX2screenX(cp1x);
  92. var cy1 = this.turtles.turtleY2screenY(cp1y);
  93. var cx2 = this.turtles.turtleX2screenX(cp2x);
  94. var cy2 = this.turtles.turtleY2screenY(cp2y);
  95. // First, we need to close the current SVG path.
  96. this.closeSVG();
  97. // Save the current stroke width.
  98. var savedStroke = this.stroke;
  99. this.stroke = 1;
  100. ctx.lineWidth = this.stroke;
  101. ctx.lineCap = "round";
  102. // Draw a hollow line.
  103. if (savedStroke < 3) {
  104. var step = 0.5;
  105. } else {
  106. var step = (savedStroke - 2) / 2.;
  107. }
  108. steps = Math.max(Math.floor(savedStroke, 1));
  109. // We need both the initial and final headings.
  110. // The initial heading is the angle between (cp1x, cp1y) and (this.x, this.y).
  111. var degreesInitial = Math.atan2(cp1x - this.x, cp1y - this.y);
  112. degreesInitial = (180 * degreesInitial / Math.PI);
  113. if (degreesInitial < 0) { degreesInitial += 360; }
  114. // The final heading is the angle between (cp2x, cp2y) and (fx, fy).
  115. var degreesFinal = Math.atan2(nx - cp2x, ny - cp2y);
  116. degreesFinal = 180 * degreesFinal / Math.PI;
  117. if (degreesFinal < 0) { degreesFinal += 360; }
  118. // We also need to calculate the deltas for the "caps" at each end.
  119. var capAngleRadiansInitial = (degreesInitial - 90) * Math.PI / 180.0;
  120. var dxi = step * Math.sin(capAngleRadiansInitial);
  121. var dyi = -step * Math.cos(capAngleRadiansInitial);
  122. var capAngleRadiansFinal = (degreesFinal - 90) * Math.PI / 180.0;
  123. var dxf = step * Math.sin(capAngleRadiansFinal);
  124. var dyf = -step * Math.cos(capAngleRadiansFinal);
  125. // The four "corners"
  126. var ax = ix - dxi;
  127. var ay = iy - dyi;
  128. var axScaled = ax * this.turtles.scale;
  129. var ayScaled = ay * this.turtles.scale;
  130. var bx = fx - dxf;
  131. var by = fy - dyf;
  132. var bxScaled = bx * this.turtles.scale;
  133. var byScaled = by * this.turtles.scale;
  134. var cx = fx + dxf;
  135. var cy = fy + dyf;
  136. var cxScaled = cx * this.turtles.scale;
  137. var cyScaled = cy * this.turtles.scale;
  138. var dx = ix + dxi;
  139. var dy = iy + dyi;
  140. var dxScaled = dx * this.turtles.scale;
  141. var dyScaled = dy * this.turtles.scale;
  142. // Control points scaled for SVG output
  143. var cx1Scaled = (cx1 + dxi)* this.turtles.scale;
  144. var cy1Scaled = (cy1 + dyi) * this.turtles.scale;
  145. var cx2Scaled = (cx2 + dxf) * this.turtles.scale;
  146. var cy2Scaled = (cy2 + dyf) * this.turtles.scale;
  147. ctx.moveTo(ax, ay);
  148. this.svgPath = true;
  149. this.svgOutput += '<path d="M ' + axScaled + ',' + ayScaled + ' ';
  150. // Initial arc
  151. var oAngleRadians = ((180 + degreesInitial) / 180) * Math.PI;
  152. var arccx = ix;
  153. var arccy = iy;
  154. var sa = oAngleRadians - Math.PI;
  155. var ea = oAngleRadians;
  156. ctx.arc(arccx, arccy, step, sa, ea, false);
  157. this._svgArc(steps, arccx * this.turtles.scale, arccy * this.turtles.scale, step * this.turtles.scale, sa, ea);
  158. // Initial bezier curve
  159. ctx.bezierCurveTo(cx1 + dxi, cy1 + dyi , cx2 + dxf, cy2 + dyf, cx, cy);
  160. this.svgOutput += 'C ' + cx1Scaled + ',' + cy1Scaled + ' ' + cx2Scaled + ',' + cy2Scaled + ' ' + cxScaled + ',' + cyScaled + ' ';
  161. this.svgOutput += 'M ' + cxScaled + ',' + cyScaled + ' ';
  162. // Final arc
  163. var oAngleRadians = (degreesFinal / 180) * Math.PI;
  164. var arccx = fx;
  165. var arccy = fy;
  166. var sa = oAngleRadians - Math.PI;
  167. var ea = oAngleRadians;
  168. ctx.arc(arccx, arccy, step, sa, ea, false);
  169. this._svgArc(steps, arccx * this.turtles.scale, arccy * this.turtles.scale, step * this.turtles.scale, sa, ea);
  170. // Final bezier curve
  171. ctx.bezierCurveTo(cx2 - dxf, cy2 - dyf, cx1 - dxi, cy1 - dyi, ax, ay);
  172. this.svgOutput += 'C ' + cx2Scaled + ',' + cy2Scaled + ' ' + cx1Scaled + ',' + cy1Scaled + ' ' + axScaled + ',' + ayScaled + ' ';
  173. this.closeSVG();
  174. ctx.stroke();
  175. ctx.closePath();
  176. // restore stroke.
  177. this.stroke = savedStroke;
  178. ctx.lineWidth = this.stroke;
  179. ctx.lineCap = "round";
  180. ctx.moveTo(fx,fy);
  181. this.x = x2;
  182. this.y = y2;
  183. } else if (this.penState) {
  184. if (this.canvasColor[0] === "#") {
  185. this.canvasColor = hex2rgb(this.canvasColor.split("#")[1]);
  186. }
  187. var subrgb = this.canvasColor.substr(0, this.canvasColor.length-2);
  188. ctx.strokeStyle = subrgb + this.canvasAlpha + ")";
  189. ctx.fillStyle = subrgb + this.canvasAlpha + ")";
  190. ctx.lineWidth = this.stroke;
  191. ctx.lineCap = "round";
  192. ctx.beginPath();
  193. ctx.moveTo(this.container.x, this.container.y);
  194. // Convert from turtle coordinates to screen coordinates.
  195. var fx = this.turtles.turtleX2screenX(x2);
  196. var fy = this.turtles.turtleY2screenY(y2);
  197. var cx1 = this.turtles.turtleX2screenX(cp1x);
  198. var cy1 = this.turtles.turtleY2screenY(cp1y);
  199. var cx2 = this.turtles.turtleX2screenX(cp2x);
  200. var cy2 = this.turtles.turtleY2screenY(cp2y);
  201. ctx.bezierCurveTo(cx1, cy1, cx2, cy2, fx, fy);
  202. if (!this.svgPath) {
  203. this.svgPath = true;
  204. var ix = this.turtles.turtleX2screenX(this.x);
  205. var iy = this.turtles.turtleY2screenY(this.y);
  206. var ixScaled = ix * this.turtles.scale;
  207. var iyScaled = iy * this.turtles.scale;
  208. this.svgOutput += '<path d="M ' + ixScaled + ',' + iyScaled + ' ';
  209. }
  210. var cx1Scaled = cx1 * this.turtles.scale;
  211. var cy1Scaled = cy1 * this.turtles.scale;
  212. var cx2Scaled = cx2 * this.turtles.scale;
  213. var cy2Scaled = cy2 * this.turtles.scale;
  214. var fxScaled = fx * this.turtles.scale;
  215. var fyScaled = fy * this.turtles.scale;
  216. this.svgOutput += 'C ' + cx1Scaled + ',' + cy1Scaled + ' ' + cx2Scaled + ',' + cy2Scaled + ' ' + fxScaled + ',' + fyScaled;
  217. this.x = x2;
  218. this.y = y2;
  219. ctx.stroke();
  220. if (!this.fillState) {
  221. ctx.closePath();
  222. }
  223. } else {
  224. this.x = x2;
  225. this.y = y2;
  226. var fx = this.turtles.turtleX2screenX(x2);
  227. var fy = this.turtles.turtleY2screenY(y2);
  228. }
  229. // Update turtle position on screen.
  230. this.container.x = fx;
  231. this.container.y = fy;
  232. // The new heading is the angle between (cp2x, cp2y) and (nx, ny).
  233. var degrees = Math.atan2(nx - cp2x, ny - cp2y);
  234. degrees = 180 * degrees / Math.PI;
  235. this.doSetHeading(degrees);
  236. };
  237. this.move = function(ox, oy, x, y, invert) {
  238. if (invert) {
  239. ox = this.turtles.turtleX2screenX(ox);
  240. oy = this.turtles.turtleY2screenY(oy);
  241. nx = this.turtles.turtleX2screenX(x);
  242. ny = this.turtles.turtleY2screenY(y);
  243. } else {
  244. nx = x;
  245. ny = y;
  246. }
  247. // Draw a line if the pen is down.
  248. if (this.penState && this.hollowState) {
  249. // First, we need to close the current SVG path.
  250. this.closeSVG();
  251. this.svgPath = true;
  252. // Save the current stroke width.
  253. var savedStroke = this.stroke;
  254. this.stroke = 1;
  255. ctx.lineWidth = this.stroke;
  256. ctx.lineCap = 'round';
  257. // Draw a hollow line.
  258. if (savedStroke < 3) {
  259. var step = 0.5;
  260. } else {
  261. var step = (savedStroke - 2) / 2.;
  262. }
  263. var capAngleRadians = (this.orientation - 90) * Math.PI / 180.0;
  264. var dx = step * Math.sin(capAngleRadians);
  265. var dy = -step * Math.cos(capAngleRadians);
  266. ctx.moveTo(ox + dx, oy + dy);
  267. var oxScaled = (ox + dx) * this.turtles.scale;
  268. var oyScaled = (oy + dy) * this.turtles.scale;
  269. this.svgOutput += '<path d="M ' + oxScaled + ',' + oyScaled + ' ';
  270. ctx.lineTo(nx + dx, ny + dy);
  271. var nxScaled = (nx + dx) * this.turtles.scale;
  272. var nyScaled = (ny + dy) * this.turtles.scale;
  273. this.svgOutput += nxScaled + ',' + nyScaled + ' ';
  274. var capAngleRadians = (this.orientation + 90) * Math.PI / 180.0;
  275. var dx = step * Math.sin(capAngleRadians);
  276. var dy = -step * Math.cos(capAngleRadians);
  277. var oAngleRadians = (this.orientation / 180) * Math.PI;
  278. var cx = nx;
  279. var cy = ny;
  280. var sa = oAngleRadians - Math.PI;
  281. var ea = oAngleRadians;
  282. ctx.arc(cx, cy, step, sa, ea, false);
  283. var nxScaled = (nx + dx) * this.turtles.scale;
  284. var nyScaled = (ny + dy) * this.turtles.scale;
  285. var radiusScaled = step * this.turtles.scale;
  286. // Simulate an arc with line segments since Tinkercad
  287. // cannot import SVG arcs reliably.
  288. // Replaces:
  289. // this.svgOutput += 'A ' + radiusScaled + ',' + radiusScaled + ' 0 0 1 ' + nxScaled + ',' + nyScaled + ' ';
  290. // this.svgOutput += 'M ' + nxScaled + ',' + nyScaled + ' ';
  291. steps = Math.max(Math.floor(savedStroke, 1));
  292. this._svgArc(steps, cx * this.turtles.scale, cy * this.turtles.scale, radiusScaled, sa);
  293. this.svgOutput += nxScaled + ',' + nyScaled + ' ';
  294. ctx.lineTo(ox + dx, oy + dy);
  295. var nxScaled = (ox + dx) * this.turtles.scale;
  296. var nyScaled = (oy + dy) * this.turtles.scale;
  297. this.svgOutput += nxScaled + ',' + nyScaled + ' ';
  298. var capAngleRadians = (this.orientation - 90) * Math.PI / 180.0;
  299. var dx = step * Math.sin(capAngleRadians);
  300. var dy = -step * Math.cos(capAngleRadians);
  301. var oAngleRadians = ((this.orientation + 180) / 180) * Math.PI;
  302. var cx = ox;
  303. var cy = oy;
  304. var sa = oAngleRadians - Math.PI;
  305. var ea = oAngleRadians;
  306. ctx.arc(cx, cy, step, sa, ea, false);
  307. var nxScaled = (ox + dx) * this.turtles.scale;
  308. var nyScaled = (oy + dy) * this.turtles.scale;
  309. var radiusScaled = step * this.turtles.scale;
  310. this._svgArc(steps, cx * this.turtles.scale, cy * this.turtles.scale, radiusScaled, sa);
  311. this.svgOutput += nxScaled + ',' + nyScaled + ' ';
  312. this.closeSVG();
  313. ctx.stroke();
  314. ctx.closePath();
  315. // restore stroke.
  316. this.stroke = savedStroke;
  317. ctx.lineWidth = this.stroke;
  318. ctx.lineCap = 'round';
  319. ctx.moveTo(nx, ny);
  320. } else if (this.penState) {
  321. ctx.lineTo(nx, ny);
  322. if (!this.svgPath) {
  323. this.svgPath = true;
  324. var oxScaled = ox * this.turtles.scale;
  325. var oyScaled = oy * this.turtles.scale;
  326. this.svgOutput += '<path d="M ' + oxScaled + ',' + oyScaled + ' ';
  327. }
  328. var nxScaled = nx * this.turtles.scale;
  329. var nyScaled = ny * this.turtles.scale;
  330. this.svgOutput += nxScaled + ',' + nyScaled + ' ';
  331. ctx.stroke();
  332. if (!this.fillState) {
  333. ctx.closePath();
  334. }
  335. } else {
  336. ctx.moveTo(nx, ny);
  337. }
  338. this.penstrokes.image = canvas;
  339. // Update turtle position on screen.
  340. this.container.x = nx;
  341. this.container.y = ny;
  342. if (invert) {
  343. this.x = x;
  344. this.y = y;
  345. } else {
  346. this.x = this.turtles.screenX2turtleX(x);
  347. this.y = this.turtles.screenY2turtleY(y);
  348. }
  349. };
  350. this.rename = function(name) {
  351. this.name = name;
  352. // Use the name on the label of the start block.
  353. if (this.startBlock != null) {
  354. this.startBlock.overrideName = this.name;
  355. if (this.name === _('start drum')) {
  356. this.startBlock.collapseText.text = _('drum');
  357. } else {
  358. this.startBlock.collapseText.text = this.name;
  359. }
  360. this.startBlock.regenerateArtwork(false);
  361. this.startBlock.value = this.turtles.turtleList.indexOf(this);
  362. }
  363. };
  364. this.arc = function(cx, cy, ox, oy, x, y, radius, start, end, anticlockwise, invert) {
  365. if (invert) {
  366. cx = this.turtles.turtleX2screenX(cx);
  367. cy = this.turtles.turtleY2screenY(cy);
  368. ox = this.turtles.turtleX2screenX(ox);
  369. oy = this.turtles.turtleY2screenY(oy);
  370. nx = this.turtles.turtleX2screenX(x);
  371. ny = this.turtles.turtleY2screenY(y);
  372. } else {
  373. nx = x;
  374. ny = y;
  375. }
  376. if (!anticlockwise) {
  377. sa = start - Math.PI;
  378. ea = end - Math.PI;
  379. } else {
  380. sa = start;
  381. ea = end;
  382. }
  383. // Draw an arc if the pen is down.
  384. if (this.penState && this.hollowState) {
  385. // First, we need to close the current SVG path.
  386. this.closeSVG();
  387. this.svgPath = true;
  388. // Save the current stroke width.
  389. var savedStroke = this.stroke;
  390. this.stroke = 1;
  391. ctx.lineWidth = this.stroke;
  392. ctx.lineCap = "round";
  393. // Draw a hollow line.
  394. if (savedStroke < 3) {
  395. var step = 0.5;
  396. } else {
  397. var step = (savedStroke - 2) / 2.;
  398. }
  399. var capAngleRadians = (this.orientation + 90) * Math.PI / 180.0;
  400. var dx = step * Math.sin(capAngleRadians);
  401. var dy = -step * Math.cos(capAngleRadians);
  402. if (anticlockwise) {
  403. ctx.moveTo(ox + dx, oy + dy);
  404. var oxScaled = (ox + dx) * this.turtles.scale;
  405. var oyScaled = (oy + dy) * this.turtles.scale;
  406. } else {
  407. ctx.moveTo(ox - dx, oy - dy);
  408. var oxScaled = (ox - dx) * this.turtles.scale;
  409. var oyScaled = (oy - dy) * this.turtles.scale;
  410. }
  411. this.svgOutput += '<path d="M ' + oxScaled + ',' + oyScaled + ' ';
  412. ctx.arc(cx, cy, radius + step, sa, ea, anticlockwise);
  413. nsteps = Math.max(Math.floor(radius * Math.abs(sa - ea) / 2), 2);
  414. steps = Math.max(Math.floor(savedStroke, 1));
  415. this._svgArc(nsteps, cx * this.turtles.scale, cy * this.turtles.scale, (radius + step) * this.turtles.scale, sa, ea);
  416. var capAngleRadians = (this.orientation + 90) * Math.PI / 180.0;
  417. var dx = step * Math.sin(capAngleRadians);
  418. var dy = -step * Math.cos(capAngleRadians);
  419. var cx1 = nx;
  420. var cy1 = ny;
  421. var sa1 = ea;
  422. var ea1 = ea + Math.PI;
  423. ctx.arc(cx1, cy1, step, sa1, ea1, anticlockwise);
  424. this._svgArc(steps, cx1 * this.turtles.scale, cy1 * this.turtles.scale, step * this.turtles.scale, sa1, ea1);
  425. ctx.arc(cx, cy, radius - step, ea, sa, !anticlockwise);
  426. this._svgArc(nsteps, cx * this.turtles.scale, cy * this.turtles.scale, (radius - step) * this.turtles.scale, ea, sa);
  427. var cx2 = ox;
  428. var cy2 = oy;
  429. var sa2 = sa - Math.PI;
  430. var ea2 = sa;
  431. ctx.arc(cx2, cy2, step, sa2, ea2, anticlockwise);
  432. this._svgArc(steps, cx2 * this.turtles.scale, cy2 * this.turtles.scale, step * this.turtles.scale, sa2, ea2);
  433. this.closeSVG();
  434. ctx.stroke();
  435. ctx.closePath();
  436. // restore stroke.
  437. this.stroke = savedStroke;
  438. ctx.lineWidth = this.stroke;
  439. ctx.lineCap = "round";
  440. ctx.moveTo(nx,ny);
  441. } else if (this.penState) {
  442. ctx.arc(cx, cy, radius, sa, ea, anticlockwise);
  443. if (!this.svgPath) {
  444. this.svgPath = true;
  445. var oxScaled = ox * this.turtles.scale;
  446. var oyScaled = oy * this.turtles.scale;
  447. this.svgOutput += '<path d="M ' + oxScaled + ',' + oyScaled + ' ';
  448. }
  449. if (anticlockwise) {
  450. var sweep = 0;
  451. } else {
  452. var sweep = 1;
  453. }
  454. var nxScaled = nx * this.turtles.scale;
  455. var nyScaled = ny * this.turtles.scale;
  456. var radiusScaled = radius * this.turtles.scale;
  457. this.svgOutput += 'A ' + radiusScaled + ',' + radiusScaled + ' 0 0 ' + sweep + ' ' + nxScaled + ',' + nyScaled + ' ';
  458. ctx.stroke();
  459. if (!this.fillState) {
  460. ctx.closePath();
  461. }
  462. } else {
  463. ctx.moveTo(nx, ny);
  464. }
  465. // Update turtle position on screen.
  466. this.container.x = nx;
  467. this.container.y = ny;
  468. if (invert) {
  469. this.x = x;
  470. this.y = y;
  471. } else {
  472. this.x = this.screenX2turtles.turtleX(x);
  473. this.y = this.screenY2turtles.turtleY(y);
  474. }
  475. };
  476. // Turtle functions
  477. this.doClear = function(resetPen, resetSkin) {
  478. // Reset turtle.
  479. this.x = 0;
  480. this.y = 0;
  481. this.orientation = 0.0;
  482. if (resetPen) {
  483. var i = this.turtles.turtleList.indexOf(this) % 10;
  484. this.color = i * 10;
  485. this.value = DEFAULTVALUE;
  486. this.chroma = DEFAULTCHROMA;
  487. this.stroke = DEFAULTSTROKE;
  488. this.font = DEFAULTFONT;
  489. }
  490. this.container.x = this.turtles.turtleX2screenX(this.x);
  491. this.container.y = this.turtles.turtleY2screenY(this.y);
  492. if (resetSkin) {
  493. if (this.drum) {
  494. if (this.name !== _('start drum')) {
  495. this.rename(_('start drum'));
  496. }
  497. } else {
  498. if (this.name !== _('start')) {
  499. this.rename(_('start'));
  500. }
  501. }
  502. if (this.skinChanged) {
  503. this.doTurtleShell(55, TURTLEBASEPATH + 'turtle-' + i.toString() + '.svg');
  504. this.skinChanged = false;
  505. }
  506. }
  507. this.bitmap.rotation = this.orientation;
  508. this.updateCache();
  509. // Clear all media.
  510. for (var i = 0; i < this.media.length; i++) {
  511. // Could be in the image Container or the Stage
  512. this.imageContainer.removeChild(this.media[i]);
  513. this.turtles.stage.removeChild(this.media[i]);
  514. delete this.media[i];
  515. }
  516. this.media = [];
  517. // Clear all graphics.
  518. this.penState = true;
  519. this.fillState = false;
  520. this.hollowState = false;
  521. this.canvasColor = getMunsellColor(this.color, this.value, this.chroma);
  522. if (this.canvasColor[0] === "#") {
  523. this.canvasColor = hex2rgb(this.canvasColor.split("#")[1]);
  524. }
  525. this.svgOutput = '';
  526. this.svgPath = false;
  527. this.penstrokes.image = null;
  528. ctx.beginPath();
  529. ctx.clearRect(0, 0, canvas.width, canvas.height);
  530. this.penstrokes.image = canvas;
  531. this.turtles.refreshCanvas();
  532. };
  533. this.clearPenStrokes = function() {
  534. this.penState = true;
  535. this.fillState = false;
  536. this.hollowState = false;
  537. this.canvasColor = getMunsellColor(this.color, this.value, this.chroma);
  538. if (this.canvasColor[0] === "#") {
  539. this.canvasColor = hex2rgb(this.canvasColor.split("#")[1]);
  540. }
  541. ctx.beginPath();
  542. ctx.clearRect(0, 0, canvas.width, canvas.height);
  543. var subrgb = this.canvasColor.substr(0, this.canvasColor.length-2);
  544. ctx.strokeStyle = subrgb + this.canvasAlpha + ")";
  545. ctx.fillStyle = subrgb + this.canvasAlpha + ")";
  546. ctx.lineWidth = this.stroke;
  547. ctx.lineCap = "round";
  548. ctx.beginPath();
  549. this.penstrokes.image = canvas;
  550. this.svgOutput = '';
  551. this.svgPath = false;
  552. this.turtles.refreshCanvas();
  553. };
  554. this.doForward = function(steps) {
  555. if (!this.fillState) {
  556. if (this.canvasColor[0] === "#") {
  557. this.canvasColor = hex2rgb(this.canvasColor.split("#")[1]);
  558. }
  559. var subrgb = this.canvasColor.substr(0, this.canvasColor.length - 2);
  560. ctx.strokeStyle = subrgb + this.canvasAlpha + ")";
  561. ctx.fillStyle = subrgb + this.canvasAlpha + ")";
  562. ctx.lineWidth = this.stroke;
  563. ctx.lineCap = "round";
  564. ctx.beginPath();
  565. ctx.moveTo(this.container.x, this.container.y);
  566. }
  567. // old turtle point
  568. var ox = this.turtles.screenX2turtleX(this.container.x);
  569. var oy = this.turtles.screenY2turtleY(this.container.y);
  570. // new turtle point
  571. var angleRadians = this.orientation * Math.PI / 180.0;
  572. var nx = ox + Number(steps) * Math.sin(angleRadians);
  573. var ny = oy + Number(steps) * Math.cos(angleRadians);
  574. this.move(ox, oy, nx, ny, true);
  575. this.turtles.refreshCanvas();
  576. };
  577. this.doSetXY = function(x, y) {
  578. if (!this.fillState) {
  579. if (this.canvasColor[0] === "#") {
  580. this.canvasColor = hex2rgb(this.canvasColor.split("#")[1]);
  581. }
  582. var subrgb = this.canvasColor.substr(0, this.canvasColor.length-2);
  583. ctx.strokeStyle = subrgb + this.canvasAlpha + ")";
  584. ctx.fillStyle = subrgb + this.canvasAlpha + ")";
  585. ctx.lineWidth = this.stroke;
  586. ctx.lineCap = "round";
  587. ctx.beginPath();
  588. ctx.moveTo(this.container.x, this.container.y);
  589. }
  590. // old turtle point
  591. var ox = this.turtles.screenX2turtleX(this.container.x);
  592. var oy = this.turtles.screenY2turtleY(this.container.y);
  593. // new turtle point
  594. var nx = Number(x)
  595. var ny = Number(y);
  596. this.move(ox, oy, nx, ny, true);
  597. this.turtles.refreshCanvas();
  598. };
  599. this.doArc = function(angle, radius) {
  600. // Break up arcs into chucks of 90 degrees or less (in order
  601. // to have exported SVG properly rendered).
  602. if (radius < 0) {
  603. radius = -radius;
  604. }
  605. var adeg = Number(angle);
  606. if (adeg < 0) {
  607. var factor = -1;
  608. adeg = -adeg;
  609. } else {
  610. var factor = 1;
  611. }
  612. var remainder = adeg % 90;
  613. var n = Math.floor(adeg / 90);
  614. for (var i = 0; i < n; i++) {
  615. this._doArcPart(90 * factor, radius);
  616. }
  617. if (remainder > 0) {
  618. this._doArcPart(remainder * factor, radius);
  619. }
  620. };
  621. this._doArcPart = function(angle, radius) {
  622. if (!this.fillState) {
  623. if (this.canvasColor[0] === "#") {
  624. this.canvasColor = hex2rgb(this.canvasColor.split("#")[1]);
  625. }
  626. var subrgb = this.canvasColor.substr(0, this.canvasColor.length-2);
  627. ctx.strokeStyle = subrgb + this.canvasAlpha + ")";
  628. ctx.fillStyle = subrgb + this.canvasAlpha + ")";
  629. ctx.lineWidth = this.stroke;
  630. ctx.lineCap = "round";
  631. ctx.beginPath();
  632. ctx.moveTo(this.container.x, this.container.y);
  633. }
  634. var adeg = Number(angle);
  635. var angleRadians = (adeg / 180) * Math.PI;
  636. var oAngleRadians = (this.orientation / 180) * Math.PI;
  637. var r = Number(radius);
  638. // old turtle point
  639. ox = this.turtles.screenX2turtleX(this.container.x);
  640. oy = this.turtles.screenY2turtleY(this.container.y);
  641. if( adeg < 0 ) {
  642. var anticlockwise = true;
  643. adeg = -adeg;
  644. // center point for arc
  645. var cx = ox - Math.cos(oAngleRadians) * r;
  646. var cy = oy + Math.sin(oAngleRadians) * r;
  647. // new position of turtle
  648. var nx = cx + Math.cos(oAngleRadians + angleRadians) * r;
  649. var ny = cy - Math.sin(oAngleRadians + angleRadians) * r;
  650. } else {
  651. var anticlockwise = false;
  652. // center point for arc
  653. var cx = ox + Math.cos(oAngleRadians) * r;
  654. var cy = oy - Math.sin(oAngleRadians) * r;
  655. // new position of turtle
  656. var nx = cx - Math.cos(oAngleRadians + angleRadians) * r;
  657. var ny = cy + Math.sin(oAngleRadians + angleRadians) * r;
  658. }
  659. this.arc(cx, cy, ox, oy, nx, ny, r, oAngleRadians, oAngleRadians + angleRadians, anticlockwise, true);
  660. if (anticlockwise) {
  661. this.doRight(-adeg);
  662. } else {
  663. this.doRight(adeg);
  664. }
  665. this.turtles.refreshCanvas();
  666. };
  667. this.doShowImage = function(size, myImage) {
  668. // Add an image object to the canvas
  669. // Is there a JS test for a valid image path?
  670. if (myImage === null) {
  671. return;
  672. }
  673. var image = new Image();
  674. var turtle = this;
  675. image.onload = function() {
  676. var bitmap = new createjs.Bitmap(image);
  677. turtle.imageContainer.addChild(bitmap);
  678. turtle.media.push(bitmap);
  679. bitmap.scaleX = Number(size) / image.width;
  680. bitmap.scaleY = bitmap.scaleX;
  681. bitmap.scale = bitmap.scaleX;
  682. bitmap.x = turtle.container.x;
  683. bitmap.y = turtle.container.y;
  684. bitmap.regX = image.width / 2;
  685. bitmap.regY = image.height / 2;
  686. bitmap.rotation = turtle.orientation;
  687. turtle.turtles.refreshCanvas();
  688. };
  689. image.src = myImage;
  690. };
  691. this.doShowURL = function(size, myURL) {
  692. // Add an image object from a URL to the canvas
  693. if (myURL === null) {
  694. return;
  695. }
  696. var image = new Image();
  697. image.src = myURL;
  698. var turtle = this;
  699. image.onload = function() {
  700. var bitmap = new createjs.Bitmap(image);
  701. turtle.imageContainer.addChild(bitmap);
  702. turtle.media.push(bitmap);
  703. bitmap.scaleX = Number(size) / image.width;
  704. bitmap.scaleY = bitmap.scaleX;
  705. bitmap.scale = bitmap.scaleX;
  706. bitmap.x = turtle.container.x;
  707. bitmap.y = turtle.container.y;
  708. bitmap.regX = image.width / 2;
  709. bitmap.regY = image.height / 2;
  710. bitmap.rotation = turtle.orientation;
  711. turtle.turtles.refreshCanvas();
  712. };
  713. };
  714. this.doTurtleShell = function(size, myImage) {
  715. // Add image to turtle
  716. if (myImage === null) {
  717. return;
  718. }
  719. var image = new Image();
  720. image.src = myImage;
  721. var turtle = this;
  722. image.onload = function() {
  723. turtle.container.removeChild(turtle.bitmap);
  724. turtle.bitmap = new createjs.Bitmap(image);
  725. turtle.container.addChild(turtle.bitmap);
  726. turtle.bitmap.scaleX = Number(size) / image.width;
  727. turtle.bitmap.scaleY = turtle.bitmap.scaleX;
  728. turtle.bitmap.scale = turtle.bitmap.scaleX;
  729. turtle.bitmap.x = 0;
  730. turtle.bitmap.y = 0;
  731. turtle.bitmap.regX = image.width / 2;
  732. turtle.bitmap.regY = image.height / 2;
  733. turtle.bitmap.rotation = turtle.orientation;
  734. turtle.skinChanged = true;
  735. turtle.container.uncache();
  736. var bounds = turtle.container.getBounds();
  737. turtle.container.cache(bounds.x, bounds.y, bounds.width, bounds.height);
  738. // Recalculate the hit area as well.
  739. var hitArea = new createjs.Shape();
  740. hitArea.graphics.beginFill('#FFF').drawRect(0, 0, bounds.width, bounds.height);
  741. hitArea.x = -bounds.width / 2;
  742. hitArea.y = -bounds.height / 2;
  743. turtle.container.hitArea = hitArea;
  744. if (turtle.startBlock != null) {
  745. turtle.startBlock.container.removeChild(turtle.decorationBitmap);
  746. turtle.decorationBitmap = new createjs.Bitmap(myImage);
  747. turtle.startBlock.container.addChild(turtle.decorationBitmap);
  748. turtle.decorationBitmap.name = 'decoration';
  749. var bounds = turtle.startBlock.container.getBounds();
  750. // FIXME: Why is the position off? Does it need a scale factor?
  751. turtle.decorationBitmap.x = bounds.width - 50 * turtle.startBlock.protoblock.scale / 2;
  752. turtle.decorationBitmap.y = 20 * turtle.startBlock.protoblock.scale / 2;
  753. turtle.decorationBitmap.scaleX = (27.5 / image.width) * turtle.startBlock.protoblock.scale / 2;
  754. turtle.decorationBitmap.scaleY = (27.5 / image.height) * turtle.startBlock.protoblock.scale / 2;
  755. turtle.decorationBitmap.scale = (27.5 / image.width) * turtle.startBlock.protoblock.scale / 2;
  756. turtle.startBlock.updateCache();
  757. }
  758. turtle.turtles.refreshCanvas();
  759. };
  760. };
  761. this.resizeDecoration = function(scale, width) {
  762. this.decorationBitmap.x = width - 30 * scale / 2;
  763. this.decorationBitmap.y = 35 * scale / 2;
  764. this.decorationBitmap.scaleX = this.decorationBitmap.scaleY = this.decorationBitmap.scale = 0.5 * scale / 2
  765. };
  766. this.doShowText = function(size, myText) {
  767. // Add a text or image object to the canvas
  768. var textSize = size.toString() + 'px ' + this.font;
  769. var text = new createjs.Text(myText.toString(), textSize, this.canvasColor);
  770. text.textAlign = 'left';
  771. text.textBaseline = 'alphabetic';
  772. this.turtles.stage.addChild(text);
  773. this.media.push(text);
  774. text.x = this.container.x;
  775. text.y = this.container.y;
  776. text.rotation = this.orientation;
  777. var xScaled = text.x * this.turtles.scale;
  778. var yScaled = text.y * this.turtles.scale;
  779. var sizeScaled = size * this.turtles.scale;
  780. this.svgOutput += '<text x="' + xScaled + '" y = "' + yScaled + '" fill="' + this.canvasColor + '" font-family = "' + this.font + '" font-size = "' + sizeScaled + '">' + myText + '</text>';
  781. this.turtles.refreshCanvas();
  782. };
  783. this.doRight = function(degrees) {
  784. // Turn right and display corresponding turtle graphic.
  785. this.orientation += Number(degrees);
  786. this.orientation %= 360;
  787. this.bitmap.rotation = this.orientation;
  788. // We cannot update the cache during the 'tween'.
  789. if (this.blinkFinished) {
  790. this.updateCache();
  791. }
  792. };
  793. this.doSetHeading = function(degrees) {
  794. this.orientation = Number(degrees);
  795. this.orientation %= 360;
  796. this.bitmap.rotation = this.orientation;
  797. // We cannot update the cache during the 'tween'.
  798. if (this.blinkFinished) {
  799. this.updateCache();
  800. }
  801. };
  802. this.doSetFont = function(font) {
  803. this.font = font;
  804. this.updateCache();
  805. };
  806. this.doSetColor = function(color) {
  807. // Color sets hue but also selects maximum chroma.
  808. this.closeSVG();
  809. this.color = Number(color);
  810. var results = getcolor(this.color);
  811. this.canvasValue = results[0];
  812. this.canvasChroma = results[1];
  813. this.canvasColor = results[2];
  814. if (this.canvasColor[0] === "#") {
  815. this.canvasColor = hex2rgb(this.canvasColor.split("#")[1]);
  816. }
  817. var subrgb = this.canvasColor.substr(0, this.canvasColor.length-2);
  818. ctx.strokeStyle = subrgb + this.canvasAlpha + ")";
  819. ctx.fillStyle = subrgb + this.canvasAlpha + ")";
  820. };
  821. this.doSetPenAlpha = function(alpha) {
  822. this.canvasAlpha = alpha;
  823. };
  824. this.doSetHue = function(hue) {
  825. this.closeSVG();
  826. this.color = Number(hue);
  827. this.canvasColor = getMunsellColor(this.color, this.value, this.chroma);
  828. if (this.canvasColor[0] === "#") {
  829. this.canvasColor = hex2rgb(this.canvasColor.split("#")[1]);
  830. }
  831. var subrgb = this.canvasColor.substr(0, this.canvasColor.length-2);
  832. ctx.strokeStyle = subrgb + this.canvasAlpha + ")";
  833. ctx.fillStyle = subrgb + this.canvasAlpha + ")";
  834. };
  835. this.doSetValue = function(shade) {
  836. this.closeSVG();
  837. this.value = Number(shade);
  838. this.canvasColor = getMunsellColor(this.color, this.value, this.chroma);
  839. if (this.canvasColor[0] === "#") {
  840. this.canvasColor = hex2rgb(this.canvasColor.split("#")[1]);
  841. }
  842. var subrgb = this.canvasColor.substr(0, this.canvasColor.length-2);
  843. ctx.strokeStyle = subrgb + this.canvasAlpha + ")";
  844. ctx.fillStyle = subrgb + this.canvasAlpha + ")";
  845. };
  846. this.doSetChroma = function(chroma) {
  847. this.closeSVG();
  848. this.chroma = Number(chroma);
  849. this.canvasColor = getMunsellColor(this.color, this.value, this.chroma);
  850. this.canvasColor = getMunsellColor(this.color, this.value, this.chroma);
  851. if (this.canvasColor[0] === "#") {
  852. this.canvasColor = hex2rgb(this.canvasColor.split("#")[1]);
  853. }
  854. var subrgb = this.canvasColor.substr(0, this.canvasColor.length-2);
  855. ctx.strokeStyle = subrgb + this.canvasAlpha + ")";
  856. ctx.fillStyle = subrgb + this.canvasAlpha + ")";
  857. };
  858. this.doSetPensize = function(size) {
  859. this.closeSVG();
  860. this.stroke = size;
  861. ctx.lineWidth = this.stroke;
  862. };
  863. this.doPenUp = function() {
  864. this.closeSVG();
  865. this.penState = false;
  866. };
  867. this.doPenDown = function() {
  868. this.penState = true;
  869. };
  870. this.doStartFill = function() {
  871. /// start tracking points here
  872. ctx.beginPath();
  873. this.fillState = true;
  874. };
  875. this.doEndFill = function() {
  876. /// redraw the points with fill enabled
  877. ctx.fill();
  878. ctx.closePath();
  879. this.closeSVG();
  880. this.fillState = false;
  881. };
  882. this.doStartHollowLine = function() {
  883. /// start tracking points here
  884. this.hollowState = true;
  885. };
  886. this.doEndHollowLine = function() {
  887. /// redraw the points with fill enabled
  888. this.hollowState = false;
  889. };
  890. this.closeSVG = function() {
  891. if (this.svgPath) {
  892. // For the SVG output, we need to replace rgba() with
  893. // rgb();fill-opacity:1 and rgb();stroke-opacity:1
  894. var svgColor = this.canvasColor.replace(/rgba/g, 'rgb');
  895. svgColor = svgColor.substr(0, this.canvasColor.length - 4) + ');';
  896. this.svgOutput += '" style="stroke-linecap:round;fill:';
  897. if (this.fillState) {
  898. this.svgOutput += svgColor + 'fill-opacity:' + this.canvasAlpha + ';';
  899. } else {
  900. this.svgOutput += 'none;';
  901. }
  902. this.svgOutput += 'stroke:' + svgColor + 'stroke-opacity:' + this.canvasAlpha + ';';
  903. var strokeScaled = this.stroke * this.turtles.scale;
  904. this.svgOutput += 'stroke-width:' + strokeScaled + 'pt;" />';
  905. this.svgPath = false;
  906. }
  907. };
  908. // Internal function for creating cache.
  909. // Includes workaround for a race condition.
  910. this.createCache = function() {
  911. var myTurtle = this;
  912. myTurtle.bounds = myTurtle.container.getBounds();
  913. if (myTurtle.bounds == null) {
  914. setTimeout(function() {
  915. myTurtle.createCache();
  916. }, 200);
  917. } else {
  918. myTurtle.container.cache(myTurtle.bounds.x, myTurtle.bounds.y, myTurtle.bounds.width, myTurtle.bounds.height);
  919. }
  920. };
  921. // Internal function for creating cache.
  922. // Includes workaround for a race condition.
  923. this.updateCache = function() {
  924. var myTurtle = this;
  925. if (myTurtle.bounds == null) {
  926. console.log('Block container for ' + myTurtle.name + ' not yet ready.');
  927. setTimeout(function() {
  928. myTurtle.updateCache();
  929. }, 300);
  930. } else {
  931. myTurtle.container.updateCache();
  932. myTurtle.turtles.refreshCanvas();
  933. }
  934. };
  935. this.blink = function(duration,volume) {
  936. var turtle = this;
  937. var sizeinuse;
  938. if (this.blinkFinished == false){
  939. sizeinuse = this.beforeBlinkSize;
  940. } else {
  941. sizeinuse = turtle.bitmap.scaleX;
  942. this.beforeBlinkSize = sizeinuse;
  943. }
  944. this.blinkFinished = false;
  945. turtle.container.uncache();
  946. var scalefactor = 60 / 55;
  947. var volumescalefactor = 4 * (volume + 200) / 1000;
  948. //Conversion: volume of 1 = 0.804, volume of 50 = 1, volume of 100 = 1.1
  949. turtle.bitmap.alpha = 0.5;
  950. turtle.bitmap.scaleX = sizeinuse * scalefactor * volumescalefactor;
  951. turtle.bitmap.scaleY = turtle.bitmap.scaleX;
  952. turtle.bitmap.scale = turtle.bitmap.scaleX;
  953. var isSkinChanged = turtle.skinChanged;
  954. turtle.skinChanged = true;
  955. createjs.Tween.get(turtle.bitmap).to({alpha: 1, scaleX: sizeinuse, scaleY: sizeinuse, scale: sizeinuse}, 500 / duration);
  956. setTimeout(function() {
  957. turtle.bitmap.scaleX = sizeinuse;
  958. turtle.bitmap.scaleY = turtle.bitmap.scaleX;
  959. turtle.bitmap.scale = turtle.bitmap.scaleX;
  960. turtle.bitmap.rotation = turtle.orientation;
  961. turtle.skinChanged = isSkinChanged;
  962. var bounds = turtle.container.getBounds();
  963. turtle.container.cache(bounds.x, bounds.y, bounds.width, bounds.height);
  964. turtle.blinkFinished = true;
  965. }, 500 / duration); // 500 / duration == (1000 * (1 / duration)) / 2
  966. };
  967. };
  968. function Turtles () {
  969. this.stage = null;
  970. this.refreshCanvas = null;
  971. this.scale = 1.0;
  972. this._canvas = null;
  973. this._rotating = false;
  974. this._drum = false;
  975. // The list of all of our turtles, one for each start block.
  976. this.turtleList = [];
  977. this.setCanvas = function (canvas) {
  978. this._canvas = canvas;
  979. return this;
  980. };
  981. this.setStage = function (stage) {
  982. this.stage = stage;
  983. return this;
  984. };
  985. this.setRefreshCanvas = function (refreshCanvas) {
  986. this.refreshCanvas = refreshCanvas;
  987. return this;
  988. };
  989. this.setScale = function (scale) {
  990. this.scale = scale;
  991. return this;
  992. };
  993. this.setBlocks = function (blocks) {
  994. this.blocks = blocks;
  995. return this;
  996. };
  997. this.addDrum = function (startBlock, infoDict) {
  998. this._drum = true;
  999. this.add(startBlock, infoDict);
  1000. };
  1001. this.addTurtle = function (startBlock, infoDict) {
  1002. this._drum = false;
  1003. this.add(startBlock, infoDict);
  1004. };
  1005. this.add = function (startBlock, infoDict) {
  1006. // Add a new turtle for each start block
  1007. if (startBlock != null) {
  1008. console.log('adding a new turtle ' + startBlock.name);
  1009. if (startBlock.value !== this.turtleList.length) {
  1010. startBlock.value = this.turtleList.length;
  1011. console.log('turtle #' + startBlock.value);
  1012. }
  1013. } else {
  1014. console.log('adding a new turtle startBlock is null');
  1015. }
  1016. var blkInfoAvailable = false;
  1017. if (typeof(infoDict) === 'object') {
  1018. if (Object.keys(infoDict).length === 8) {
  1019. blkInfoAvailable = true;
  1020. }
  1021. }
  1022. var i = this.turtleList.length;
  1023. var turtleName = i.toString();
  1024. var myTurtle = new Turtle(turtleName, this, this._drum);
  1025. if (blkInfoAvailable) {
  1026. myTurtle.x = infoDict['xcor'];
  1027. myTurtle.y = infoDict['ycor'];
  1028. }
  1029. this.turtleList.push(myTurtle);
  1030. // Each turtle needs its own canvas.
  1031. myTurtle.imageContainer = new createjs.Container();
  1032. this.stage.addChild(myTurtle.imageContainer);
  1033. myTurtle.penstrokes = new createjs.Bitmap();
  1034. this.stage.addChild(myTurtle.penstrokes);
  1035. var turtleImage = new Image();
  1036. i %= 10;
  1037. myTurtle.container = new createjs.Container();
  1038. this.stage.addChild(myTurtle.container);
  1039. myTurtle.container.x = this.turtleX2screenX(myTurtle.x);
  1040. myTurtle.container.y = this.turtleY2screenY(myTurtle.y);
  1041. var hitArea = new createjs.Shape();
  1042. hitArea.graphics.beginFill('#FFF').drawEllipse(-27, -27, 55, 55);
  1043. hitArea.x = 0;
  1044. hitArea.y = 0;
  1045. myTurtle.container.hitArea = hitArea;
  1046. function __processTurtleBitmap(turtles, name, bitmap, startBlock) {
  1047. myTurtle.bitmap = bitmap;
  1048. myTurtle.bitmap.regX = 27 | 0;
  1049. myTurtle.bitmap.regY = 27 | 0;
  1050. myTurtle.bitmap.cursor = 'pointer';
  1051. myTurtle.container.addChild(myTurtle.bitmap);
  1052. myTurtle.createCache();
  1053. myTurtle.startBlock = startBlock;
  1054. if (startBlock != null) {
  1055. startBlock.updateCache();
  1056. myTurtle.decorationBitmap = myTurtle.bitmap.clone();
  1057. startBlock.container.addChild(myTurtle.decorationBitmap);
  1058. myTurtle.decorationBitmap.name = 'decoration';
  1059. var bounds = startBlock.container.getBounds();
  1060. // Race condition with collapse/expand bitmap generation.
  1061. if (startBlock.expandBitmap == null) {
  1062. var offset = 75;
  1063. } else {
  1064. var offset = 40;
  1065. }
  1066. myTurtle.decorationBitmap.x = bounds.width - offset * startBlock.protoblock.scale / 2;
  1067. myTurtle.decorationBitmap.y = 35 * startBlock.protoblock.scale / 2;
  1068. myTurtle.decorationBitmap.scaleX = myTurtle.decorationBitmap.scaleY = myTurtle.decorationBitmap.scale = 0.5 * startBlock.protoblock.scale / 2
  1069. startBlock.updateCache();
  1070. }
  1071. turtles.refreshCanvas();
  1072. };
  1073. if (this._drum) {
  1074. var artwork = DRUMSVG;
  1075. } else {
  1076. var artwork = TURTLESVG;
  1077. }
  1078. if (sugarizerCompatibility.isInsideSugarizer()) {
  1079. this._makeTurtleBitmap(artwork.replace(/fill_color/g, sugarizerCompatibility.xoColor.fill).replace(/stroke_color/g, sugarizerCompatibility.xoColor.stroke), 'turtle', __processTurtleBitmap, startBlock);
  1080. } else {
  1081. this._makeTurtleBitmap(artwork.replace(/fill_color/g, FILLCOLORS[i]).replace(/stroke_color/g, STROKECOLORS[i]), 'turtle', __processTurtleBitmap, startBlock);
  1082. }
  1083. myTurtle.color = i * 10;
  1084. myTurtle.canvasColor = getMunsellColor(myTurtle.color, DEFAULTVALUE, DEFAULTCHROMA);
  1085. var turtles = this;
  1086. myTurtle.container.on('mousedown', function (event) {
  1087. if (turtles._rotating) {
  1088. return;
  1089. }
  1090. var offset = {
  1091. x: myTurtle.container.x - (event.stageX / turtles.scale),
  1092. y: myTurtle.container.y - (event.stageY / turtles.scale)
  1093. }
  1094. myTurtle.container.on('pressmove', function (event) {
  1095. if (myTurtle.running) {
  1096. return;
  1097. }
  1098. myTurtle.container.x = (event.stageX / turtles.scale) + offset.x;
  1099. myTurtle.container.y = (event.stageY / turtles.scale) + offset.y;
  1100. myTurtle.x = turtles.screenX2turtleX(myTurtle.container.x);
  1101. myTurtle.y = turtles.screenY2turtleY(myTurtle.container.y);
  1102. turtles.refreshCanvas();
  1103. });
  1104. });
  1105. myTurtle.container.on('click', function (event) {
  1106. // If turtles listen for clicks then they can be used as buttons.
  1107. console.log('--> [click' + myTurtle.name + ']');
  1108. turtles.stage.dispatchEvent('click' + myTurtle.name);
  1109. });
  1110. myTurtle.container.on('mouseover', function (event) {
  1111. myTurtle.bitmap.scaleX = 1.2;
  1112. myTurtle.bitmap.scaleY = 1.2;
  1113. myTurtle.bitmap.scale = 1.2;
  1114. turtles.refreshCanvas();
  1115. });
  1116. myTurtle.container.on('mouseout', function (event) {
  1117. myTurtle.bitmap.scaleX = 1;
  1118. myTurtle.bitmap.scaleY = 1;
  1119. myTurtle.bitmap.scale = 1;
  1120. turtles.refreshCanvas();
  1121. });
  1122. document.getElementById('loader').className = '';
  1123. setTimeout(function () {
  1124. if (blkInfoAvailable) {
  1125. myTurtle.doSetHeading(infoDict['heading']);
  1126. myTurtle.doSetPensize(infoDict['pensize']);
  1127. myTurtle.doSetChroma(infoDict['grey']);
  1128. myTurtle.doSetValue(infoDict['shade']);
  1129. myTurtle.doSetColor(infoDict['color']);
  1130. }
  1131. }, 1000);
  1132. this.refreshCanvas();
  1133. };
  1134. this._makeTurtleBitmap = function (data, name, callback, extras) {
  1135. // Async creation of bitmap from SVG data
  1136. // Works with Chrome, Safari, Firefox (untested on IE)
  1137. var img = new Image();
  1138. var turtles = this;
  1139. img.onload = function () {
  1140. complete = true;
  1141. var bitmap = new createjs.Bitmap(img);
  1142. callback(turtles, name, bitmap, extras);
  1143. };
  1144. img.src = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(data)));
  1145. };
  1146. this.screenX2turtleX = function (x) {
  1147. return x - (this._canvas.width / (2.0 * this.scale));
  1148. };
  1149. this.screenY2turtleY = function (y) {
  1150. return this.invertY(y);
  1151. };
  1152. this.turtleX2screenX = function (x) {
  1153. return (this._canvas.width / (2.0 * this.scale)) + x;
  1154. };
  1155. this.turtleY2screenY = function (y) {
  1156. return this.invertY(y);
  1157. };
  1158. this.invertY = function (y) {
  1159. return this._canvas.height / (2.0 * this.scale) - y;
  1160. };
  1161. this.markAsStopped = function () {
  1162. for (var turtle in this.turtleList) {
  1163. this.turtleList[turtle].running = false;
  1164. }
  1165. };
  1166. this.running = function () {
  1167. for (var turtle in this.turtleList) {
  1168. if (this.turtleList[turtle].running) {
  1169. return true;
  1170. }
  1171. }
  1172. return false;
  1173. };
  1174. };
  1175. // Queue entry for managing running blocks.
  1176. function Queue (blk, count, parentBlk, args) {
  1177. this.blk = blk;
  1178. this.count = count;
  1179. this.parentBlk = parentBlk;
  1180. this.args = args
  1181. };
  1182. function hex2rgb (hex) {
  1183. var bigint = parseInt(hex, 16);
  1184. var r = (bigint >> 16) & 255;
  1185. var g = (bigint >> 8) & 255;
  1186. var b = bigint & 255;
  1187. return 'rgba(' + r + ',' + g + ',' + b + ',1)';
  1188. };