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.

1063 lines
36 KiB

  1. // Generated by CoffeeScript 1.6.3
  2. (function() {
  3. "use strict";
  4. var ArcSegment, Board, Chain, FPS, Gear, GearSketch, LineSegment, MIN_GEAR_TEETH, MIN_MOMENTUM, Point, Util,
  5. __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
  6. __hasProp = {}.hasOwnProperty,
  7. __slice = [].slice;
  8. Point = window.gearsketch.Point;
  9. ArcSegment = window.gearsketch.ArcSegment;
  10. LineSegment = window.gearsketch.LineSegment;
  11. Util = window.gearsketch.Util;
  12. Gear = window.gearsketch.model.Gear;
  13. Chain = window.gearsketch.model.Chain;
  14. Board = window.gearsketch.model.Board;
  15. FPS = 60;
  16. MIN_GEAR_TEETH = 8;
  17. MIN_MOMENTUM = 0.2;
  18. GearSketch = (function() {
  19. var AXIS_RADIUS, BUTTON_INFO, MODULE, MovementAction, MovementType;
  20. MODULE = Util.MODULE;
  21. AXIS_RADIUS = Util.AXIS_RADIUS;
  22. BUTTON_INFO = [["gearButton", "GearIcon.png"], ["chainButton", "ChainIcon.png"], ["momentumButton", "MomentumIcon.png"], ["playButton", "PlayIcon.png"], ["clearButton", "ClearIcon.png"], ["cloudButton", "CloudIcon.png"], ["helpButton", "HelpIcon.png"]];
  23. MovementAction = {
  24. PEN_DOWN: "penDown",
  25. PEN_UP: "penUp",
  26. PEN_TAP: "penTap"
  27. };
  28. MovementType = {
  29. STRAIGHT: "straight",
  30. CIRCLE: "circle",
  31. LEFT_HALF_CIRCLE: "leftHalfCircle",
  32. RIGHT_HALF_CIRCLE: "rightHalfCircle"
  33. };
  34. GearSketch.prototype.buttons = {};
  35. GearSketch.prototype.loadedButtons = 0;
  36. GearSketch.prototype.areButtonsLoaded = false;
  37. GearSketch.prototype.selectedButton = BUTTON_INFO[0][0];
  38. GearSketch.prototype.gearImages = {};
  39. GearSketch.prototype.isPenDown = false;
  40. GearSketch.prototype.stroke = [];
  41. GearSketch.prototype.offset = new Point();
  42. GearSketch.prototype.message = "";
  43. GearSketch.prototype.messageColor = "black";
  44. GearSketch.prototype.pointerLocation = new Point();
  45. GearSketch.prototype.currentDemoMovement = 0;
  46. GearSketch.prototype.movementCompletion = 0;
  47. GearSketch.prototype.restTimer = 0;
  48. function GearSketch(showButtons) {
  49. if (showButtons == null) {
  50. showButtons = true;
  51. }
  52. this.update = __bind(this.update, this);
  53. this.updateAndDrawNoRAF = __bind(this.updateAndDrawNoRAF, this);
  54. this.updateAndDraw = __bind(this.updateAndDraw, this);
  55. this.loadButtons();
  56. this.showButtons = showButtons;
  57. this.loadDemoPointer();
  58. this.loadBoard();
  59. this.canvas = document.getElementById("gearsketch_canvas");
  60. this.canvasOffsetX = this.canvas.getBoundingClientRect().left;
  61. this.canvasOffsetY = this.canvas.getBoundingClientRect().top;
  62. this.isDemoPlaying = false;
  63. this.updateCanvasSize();
  64. this.addCanvasListeners();
  65. this.lastUpdateTime = new Date().getTime();
  66. this.updateAndDrawNoRAF();
  67. }
  68. GearSketch.prototype.buttonLoaded = function() {
  69. this.loadedButtons++;
  70. if (this.loadedButtons === BUTTON_INFO.length) {
  71. return this.areButtonsLoaded = true;
  72. }
  73. };
  74. GearSketch.prototype.loadButtons = function() {
  75. var button, file, name, x, y, _i, _len, _ref, _results,
  76. _this = this;
  77. x = y = 20;
  78. _results = [];
  79. for (_i = 0, _len = BUTTON_INFO.length; _i < _len; _i++) {
  80. _ref = BUTTON_INFO[_i], name = _ref[0], file = _ref[1];
  81. button = new Image();
  82. button.name = name;
  83. button.onload = function() {
  84. return _this.buttonLoaded();
  85. };
  86. button.src = "img/" + file;
  87. button.location = new Point(x, y);
  88. button.padding = 3;
  89. this.buttons[name] = button;
  90. _results.push(x += 80);
  91. }
  92. return _results;
  93. };
  94. GearSketch.prototype.loadDemoPointer = function() {
  95. var image,
  96. _this = this;
  97. image = new Image();
  98. image.onload = function() {
  99. return _this.pointerImage = image;
  100. };
  101. return image.src = "img/hand.png";
  102. };
  103. GearSketch.prototype.loadBoard = function() {
  104. var boardJSON, error, gear, hash, id, _ref, _results;
  105. this.board = (function() {
  106. if (parent.location.hash.length > 1) {
  107. try {
  108. hash = parent.location.hash.substr(1);
  109. boardJSON = Util.sendGetRequest("boards/" + hash + ".txt");
  110. return Board.fromObject(JSON.parse(boardJSON));
  111. } catch (_error) {
  112. error = _error;
  113. this.displayMessage("Error: could not load board", "red", 2000);
  114. return new Board();
  115. }
  116. } else {
  117. return new Board();
  118. }
  119. }).call(this);
  120. _ref = this.board.getGears();
  121. _results = [];
  122. for (id in _ref) {
  123. gear = _ref[id];
  124. _results.push(this.addGearImage(gear));
  125. }
  126. return _results;
  127. };
  128. GearSketch.prototype.displayMessage = function(message, color, time) {
  129. var _this = this;
  130. if (color == null) {
  131. color = "black";
  132. }
  133. if (time == null) {
  134. time = 0;
  135. }
  136. this.message = message;
  137. this.messageColor = color;
  138. if (time > 0) {
  139. return setTimeout((function() {
  140. return _this.clearMessage();
  141. }), time);
  142. }
  143. };
  144. GearSketch.prototype.clearMessage = function() {
  145. return this.message = "";
  146. };
  147. GearSketch.prototype.selectButton = function(buttonName) {
  148. return this.selectedButton = buttonName;
  149. };
  150. GearSketch.prototype.shouldShowButtons = function() {
  151. return this.showButtons || this.isDemoPlaying;
  152. };
  153. GearSketch.prototype.addCanvasListeners = function() {
  154. var canvasEventHandler,
  155. _this = this;
  156. canvasEventHandler = Hammer(this.canvas, {
  157. drag_min_distance: 1
  158. });
  159. canvasEventHandler.on("touch", (function(e) {
  160. return _this.forwardPenDownEvent.call(_this, e);
  161. }));
  162. canvasEventHandler.on("drag", (function(e) {
  163. return _this.forwardPenMoveEvent.call(_this, e);
  164. }));
  165. return canvasEventHandler.on("release", (function(e) {
  166. return _this.forwardPenUpEvent.call(_this, e);
  167. }));
  168. };
  169. GearSketch.prototype.forwardPenDownEvent = function(event) {
  170. var x, y;
  171. event.gesture.preventDefault();
  172. if (this.isDemoPlaying) {
  173. return this.stopDemo();
  174. } else {
  175. x = event.gesture.center.pageX - this.canvasOffsetX;
  176. y = event.gesture.center.pageY - this.canvasOffsetY;
  177. return this.handlePenDown(x, y);
  178. }
  179. };
  180. GearSketch.prototype.forwardPenMoveEvent = function(event) {
  181. var x, y;
  182. event.gesture.preventDefault();
  183. if (!this.isDemoPlaying) {
  184. x = event.gesture.center.pageX - this.canvasOffsetX;
  185. y = event.gesture.center.pageY - this.canvasOffsetY;
  186. return this.handlePenMove(x, y);
  187. }
  188. };
  189. GearSketch.prototype.forwardPenUpEvent = function(event) {
  190. if (!this.isDemoPlaying) {
  191. return this.handlePenUp();
  192. }
  193. };
  194. GearSketch.prototype.handlePenDown = function(x, y) {
  195. var button, point;
  196. point = new Point(x, y);
  197. if (this.isPenDown) {
  198. return this.handlePenUp();
  199. } else {
  200. button = this.getButtonAt(x, y);
  201. if (button) {
  202. if (button.name === "clearButton") {
  203. parent.location.hash = "";
  204. return this.board.clear();
  205. } else if (button.name === "cloudButton") {
  206. return this.uploadBoard();
  207. } else if (button.name === "helpButton") {
  208. return this.playDemo();
  209. } else {
  210. return this.selectButton(button.name);
  211. }
  212. } else if (this.selectedButton === "gearButton") {
  213. this.selectedGear = this.board.getTopLevelGearAt(point);
  214. if (this.selectedGear != null) {
  215. this.offset = point.minus(this.selectedGear.location);
  216. } else if (this.board.getGearAt(point) == null) {
  217. this.stroke.push(point);
  218. }
  219. return this.isPenDown = true;
  220. } else if (this.selectedButton === "chainButton") {
  221. this.stroke.push(point);
  222. return this.isPenDown = true;
  223. } else if (this.selectedButton === "momentumButton") {
  224. this.selectedGear = this.board.getGearAt(point);
  225. if (this.selectedGear) {
  226. this.selectedGear.momentum = 0;
  227. this.selectedGearMomentum = this.calculateMomentumFromCoords(this.selectedGear, x, y);
  228. }
  229. return this.isPenDown = true;
  230. }
  231. }
  232. };
  233. GearSketch.prototype.handlePenMove = function(x, y) {
  234. var canPlaceGear, goalLocation, point;
  235. point = new Point(x, y);
  236. if (this.isPenDown) {
  237. if (this.selectedButton === "gearButton") {
  238. if (this.selectedGear) {
  239. goalLocation = point.minus(this.offset);
  240. canPlaceGear = this.board.placeGear(this.selectedGear, goalLocation);
  241. if (canPlaceGear) {
  242. return this.goalLocationGear = null;
  243. } else {
  244. return this.goalLocationGear = new Gear(goalLocation, this.selectedGear.rotation, this.selectedGear.numberOfTeeth, this.selectedGear.id, this.selectedGear.hue);
  245. }
  246. } else if (this.stroke.length > 0) {
  247. return this.stroke.push(point);
  248. }
  249. } else if (this.selectedButton === "chainButton") {
  250. return this.stroke.push(point);
  251. } else if (this.selectedButton === "momentumButton") {
  252. if (this.selectedGear) {
  253. return this.selectedGearMomentum = this.calculateMomentumFromCoords(this.selectedGear, x, y);
  254. }
  255. }
  256. }
  257. };
  258. GearSketch.prototype.handlePenUp = function() {
  259. if (this.isPenDown) {
  260. if (this.selectedButton === "gearButton") {
  261. if (!((this.selectedGear != null) || this.stroke.length === 0)) {
  262. this.processGearStroke();
  263. }
  264. } else if (this.selectedButton === "chainButton") {
  265. this.processChainStroke();
  266. } else if (this.selectedButton === "momentumButton") {
  267. if (this.selectedGear) {
  268. if (Math.abs(this.selectedGearMomentum) > MIN_MOMENTUM) {
  269. this.selectedGear.momentum = this.selectedGearMomentum;
  270. } else {
  271. this.selectedGear.momentum = 0;
  272. }
  273. }
  274. this.selectedGearMomentum = 0;
  275. }
  276. this.selectedGear = null;
  277. this.goalLocationGear = null;
  278. return this.isPenDown = false;
  279. }
  280. };
  281. GearSketch.prototype.isButtonAt = function(x, y, button) {
  282. return x > button.location.x && x < button.location.x + button.width + 2 * button.padding && y > button.location.y && y < button.location.y + button.height + 2 * button.padding;
  283. };
  284. GearSketch.prototype.getButtonAt = function(x, y) {
  285. var button, buttonName, _ref;
  286. if (!this.shouldShowButtons()) {
  287. return null;
  288. }
  289. _ref = this.buttons;
  290. for (buttonName in _ref) {
  291. if (!__hasProp.call(_ref, buttonName)) continue;
  292. button = _ref[buttonName];
  293. if (this.isButtonAt(x, y, button)) {
  294. return button;
  295. }
  296. }
  297. return null;
  298. };
  299. GearSketch.prototype.normalizeStroke = function(stroke) {
  300. var MIN_POINT_DISTANCE, normalizedStroke, p1, p2, strokeTail, _i, _len;
  301. MIN_POINT_DISTANCE = 10;
  302. normalizedStroke = [];
  303. if (stroke.length > 0) {
  304. p1 = stroke[0], strokeTail = 2 <= stroke.length ? __slice.call(stroke, 1) : [];
  305. normalizedStroke.push(p1);
  306. for (_i = 0, _len = strokeTail.length; _i < _len; _i++) {
  307. p2 = strokeTail[_i];
  308. if (p1.distance(p2) > MIN_POINT_DISTANCE) {
  309. normalizedStroke.push(p2);
  310. p1 = p2;
  311. }
  312. }
  313. }
  314. return normalizedStroke;
  315. };
  316. GearSketch.prototype.createGearFromStroke = function(stroke) {
  317. var area, doubleArea, height, i, idealTrueAreaRatio, j, maxX, maxY, minX, minY, numberOfPoints, p, radius, sumX, sumY, t, width, x, y, _i, _j, _len;
  318. numberOfPoints = stroke.length;
  319. if (numberOfPoints > 0) {
  320. sumX = 0;
  321. sumY = 0;
  322. minX = Number.MAX_VALUE;
  323. maxX = Number.MIN_VALUE;
  324. minY = Number.MAX_VALUE;
  325. maxY = Number.MIN_VALUE;
  326. for (_i = 0, _len = stroke.length; _i < _len; _i++) {
  327. p = stroke[_i];
  328. sumX += p.x;
  329. sumY += p.y;
  330. minX = Math.min(minX, p.x);
  331. maxX = Math.max(maxX, p.x);
  332. minY = Math.min(minY, p.y);
  333. maxY = Math.max(maxY, p.y);
  334. }
  335. width = maxX - minX;
  336. height = maxY - minY;
  337. t = Math.floor(0.5 * (width + height) / MODULE);
  338. doubleArea = 0;
  339. for (i = _j = 0; 0 <= numberOfPoints ? _j < numberOfPoints : _j > numberOfPoints; i = 0 <= numberOfPoints ? ++_j : --_j) {
  340. j = (i + 1) % numberOfPoints;
  341. doubleArea += stroke[i].x * stroke[j].y;
  342. doubleArea -= stroke[i].y * stroke[j].x;
  343. }
  344. area = Math.abs(doubleArea) / 2;
  345. radius = 0.25 * ((maxX - minX) + (maxY - minY));
  346. idealTrueAreaRatio = (Math.PI * Math.pow(radius, 2)) / area;
  347. if (idealTrueAreaRatio > 0.80 && idealTrueAreaRatio < 1.20 && t > MIN_GEAR_TEETH) {
  348. x = sumX / numberOfPoints;
  349. y = sumY / numberOfPoints;
  350. return new Gear(new Point(x, y), 0, t);
  351. }
  352. }
  353. return null;
  354. };
  355. GearSketch.prototype.removeStrokedGears = function(stroke) {
  356. var gear, id, _ref, _results;
  357. _ref = this.board.getTopLevelGears();
  358. _results = [];
  359. for (id in _ref) {
  360. if (!__hasProp.call(_ref, id)) continue;
  361. gear = _ref[id];
  362. if (Util.pointPathDistance(gear.location, stroke, false) < gear.innerRadius) {
  363. _results.push(this.board.removeGear(gear));
  364. } else {
  365. _results.push(void 0);
  366. }
  367. }
  368. return _results;
  369. };
  370. GearSketch.prototype.processGearStroke = function() {
  371. var gear, isGearAdded, normalizedStroke;
  372. normalizedStroke = this.normalizeStroke(this.stroke);
  373. gear = this.createGearFromStroke(normalizedStroke);
  374. if (gear != null) {
  375. isGearAdded = this.board.addGear(gear);
  376. if (isGearAdded && !(gear.numberOfTeeth in this.gearImages)) {
  377. this.addGearImage(gear);
  378. }
  379. } else {
  380. this.removeStrokedGears(normalizedStroke);
  381. }
  382. return this.stroke = [];
  383. };
  384. GearSketch.prototype.gearImageLoaded = function(numberOfTeeth, image) {
  385. return this.gearImages[numberOfTeeth] = image;
  386. };
  387. GearSketch.prototype.addGearImage = function(gear) {
  388. var ctx, gearCanvas, gearCopy, image, size,
  389. _this = this;
  390. gearCanvas = document.createElement("canvas");
  391. size = 2 * (gear.outerRadius + MODULE);
  392. gearCanvas.height = size;
  393. gearCanvas.width = size;
  394. ctx = gearCanvas.getContext("2d");
  395. gearCopy = new Gear(new Point(0.5 * size, 0.5 * size), 0, gear.numberOfTeeth, gear.id, null, null, null, null, gear.hue);
  396. this.drawGear(ctx, gearCopy);
  397. image = new Image();
  398. image.onload = function() {
  399. return _this.gearImageLoaded(gear.numberOfTeeth, image);
  400. };
  401. return image.src = gearCanvas.toDataURL("image/png");
  402. };
  403. GearSketch.prototype.removeStrokedChains = function(stroke) {
  404. var chain, id, _ref, _results;
  405. _ref = this.board.getChains();
  406. _results = [];
  407. for (id in _ref) {
  408. if (!__hasProp.call(_ref, id)) continue;
  409. chain = _ref[id];
  410. if (chain.intersectsPath(stroke)) {
  411. _results.push(this.board.removeChain(chain));
  412. } else {
  413. _results.push(void 0);
  414. }
  415. }
  416. return _results;
  417. };
  418. GearSketch.prototype.processChainStroke = function() {
  419. var chain, gearsInChain, normalizedStroke;
  420. normalizedStroke = this.normalizeStroke(this.stroke);
  421. this.stroke = [];
  422. gearsInChain = Util.findGearsInsidePolygon(normalizedStroke, this.board.getGears());
  423. if (normalizedStroke.length >= 3 && gearsInChain.length > 0) {
  424. chain = new Chain(normalizedStroke);
  425. return this.board.addChain(chain);
  426. } else if (normalizedStroke.length >= 2) {
  427. return this.removeStrokedChains(normalizedStroke);
  428. }
  429. };
  430. GearSketch.prototype.calculateMomentumFromCoords = function(gear, x, y) {
  431. var angle, angleFromTop;
  432. angle = Math.atan2(y - gear.location.y, x - gear.location.x);
  433. angleFromTop = angle + 0.5 * Math.PI;
  434. if (angleFromTop < Math.PI) {
  435. return angleFromTop;
  436. } else {
  437. return angleFromTop - 2 * Math.PI;
  438. }
  439. };
  440. GearSketch.prototype.updateAndDraw = function() {
  441. var _this = this;
  442. return setTimeout((function() {
  443. requestAnimationFrame(_this.updateAndDraw);
  444. _this.update();
  445. return _this.draw();
  446. }), 1000 / FPS);
  447. };
  448. GearSketch.prototype.updateAndDrawNoRAF = function() {
  449. var _this = this;
  450. this.update();
  451. this.draw();
  452. return setTimeout((function() {
  453. return _this.updateAndDrawNoRAF();
  454. }), 1000 / FPS);
  455. };
  456. GearSketch.prototype.update = function() {
  457. var delta, updateTime;
  458. updateTime = new Date().getTime();
  459. delta = updateTime - this.lastUpdateTime;
  460. if (this.selectedButton === "playButton") {
  461. this.board.rotateAllTurningObjects(delta);
  462. }
  463. if (this.isDemoPlaying) {
  464. this.updateDemo(delta);
  465. }
  466. return this.lastUpdateTime = updateTime;
  467. };
  468. GearSketch.prototype.drawGear = function(ctx, gear, color) {
  469. var angleStep, gearImage, i, innerPoints, numberOfTeeth, outerPoints, r, rotation, x, y, _i, _j, _k, _ref, _ref1;
  470. if (color == null) {
  471. color = "black";
  472. }
  473. _ref = gear.location, x = _ref.x, y = _ref.y;
  474. rotation = gear.rotation;
  475. numberOfTeeth = gear.numberOfTeeth;
  476. gearImage = this.gearImages[gear.numberOfTeeth];
  477. if (color === "black" && (gearImage != null)) {
  478. gearImage = this.gearImages[gear.numberOfTeeth];
  479. ctx.save();
  480. ctx.translate(x, y);
  481. ctx.rotate(rotation);
  482. ctx.drawImage(gearImage, -0.5 * gearImage.width, -0.5 * gearImage.height);
  483. ctx.restore();
  484. return;
  485. }
  486. angleStep = 2 * Math.PI / numberOfTeeth;
  487. innerPoints = [];
  488. outerPoints = [];
  489. for (i = _i = 0; 0 <= numberOfTeeth ? _i < numberOfTeeth : _i > numberOfTeeth; i = 0 <= numberOfTeeth ? ++_i : --_i) {
  490. for (r = _j = 0; _j < 4; r = ++_j) {
  491. if (r === 0 || r === 3) {
  492. innerPoints.push(Point.polar((i + 0.25 * r) * angleStep, gear.innerRadius));
  493. } else {
  494. outerPoints.push(Point.polar((i + 0.25 * r) * angleStep, gear.outerRadius));
  495. }
  496. }
  497. }
  498. ctx.save();
  499. if (color === "black") {
  500. if (!gear.hue) {
  501. gear.hue = Math.floor(Math.random()*360);
  502. }
  503. ctx.fillStyle = "hsla(" + gear.hue + ", 85%, 60%, 0.8)";
  504. } else {
  505. ctx.fillStyle = "hsla(0, 0%, 90%, 0.2)";
  506. }
  507. ctx.strokeStyle = color;
  508. ctx.lineWidth = 2;
  509. ctx.translate(x, y);
  510. ctx.rotate(rotation);
  511. ctx.beginPath();
  512. ctx.moveTo(gear.innerRadius, 0);
  513. for (i = _k = 0, _ref1 = numberOfTeeth * 2; 0 <= _ref1 ? _k < _ref1 : _k > _ref1; i = 0 <= _ref1 ? ++_k : --_k) {
  514. if (i % 2 === 0) {
  515. ctx.lineTo(innerPoints[i].x, innerPoints[i].y);
  516. ctx.lineTo(outerPoints[i].x, outerPoints[i].y);
  517. } else {
  518. ctx.lineTo(outerPoints[i].x, outerPoints[i].y);
  519. ctx.lineTo(innerPoints[i].x, innerPoints[i].y);
  520. }
  521. }
  522. ctx.closePath();
  523. ctx.fill();
  524. ctx.stroke();
  525. ctx.beginPath();
  526. ctx.moveTo(AXIS_RADIUS, 0);
  527. ctx.arc(0, 0, AXIS_RADIUS, 0, 2 * Math.PI, true);
  528. ctx.closePath();
  529. ctx.stroke();
  530. ctx.beginPath();
  531. ctx.moveTo(AXIS_RADIUS, 0);
  532. ctx.lineTo(gear.innerRadius, 0);
  533. ctx.closePath();
  534. ctx.stroke();
  535. return ctx.restore();
  536. };
  537. GearSketch.prototype.drawButton = function(ctx, button) {
  538. var height, padding, radius, width, x, y, _ref;
  539. _ref = button.location, x = _ref.x, y = _ref.y;
  540. padding = button.padding;
  541. ctx.save();
  542. ctx.translate(x, y);
  543. ctx.beginPath();
  544. radius = 10;
  545. width = button.width + 2 * padding;
  546. height = button.height + 2 * padding;
  547. ctx.moveTo(radius, 0);
  548. ctx.lineTo(width - radius, 0);
  549. ctx.quadraticCurveTo(width, 0, width, radius);
  550. ctx.lineTo(width, height - radius);
  551. ctx.quadraticCurveTo(width, height, width - radius, height);
  552. ctx.lineTo(radius, height);
  553. ctx.quadraticCurveTo(0, height, 0, height - radius);
  554. ctx.lineTo(0, radius);
  555. ctx.quadraticCurveTo(0, 0, radius, 0);
  556. if (button.name === this.selectedButton) {
  557. ctx.fillStyle = "rgba(50, 150, 255, 0.8)";
  558. } else {
  559. ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
  560. }
  561. ctx.fill();
  562. ctx.lineWidth = 1;
  563. ctx.strokeStyle = "black";
  564. ctx.stroke();
  565. ctx.drawImage(button, padding, padding);
  566. return ctx.restore();
  567. };
  568. GearSketch.prototype.drawMomentum = function(ctx, gear, momentum, color) {
  569. var angle, head, headX, headY, length, p1, p2, pitchRadius, sign, top;
  570. if (color == null) {
  571. color = "red";
  572. }
  573. pitchRadius = gear.pitchRadius;
  574. top = new Point(gear.location.x, gear.location.y - pitchRadius);
  575. ctx.save();
  576. ctx.lineWidth = 5;
  577. ctx.lineCap = "round";
  578. ctx.strokeStyle = color;
  579. ctx.translate(top.x, top.y);
  580. ctx.beginPath();
  581. ctx.arc(0, pitchRadius, pitchRadius, -0.5 * Math.PI, momentum - 0.5 * Math.PI, momentum < 0);
  582. ctx.stroke();
  583. length = 15;
  584. angle = 0.2 * Math.PI;
  585. headX = -Math.cos(momentum + 0.5 * Math.PI) * pitchRadius;
  586. headY = pitchRadius - Math.sin(momentum + 0.5 * Math.PI) * pitchRadius;
  587. head = new Point(headX, headY);
  588. sign = Util.sign(momentum);
  589. p1 = head.minus(Point.polar(momentum + angle, sign * length));
  590. ctx.beginPath();
  591. ctx.moveTo(headX, headY);
  592. ctx.lineTo(p1.x, p1.y);
  593. ctx.stroke();
  594. p2 = head.minus(Point.polar(momentum - angle, sign * length));
  595. ctx.beginPath();
  596. ctx.moveTo(headX, headY);
  597. ctx.lineTo(p2.x, p2.y);
  598. ctx.stroke();
  599. return ctx.restore();
  600. };
  601. GearSketch.prototype.drawChain = function(ctx, chain) {
  602. var isCounterClockwise, point, segment, _i, _j, _len, _len1, _ref, _ref1;
  603. ctx.save();
  604. ctx.lineWidth = Chain.WIDTH;
  605. ctx.lineCap = "round";
  606. ctx.strokeStyle = "rgb(0, 0, 255)";
  607. ctx.moveTo(chain.segments[0].start.x, chain.segments[0].start.y);
  608. _ref = chain.segments;
  609. for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  610. segment = _ref[_i];
  611. if (segment instanceof ArcSegment) {
  612. isCounterClockwise = segment.direction === Util.Direction.COUNTER_CLOCKWISE;
  613. ctx.beginPath();
  614. ctx.arc(segment.center.x, segment.center.y, segment.radius, segment.startAngle, segment.endAngle, isCounterClockwise);
  615. ctx.stroke();
  616. } else {
  617. ctx.beginPath();
  618. ctx.moveTo(segment.start.x, segment.start.y);
  619. ctx.lineTo(segment.end.x, segment.end.y);
  620. ctx.stroke();
  621. }
  622. }
  623. ctx.fillStyle = "white";
  624. _ref1 = chain.findPointsOnChain(25);
  625. for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
  626. point = _ref1[_j];
  627. ctx.beginPath();
  628. ctx.arc(point.x, point.y, 3, 0, 2 * Math.PI, true);
  629. ctx.fill();
  630. }
  631. return ctx.restore();
  632. };
  633. GearSketch.prototype.drawDemoPointer = function(ctx, location) {
  634. return ctx.drawImage(this.pointerImage, location.x - 0.5 * this.pointerImage.width, location.y);
  635. };
  636. GearSketch.prototype.draw = function() {
  637. var arrow, arrowsToDraw, buttonName, chain, ctx, gear, i, momentum, shouldDrawChainsAndArrows, sortedGears, _i, _j, _k, _l, _len, _len1, _ref, _ref1, _ref2, _ref3;
  638. if (this.canvas.getContext != null) {
  639. this.updateCanvasSize();
  640. ctx = this.canvas.getContext("2d");
  641. ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
  642. sortedGears = this.board.getGearsSortedByGroupAndLevel();
  643. arrowsToDraw = [];
  644. for (i = _i = 0, _ref = sortedGears.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
  645. gear = sortedGears[i];
  646. momentum = gear.momentum;
  647. if (gear === this.selectedGear && this.goalLocationGear) {
  648. this.drawGear(ctx, gear, "grey");
  649. if (momentum) {
  650. arrowsToDraw.push([gear, momentum, "grey"]);
  651. }
  652. } else {
  653. this.drawGear(ctx, gear);
  654. if (momentum) {
  655. arrowsToDraw.push([gear, momentum, "red"]);
  656. }
  657. }
  658. shouldDrawChainsAndArrows = (i === sortedGears.length - 1) || (this.board.getLevelScore(gear) !== this.board.getLevelScore(sortedGears[i + 1]));
  659. if (shouldDrawChainsAndArrows) {
  660. _ref1 = this.board.getChainsInGroupOnLevel(gear.group, gear.level);
  661. for (_j = 0, _len = _ref1.length; _j < _len; _j++) {
  662. chain = _ref1[_j];
  663. this.drawChain(ctx, chain);
  664. }
  665. for (_k = 0, _len1 = arrowsToDraw.length; _k < _len1; _k++) {
  666. arrow = arrowsToDraw[_k];
  667. this.drawMomentum(ctx, arrow[0], arrow[1], arrow[2]);
  668. }
  669. arrowsToDraw = [];
  670. }
  671. }
  672. if (this.goalLocationGear) {
  673. this.drawGear(ctx, this.goalLocationGear, "red");
  674. }
  675. if ((this.selectedGear != null) && this.selectedGearMomentum) {
  676. this.drawMomentum(ctx, this.selectedGear, this.selectedGearMomentum);
  677. }
  678. if (this.stroke.length > 0) {
  679. ctx.save();
  680. if (this.selectedButton === "gearButton") {
  681. ctx.strokeStyle = "black";
  682. ctx.lineWidth = 2;
  683. } else {
  684. ctx.strokeStyle = "blue";
  685. ctx.lineWidth = 4;
  686. }
  687. ctx.beginPath();
  688. ctx.moveTo(this.stroke[0].x, this.stroke[0].y);
  689. for (i = _l = 1, _ref2 = this.stroke.length; 1 <= _ref2 ? _l < _ref2 : _l > _ref2; i = 1 <= _ref2 ? ++_l : --_l) {
  690. ctx.lineTo(this.stroke[i].x, this.stroke[i].y);
  691. }
  692. ctx.stroke();
  693. ctx.restore();
  694. }
  695. if (this.areButtonsLoaded && this.shouldShowButtons()) {
  696. _ref3 = this.buttons;
  697. for (buttonName in _ref3) {
  698. if (!__hasProp.call(_ref3, buttonName)) continue;
  699. this.drawButton(ctx, this.buttons[buttonName]);
  700. }
  701. }
  702. if (this.message.length > 0) {
  703. ctx.save();
  704. ctx.fillStyle = this.messageColor;
  705. ctx.font = "bold 20px Arial";
  706. ctx.fillText(this.message, 20, 120);
  707. ctx.restore();
  708. }
  709. if (this.isDemoPlaying && this.pointerImage) {
  710. return this.drawDemoPointer(ctx, this.pointerLocation);
  711. }
  712. }
  713. };
  714. GearSketch.prototype.updateCanvasSize = function() {
  715. this.canvas.width = this.canvas.parentElement.getBoundingClientRect().width;
  716. this.canvas.height = this.canvas.parentElement.getBoundingClientRect().height;
  717. this.buttons["clearButton"].location.x = Math.max(this.canvas.width - 260, this.buttons["playButton"].location.x + 80);
  718. this.buttons["cloudButton"].location.x = this.buttons["clearButton"].location.x + 80;
  719. return this.buttons["helpButton"].location.x = this.buttons["cloudButton"].location.x + 80;
  720. };
  721. GearSketch.prototype.loadDemoMovements = function() {
  722. return this.demoMovements = [
  723. {
  724. from: this.getButtonCenter("helpButton"),
  725. to: this.getButtonCenter("gearButton"),
  726. atEnd: MovementAction.PEN_TAP,
  727. type: MovementType.STRAIGHT,
  728. duration: 2000
  729. }, {
  730. to: new Point(300, 200),
  731. type: MovementType.STRAIGHT,
  732. duration: 1500
  733. }, {
  734. atStart: MovementAction.PEN_DOWN,
  735. atEnd: MovementAction.PEN_UP,
  736. type: MovementType.CIRCLE,
  737. radius: 100,
  738. duration: 1500
  739. }, {
  740. to: new Point(500, 200),
  741. type: MovementType.STRAIGHT,
  742. duration: 1000
  743. }, {
  744. atStart: MovementAction.PEN_DOWN,
  745. atEnd: MovementAction.PEN_UP,
  746. type: MovementType.CIRCLE,
  747. radius: 40,
  748. duration: 1000
  749. }, {
  750. to: new Point(500, 240),
  751. type: MovementType.STRAIGHT,
  752. duration: 500
  753. }, {
  754. to: new Point(300, 300),
  755. atStart: MovementAction.PEN_DOWN,
  756. atEnd: MovementAction.PEN_UP,
  757. type: MovementType.STRAIGHT,
  758. duration: 1500
  759. }, {
  760. to: new Point(100, 180),
  761. type: MovementType.STRAIGHT,
  762. duration: 1000
  763. }, {
  764. atStart: MovementAction.PEN_DOWN,
  765. atEnd: MovementAction.PEN_UP,
  766. type: MovementType.CIRCLE,
  767. radius: 90,
  768. duration: 1000
  769. }, {
  770. to: new Point(100, 260),
  771. type: MovementType.STRAIGHT,
  772. duration: 500
  773. }, {
  774. to: new Point(180, 260),
  775. atStart: MovementAction.PEN_DOWN,
  776. atEnd: MovementAction.PEN_UP,
  777. type: MovementType.STRAIGHT,
  778. duration: 1500
  779. }, {
  780. to: new Point(550, 220),
  781. type: MovementType.STRAIGHT,
  782. duration: 1500
  783. }, {
  784. atStart: MovementAction.PEN_DOWN,
  785. atEnd: MovementAction.PEN_UP,
  786. type: MovementType.CIRCLE,
  787. radius: 80,
  788. duration: 1000
  789. }, {
  790. to: this.getButtonCenter("chainButton"),
  791. atEnd: MovementAction.PEN_TAP,
  792. type: MovementType.STRAIGHT,
  793. duration: 1500
  794. }, {
  795. to: new Point(280, 150),
  796. type: MovementType.STRAIGHT,
  797. duration: 1500
  798. }, {
  799. atStart: MovementAction.PEN_DOWN,
  800. type: MovementType.LEFT_HALF_CIRCLE,
  801. radius: 140,
  802. duration: 1500,
  803. pause: 0
  804. }, {
  805. to: new Point(600, 400),
  806. type: MovementType.STRAIGHT,
  807. duration: 1000,
  808. pause: 0
  809. }, {
  810. type: MovementType.RIGHT_HALF_CIRCLE,
  811. radius: 110,
  812. duration: 1000,
  813. pause: 0
  814. }, {
  815. to: new Point(280, 150),
  816. atEnd: MovementAction.PEN_UP,
  817. type: MovementType.STRAIGHT,
  818. duration: 1000
  819. }, {
  820. to: this.getButtonCenter("momentumButton"),
  821. atEnd: MovementAction.PEN_TAP,
  822. type: MovementType.STRAIGHT,
  823. duration: 1500
  824. }, {
  825. to: new Point(185, 180),
  826. type: MovementType.STRAIGHT,
  827. duration: 1500
  828. }, {
  829. to: new Point(150, 190),
  830. atStart: MovementAction.PEN_DOWN,
  831. atEnd: MovementAction.PEN_UP,
  832. type: MovementType.STRAIGHT,
  833. duration: 1000
  834. }, {
  835. to: this.getButtonCenter("playButton"),
  836. atEnd: MovementAction.PEN_TAP,
  837. type: MovementType.STRAIGHT,
  838. duration: 1500
  839. }, {
  840. to: this.getButtonCenter("chainButton"),
  841. atEnd: MovementAction.PEN_TAP,
  842. type: MovementType.STRAIGHT,
  843. duration: 3000
  844. }, {
  845. to: new Point(425, 250),
  846. type: MovementType.STRAIGHT,
  847. duration: 1000
  848. }, {
  849. to: new Point(525, 150),
  850. atStart: MovementAction.PEN_DOWN,
  851. atEnd: MovementAction.PEN_UP,
  852. type: MovementType.STRAIGHT,
  853. duration: 1000
  854. }, {
  855. to: this.getButtonCenter("gearButton"),
  856. atEnd: MovementAction.PEN_TAP,
  857. type: MovementType.STRAIGHT,
  858. duration: 1500
  859. }, {
  860. to: new Point(20, 250),
  861. type: MovementType.STRAIGHT,
  862. duration: 1000
  863. }, {
  864. to: new Point(650, 300),
  865. atStart: MovementAction.PEN_DOWN,
  866. atEnd: MovementAction.PEN_UP,
  867. type: MovementType.STRAIGHT,
  868. duration: 1500
  869. }, {
  870. to: new Point(425, 200),
  871. type: MovementType.STRAIGHT,
  872. duration: 1000
  873. }, {
  874. to: new Point(200, 400),
  875. atStart: MovementAction.PEN_DOWN,
  876. atEnd: MovementAction.PEN_UP,
  877. type: MovementType.STRAIGHT,
  878. duration: 1500
  879. }
  880. ];
  881. };
  882. GearSketch.prototype.getButtonCenter = function(buttonName) {
  883. var button, buttonCorner;
  884. button = this.buttons[buttonName];
  885. buttonCorner = new Point(button.location.x, button.location.y);
  886. return buttonCorner.plus(new Point(0.5 * button.width + button.padding, 0.5 * button.height + button.padding));
  887. };
  888. GearSketch.prototype.updateDemo = function(delta) {
  889. var movement;
  890. if (this.restTimer > 0) {
  891. this.restTimer = Math.max(this.restTimer - delta, 0);
  892. return;
  893. } else if (this.currentDemoMovement === this.demoMovements.length) {
  894. this.stopDemo();
  895. return;
  896. }
  897. movement = this.demoMovements[this.currentDemoMovement];
  898. if (this.movementCompletion === 0) {
  899. if (movement.from == null) {
  900. movement.from = this.pointerLocation;
  901. }
  902. if (movement.pause == null) {
  903. movement.pause = 500;
  904. }
  905. this.pointerLocation = movement.from.clone();
  906. if (movement.atStart === MovementAction.PEN_DOWN) {
  907. this.handlePenDown(this.pointerLocation.x, this.pointerLocation.y);
  908. }
  909. }
  910. if (this.movementCompletion < 1) {
  911. this.movementCompletion = Math.min(1, this.movementCompletion + delta / movement.duration);
  912. this.updatePointerLocation(movement, this.movementCompletion);
  913. this.handlePenMove(this.pointerLocation.x, this.pointerLocation.y);
  914. }
  915. if (this.movementCompletion === 1) {
  916. if (movement.atEnd === MovementAction.PEN_TAP) {
  917. this.handlePenDown(this.pointerLocation.x, this.pointerLocation.y);
  918. this.handlePenUp();
  919. } else if (movement.atEnd === MovementAction.PEN_UP) {
  920. this.handlePenUp();
  921. }
  922. this.restTimer = movement.pause;
  923. this.movementCompletion = 0;
  924. return this.currentDemoMovement++;
  925. }
  926. };
  927. GearSketch.prototype.updatePointerLocation = function(movement, movementCompletion) {
  928. var angle, center, delta;
  929. if (movement.type === MovementType.STRAIGHT) {
  930. delta = movement.to.minus(movement.from);
  931. return this.pointerLocation = movement.from.plus(delta.times(movementCompletion));
  932. } else if (movement.type === MovementType.CIRCLE) {
  933. center = new Point(movement.from.x, movement.from.y + movement.radius);
  934. return this.pointerLocation = center.plus(Point.polar(Math.PI - (movementCompletion - 0.25) * 2 * Math.PI, movement.radius));
  935. } else if (movement.type === MovementType.LEFT_HALF_CIRCLE) {
  936. center = new Point(movement.from.x, movement.from.y + movement.radius);
  937. angle = 1.5 * Math.PI - movementCompletion * Math.PI;
  938. return this.pointerLocation = center.plus(Point.polar(angle, movement.radius));
  939. } else if (movement.type === MovementType.RIGHT_HALF_CIRCLE) {
  940. center = new Point(movement.from.x, movement.from.y - movement.radius);
  941. angle = 0.5 * Math.PI - movementCompletion * Math.PI;
  942. return this.pointerLocation = center.plus(Point.polar(angle, movement.radius));
  943. }
  944. };
  945. GearSketch.prototype.playDemo = function() {
  946. this.loadDemoMovements();
  947. this.boardBackup = this.board.clone();
  948. this.board.clear();
  949. this.currentDemoMovement = 0;
  950. this.movementCompletion = 0;
  951. this.isDemoPlaying = true;
  952. return this.displayMessage("click anywhere to stop the demo");
  953. };
  954. GearSketch.prototype.stopDemo = function() {
  955. this.isDemoPlaying = false;
  956. this.restTimer = 0;
  957. this.stroke = [];
  958. this.selectedGear = null;
  959. this.selectedIcon = "gearIcon";
  960. this.board.restoreAfterDemo(this.boardBackup);
  961. return this.clearMessage();
  962. };
  963. GearSketch.prototype.boardUploaded = function(event) {
  964. parent.location.hash = event.target.responseText.trim();
  965. return this.displayMessage("Board saved. Share it by copying the text in your address bar.", "black", 4000);
  966. };
  967. GearSketch.prototype.uploadBoard = function() {
  968. var boardJSON,
  969. _this = this;
  970. boardJSON = JSON.stringify(this.board);
  971. return Util.sendPostRequest(boardJSON, "upload_board.php", (function(event) {
  972. return _this.boardUploaded(event);
  973. }));
  974. };
  975. return GearSketch;
  976. })();
  977. window.gearsketch.GearSketch = GearSketch;
  978. }).call(this);
  979. /*
  980. //@ sourceMappingURL=gearsketch_main.map
  981. */