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.

1451 lines
51 KiB

  1. // Generated by CoffeeScript 1.6.3
  2. (function() {
  3. "use strict";
  4. var ArcSegment, Board, Chain, Gear, LineSegment, Point, Util,
  5. __hasProp = {}.hasOwnProperty,
  6. __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
  7. Point = window.gearsketch.Point;
  8. ArcSegment = window.gearsketch.ArcSegment;
  9. LineSegment = window.gearsketch.LineSegment;
  10. Util = window.gearsketch.Util;
  11. window.gearsketch.model = {};
  12. Gear = (function() {
  13. function Gear(location, rotation, numberOfTeeth, id, momentum, group, level, connections, hue) {
  14. this.location = location;
  15. this.rotation = rotation;
  16. this.numberOfTeeth = numberOfTeeth;
  17. this.id = id;
  18. this.momentum = momentum != null ? momentum : 0;
  19. this.group = group != null ? group : 0;
  20. this.level = level != null ? level : 0;
  21. this.connections = connections != null ? connections : {};
  22. if (this.id == null) {
  23. this.id = Util.createUUID();
  24. }
  25. this.pitchRadius = Util.MODULE * (0.5 * this.numberOfTeeth);
  26. this.innerRadius = Util.MODULE * (0.5 * this.numberOfTeeth - 1.25);
  27. this.outerRadius = Util.MODULE * (0.5 * this.numberOfTeeth + 1);
  28. this.hue = hue != null? hue : Math.floor(Math.random()*360);
  29. }
  30. Gear.prototype.getCircumference = function() {
  31. return 2 * Math.PI * this.pitchRadius;
  32. };
  33. Gear.prototype.distanceToPoint = function(point) {
  34. return Math.max(0, this.location.distance(point) - this.pitchRadius);
  35. };
  36. Gear.prototype.edgeDistance = function(gear) {
  37. var axisDistance;
  38. axisDistance = this.location.distance(gear.location);
  39. return Math.abs(axisDistance - this.pitchRadius - gear.pitchRadius);
  40. };
  41. Gear.prototype.restore = function(gear) {
  42. this.location = gear.location;
  43. this.rotation = gear.rotation;
  44. this.momentum = gear.momentum;
  45. this.group = gear.group;
  46. this.level = gear.level;
  47. return this.connections = gear.connections;
  48. };
  49. Gear.prototype.clone = function() {
  50. return new Gear(this.location.clone(), this.rotation, this.numberOfTeeth, this.id, this.momentum, this.group, this.level, Util.clone(this.connections), this.hue);
  51. };
  52. Gear.fromObject = function(obj) {
  53. return new Gear(Point.fromObject(obj.location), obj.rotation, obj.numberOfTeeth, obj.id, obj.momentum, obj.group, obj.level, obj.connections, obj.hue);
  54. };
  55. return Gear;
  56. })();
  57. window.gearsketch.model.Gear = Gear;
  58. Chain = (function() {
  59. Chain.WIDTH = 8;
  60. Chain.prototype.points = [];
  61. Chain.prototype.segments = [];
  62. function Chain(stroke, id, group, level, connections) {
  63. this.id = id;
  64. this.group = group != null ? group : 0;
  65. this.level = level != null ? level : 0;
  66. this.connections = connections != null ? connections : {};
  67. if (this.id == null) {
  68. this.id = Util.createUUID();
  69. }
  70. this.points = Util.clone(stroke);
  71. this.rotation = 0;
  72. }
  73. Chain.prototype.getLength = function() {
  74. return this.segments.reduce((function(total, segment) {
  75. return total + segment.getLength();
  76. }), 0);
  77. };
  78. Chain.prototype.getCircumference = function() {
  79. return this.getLength();
  80. };
  81. Chain.prototype.getStartingPoint = function() {
  82. if (this.direction === Util.Direction.CLOCKWISE) {
  83. return this.rotation / (2 * Math.PI) * this.getLength();
  84. } else {
  85. return -this.rotation / (2 * Math.PI) * this.getLength();
  86. }
  87. };
  88. Chain.prototype.findPointOnChain = function(distance) {
  89. var distanceToGo, length, segment, segmentLength, _i, _len, _ref;
  90. length = this.getLength();
  91. distanceToGo = Util.mod(distance + this.getStartingPoint(), length);
  92. _ref = this.segments;
  93. for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  94. segment = _ref[_i];
  95. segmentLength = segment.getLength();
  96. if (distanceToGo < segmentLength) {
  97. return segment.findPoint(distanceToGo);
  98. } else {
  99. distanceToGo -= segmentLength;
  100. }
  101. }
  102. return null;
  103. };
  104. Chain.prototype.findPointsOnChain = function(numberOfPoints) {
  105. var delta, p, _i, _results;
  106. delta = this.getLength() / numberOfPoints;
  107. _results = [];
  108. for (p = _i = 0; 0 <= numberOfPoints ? _i < numberOfPoints : _i > numberOfPoints; p = 0 <= numberOfPoints ? ++_i : --_i) {
  109. _results.push(this.findPointOnChain(p * delta));
  110. }
  111. return _results;
  112. };
  113. Chain.prototype.distanceToPoint = function(point) {
  114. var segment;
  115. return Math.min.apply(null, (function() {
  116. var _i, _len, _ref, _results;
  117. _ref = this.segments;
  118. _results = [];
  119. for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  120. segment = _ref[_i];
  121. _results.push(segment.distanceToPoint(point));
  122. }
  123. return _results;
  124. }).call(this));
  125. };
  126. Chain.prototype.distanceToSegment = function(s) {
  127. var segment;
  128. return Math.min.apply(null, (function() {
  129. var _i, _len, _ref, _results;
  130. _ref = this.segments;
  131. _results = [];
  132. for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  133. segment = _ref[_i];
  134. _results.push(segment.distanceToSegment(s));
  135. }
  136. return _results;
  137. }).call(this));
  138. };
  139. Chain.prototype.distanceToChain = function(chain) {
  140. var segment;
  141. return Math.min.apply(null, (function() {
  142. var _i, _len, _ref, _results;
  143. _ref = this.segments;
  144. _results = [];
  145. for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  146. segment = _ref[_i];
  147. _results.push(chain.distanceToSegment(segment));
  148. }
  149. return _results;
  150. }).call(this));
  151. };
  152. Chain.prototype.intersectsPath = function(path) {
  153. var i, j, _i, _ref;
  154. for (i = _i = 0, _ref = path.length - 1; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
  155. j = i + 1;
  156. if (this.distanceToSegment(new LineSegment(path[i], path[j])) === 0) {
  157. return true;
  158. }
  159. }
  160. return false;
  161. };
  162. Chain.prototype.crossesNonSupportingGears = function(board) {
  163. var gear, id, _ref;
  164. _ref = board.getGears();
  165. for (id in _ref) {
  166. if (!__hasProp.call(_ref, id)) continue;
  167. gear = _ref[id];
  168. if (!(__indexOf.call(this.supportingGearIds, id) >= 0) && !(id in this.ignoredGearIds)) {
  169. if (this.distanceToPoint(gear.location) < gear.pitchRadius + Util.EPSILON) {
  170. return true;
  171. }
  172. }
  173. }
  174. return false;
  175. };
  176. Chain.prototype.findPointOnSupportingGear = function(gearIndex, incoming) {
  177. if (incoming) {
  178. return this.points[Util.mod(2 * gearIndex - 1, this.points.length)];
  179. } else {
  180. return this.points[2 * gearIndex];
  181. }
  182. };
  183. Chain.prototype.removeGear = function(gear, board) {
  184. var acknowledgedGears, afterIndex, beforeGear, beforeIndex, g, gears, index, numberOfGears, path, replacementGears;
  185. while ((index = this.supportingGearIds.indexOf(gear.id)) !== -1) {
  186. gears = board.getGearsWithIds(this.supportingGearIds);
  187. numberOfGears = gears.length;
  188. beforeIndex = Util.mod(index - 1, numberOfGears);
  189. beforeGear = gears[beforeIndex];
  190. afterIndex = Util.mod(index + 1, numberOfGears);
  191. acknowledgedGears = board.getAcknowledgedGears(this.ignoredGearIds);
  192. path = [this.findPointOnSupportingGear(index, true), this.findPointOnSupportingGear(index, false), this.findPointOnSupportingGear(afterIndex, true)];
  193. replacementGears = this.findSupportingGearsOnPath(acknowledgedGears, beforeGear, path, 0, false);
  194. gears.splice.apply(gears, [index, 1].concat(replacementGears));
  195. this.removeRepeatedGears(gears);
  196. this.supportingGearIds = (function() {
  197. var _i, _len, _results;
  198. _results = [];
  199. for (_i = 0, _len = gears.length; _i < _len; _i++) {
  200. g = gears[_i];
  201. _results.push(g.id);
  202. }
  203. return _results;
  204. })();
  205. }
  206. return this.update(board);
  207. };
  208. Chain.prototype.findChainTangentSide = function(gear) {
  209. if ((this.direction === Util.Direction.CLOCKWISE) === (gear.id in this.innerGearIds)) {
  210. return Util.Side.LEFT;
  211. } else {
  212. return Util.Side.RIGHT;
  213. }
  214. };
  215. Chain.prototype.findReverseChainTangentSide = function(gear) {
  216. if (this.findChainTangentSide(gear) === Util.Side.LEFT) {
  217. return Util.Side.RIGHT;
  218. } else {
  219. return Util.Side.LEFT;
  220. }
  221. };
  222. Chain.prototype.findFirstSupportingGearOnPath = function(path, gears) {
  223. var a, b, d, pathLength, stepSize, supportingGear;
  224. stepSize = 10;
  225. pathLength = Util.getLength(path);
  226. supportingGear = null;
  227. a = path[0];
  228. d = 0;
  229. while (d < pathLength && (supportingGear == null)) {
  230. d += stepSize;
  231. b = Util.findPointOnPath(path, d);
  232. supportingGear = Util.findNearestIntersectingGear(gears, new LineSegment(a, b));
  233. }
  234. return [supportingGear, d];
  235. };
  236. Chain.prototype.findSupportingGearsOnPath = function(gears, firstSupportingGear, path, startDistance, isClosed) {
  237. var a, b, d, lineSegment, nextSupportingGear, pathLength, stepSize, supportingGear, supportingGears, tangentPoint, tangentSide;
  238. if (startDistance == null) {
  239. startDistance = 0;
  240. }
  241. if (isClosed == null) {
  242. isClosed = true;
  243. }
  244. stepSize = 10;
  245. pathLength = Util.getLength(path, isClosed);
  246. supportingGear = firstSupportingGear;
  247. supportingGears = [];
  248. a = firstSupportingGear.location;
  249. d = startDistance;
  250. while (d < pathLength) {
  251. d += stepSize;
  252. b = Util.findPointOnPath(path, d);
  253. tangentSide = this.findReverseChainTangentSide(supportingGear);
  254. tangentPoint = Util.findGearTangentPoints(b, supportingGear)[tangentSide];
  255. if (tangentPoint != null) {
  256. a = tangentPoint;
  257. }
  258. lineSegment = new LineSegment(a, b);
  259. nextSupportingGear = Util.findNearestIntersectingGear(gears, lineSegment, Util.makeSet(supportingGear.id));
  260. if (nextSupportingGear != null) {
  261. supportingGear = nextSupportingGear;
  262. supportingGears.push(supportingGear);
  263. }
  264. }
  265. return supportingGears;
  266. };
  267. Chain.prototype.removeRepeatedGears = function(gearsList) {
  268. var g1, g2, i, j, numberOfGears, numberOfNoops;
  269. numberOfNoops = 0;
  270. i = 0;
  271. while (numberOfNoops < (numberOfGears = gearsList.length)) {
  272. j = (i + 1) % numberOfGears;
  273. g1 = gearsList[i];
  274. g2 = gearsList[j];
  275. if (g1 === g2) {
  276. gearsList.splice(j, 1);
  277. numberOfNoops = 0;
  278. } else {
  279. numberOfNoops++;
  280. i = (i + 1) % numberOfGears;
  281. }
  282. }
  283. return gearsList;
  284. };
  285. Chain.prototype.containsSuccessiveOverlappingGears = function(gearsList) {
  286. var g1, g2, i, j, numberOfGears, _i;
  287. numberOfGears = gearsList.length;
  288. for (i = _i = 0; 0 <= numberOfGears ? _i < numberOfGears : _i > numberOfGears; i = 0 <= numberOfGears ? ++_i : --_i) {
  289. j = (i + 1) % numberOfGears;
  290. g1 = gearsList[i];
  291. g2 = gearsList[j];
  292. if (g1.location.distance(g2.location) < (g1.outerRadius + g2.outerRadius)) {
  293. return true;
  294. }
  295. }
  296. return false;
  297. };
  298. Chain.prototype.findSupportingGearIds = function(gears) {
  299. var finalSegment, firstSupportingGear, gear, lastSupportingGear, nextSupportingGears, startDistance, supportingGears, tangentPoint, tangentSide, _i, _len, _ref, _ref1, _results;
  300. _ref = this.findFirstSupportingGearOnPath(this.points, gears), firstSupportingGear = _ref[0], startDistance = _ref[1];
  301. supportingGears = [firstSupportingGear];
  302. nextSupportingGears = this.findSupportingGearsOnPath(gears, firstSupportingGear, this.points, startDistance);
  303. supportingGears = supportingGears.concat(nextSupportingGears);
  304. tangentSide = this.findChainTangentSide(firstSupportingGear);
  305. tangentPoint = Util.findGearTangentPoints(this.points[0], firstSupportingGear)[tangentSide];
  306. if (tangentPoint != null) {
  307. finalSegment = [this.points[0], tangentPoint];
  308. lastSupportingGear = supportingGears[supportingGears.length - 1];
  309. nextSupportingGears = this.findSupportingGearsOnPath(gears, lastSupportingGear, finalSegment, 0, false);
  310. supportingGears = supportingGears.concat(nextSupportingGears);
  311. }
  312. _ref1 = this.removeRepeatedGears(supportingGears);
  313. _results = [];
  314. for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
  315. gear = _ref1[_i];
  316. _results.push(gear.id);
  317. }
  318. return _results;
  319. };
  320. Chain.prototype.findIgnoredGearIds = function(board) {
  321. var acknowledgedLevels, currentDistance, currentLevel, d, distance, gear, gears, group, id, ignoredGearIds, level, levels, minDistances, _ref;
  322. gears = board.getGears();
  323. minDistances = {};
  324. for (id in gears) {
  325. if (!__hasProp.call(gears, id)) continue;
  326. gear = gears[id];
  327. group = gear.group;
  328. level = gear.level;
  329. d = Util.pointPathDistance(gear.location, this.points) - gear.pitchRadius;
  330. if ((((_ref = minDistances[group]) != null ? _ref[level] : void 0) == null) || d < minDistances[group][level]) {
  331. if (minDistances[group] == null) {
  332. minDistances[group] = {};
  333. }
  334. minDistances[group][level] = d;
  335. }
  336. }
  337. acknowledgedLevels = {};
  338. for (group in minDistances) {
  339. if (!__hasProp.call(minDistances, group)) continue;
  340. levels = minDistances[group];
  341. for (level in levels) {
  342. if (!__hasProp.call(levels, level)) continue;
  343. distance = levels[level];
  344. currentLevel = acknowledgedLevels[group];
  345. if (currentLevel == null) {
  346. acknowledgedLevels[group] = parseInt(level, 10);
  347. } else if (distance > 0) {
  348. currentDistance = minDistances[group][currentLevel];
  349. if (currentDistance < 0 || distance < currentDistance) {
  350. acknowledgedLevels[group] = parseInt(level, 10);
  351. }
  352. }
  353. }
  354. }
  355. ignoredGearIds = {};
  356. for (id in gears) {
  357. if (!__hasProp.call(gears, id)) continue;
  358. gear = gears[id];
  359. if (acknowledgedLevels[gear.group] !== gear.level) {
  360. ignoredGearIds[id] = true;
  361. }
  362. }
  363. return ignoredGearIds;
  364. };
  365. Chain.prototype.findIgnoredGearIdsInTightenedChain = function(board) {
  366. var gear, gearId, group, groups, id, level, updatedIgnoredGearIds, _i, _len, _ref, _ref1;
  367. groups = {};
  368. _ref = this.supportingGearIds;
  369. for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  370. gearId = _ref[_i];
  371. gear = board.getGearWithId(gearId);
  372. group = gear.group;
  373. level = gear.level;
  374. if (groups[group] == null) {
  375. groups[group] = {};
  376. }
  377. groups[group][level] = true;
  378. }
  379. updatedIgnoredGearIds = {};
  380. _ref1 = board.getGears();
  381. for (id in _ref1) {
  382. if (!__hasProp.call(_ref1, id)) continue;
  383. gear = _ref1[id];
  384. group = gear.group;
  385. level = gear.level;
  386. if ((groups[group] != null) && (groups[group][level] == null)) {
  387. updatedIgnoredGearIds[id] = true;
  388. }
  389. }
  390. return this.ignoredGearIds = updatedIgnoredGearIds;
  391. };
  392. Chain.prototype.toPolygon = function(segments) {
  393. var polygon, segment, _i, _len;
  394. if (segments == null) {
  395. segments = this.segments;
  396. }
  397. polygon = [];
  398. for (_i = 0, _len = segments.length; _i < _len; _i++) {
  399. segment = segments[_i];
  400. if (segment instanceof LineSegment) {
  401. polygon.push(segment.start);
  402. } else {
  403. polygon.push(segment.findPoint(0));
  404. polygon.push(segment.findPoint(0.5 * segment.getLength()));
  405. }
  406. }
  407. return polygon;
  408. };
  409. Chain.prototype.update = function(board, gears) {
  410. var acknowledgedGears, arcEnd, arcSegment, arcStart, chainPolygon, direction, g1, g2, g3, gear, gearId, i, id, intersection, j, k, lineSegment, lineSegment1, lineSegment2, middleSegment, numberOfGears, numberOfSegments, p0, p1, p2, path, replacementGears, s1, s2, tangentLine, tangentPointG1, tangentPointG3, tangentSideG1, tangentSideG3, updatedAcknowledgedGears, updatedIgnoredGearIds, updatedInnerGearIds, updatedPoints, updatedSegments, _i, _j, _k, _l, _ref, _ref1, _ref2;
  411. if (gears == null) {
  412. gears = board.getGearsWithIds(this.supportingGearIds);
  413. }
  414. if (gears.length < 2) {
  415. return false;
  416. }
  417. if (this.containsSuccessiveOverlappingGears(gears)) {
  418. return false;
  419. }
  420. updatedIgnoredGearIds = this.findIgnoredGearIdsInTightenedChain(board);
  421. acknowledgedGears = board.getAcknowledgedGears(updatedIgnoredGearIds);
  422. i = 0;
  423. while (i < (numberOfGears = gears.length)) {
  424. j = (i + 1) % numberOfGears;
  425. k = (i + 2) % numberOfGears;
  426. g1 = gears[i];
  427. g2 = gears[j];
  428. g3 = gears[k];
  429. lineSegment1 = Util.findTangentLine(g1, g2, this.innerGearIds, this.direction);
  430. lineSegment2 = Util.findTangentLine(g2, g3, this.innerGearIds, this.direction);
  431. intersection = lineSegment1.findIntersection(lineSegment2);
  432. if (intersection != null) {
  433. tangentSideG1 = this.findReverseChainTangentSide(g1);
  434. tangentPointG1 = Util.findGearTangentPoints(intersection, g1)[tangentSideG1];
  435. tangentSideG3 = this.findChainTangentSide(g3);
  436. tangentPointG3 = Util.findGearTangentPoints(intersection, g3)[tangentSideG3];
  437. path = [tangentPointG1, intersection, tangentPointG3];
  438. replacementGears = this.findSupportingGearsOnPath(acknowledgedGears, g1, path, 0, false);
  439. if (__indexOf.call(replacementGears, g2) >= 0) {
  440. return false;
  441. }
  442. gears.splice.apply(gears, [j, 1].concat(replacementGears));
  443. this.removeRepeatedGears(gears);
  444. return this.update(board, gears);
  445. }
  446. gear = Util.findNearestIntersectingGear(acknowledgedGears, lineSegment1, Util.makeSet(g1.id, g2.id));
  447. if (gear != null) {
  448. gears.splice(j, 0, gear);
  449. if (this.containsSuccessiveOverlappingGears(gears)) {
  450. return false;
  451. }
  452. }
  453. i++;
  454. }
  455. updatedPoints = [];
  456. for (i = _i = 0; 0 <= numberOfGears ? _i < numberOfGears : _i > numberOfGears; i = 0 <= numberOfGears ? ++_i : --_i) {
  457. j = (i + 1) % numberOfGears;
  458. g1 = gears[i];
  459. g2 = gears[j];
  460. tangentLine = Util.findTangentLine(g1, g2, this.innerGearIds, this.direction);
  461. updatedPoints.push(tangentLine.start, tangentLine.end);
  462. }
  463. updatedSegments = [];
  464. for (i = _j = 0; 0 <= numberOfGears ? _j < numberOfGears : _j > numberOfGears; i = 0 <= numberOfGears ? ++_j : --_j) {
  465. p0 = updatedPoints[2 * i];
  466. p1 = updatedPoints[2 * i + 1];
  467. p2 = updatedPoints[2 * ((i + 1) % numberOfGears)];
  468. gear = gears[(i + 1) % numberOfGears];
  469. lineSegment = new LineSegment(p0, p1);
  470. arcStart = Math.atan2(p1.y - gear.location.y, p1.x - gear.location.x);
  471. arcEnd = Math.atan2(p2.y - gear.location.y, p2.x - gear.location.x);
  472. direction = (this.direction === Util.Direction.CLOCKWISE) === (gear.id in this.innerGearIds) ? Util.Direction.CLOCKWISE : Util.Direction.COUNTER_CLOCKWISE;
  473. arcSegment = new ArcSegment(gear.location, gear.pitchRadius, arcStart, arcEnd, direction);
  474. updatedSegments.push(lineSegment, arcSegment);
  475. }
  476. numberOfSegments = updatedSegments.length;
  477. for (i = _k = 0, _ref = numberOfSegments - 2; 0 <= _ref ? _k < _ref : _k > _ref; i = 0 <= _ref ? ++_k : --_k) {
  478. for (j = _l = _ref1 = i + 2; _ref1 <= numberOfSegments ? _l < numberOfSegments : _l > numberOfSegments; j = _ref1 <= numberOfSegments ? ++_l : --_l) {
  479. if (i !== 0 || j !== numberOfSegments - 1) {
  480. s1 = updatedSegments[i];
  481. s2 = updatedSegments[j];
  482. if (s1.distanceToSegment(s2) < Chain.WIDTH) {
  483. if ((i + 2) === j) {
  484. middleSegment = updatedSegments[i + 1];
  485. if ((middleSegment instanceof ArcSegment) && (middleSegment.getLength() < 2 * Chain.WIDTH)) {
  486. continue;
  487. }
  488. }
  489. if (((j + 2) % numberOfSegments) === i) {
  490. middleSegment = updatedSegments[(j + 1) % numberOfSegments];
  491. if ((middleSegment instanceof ArcSegment) && (middleSegment.getLength() < 2 * Chain.WIDTH)) {
  492. continue;
  493. }
  494. }
  495. return false;
  496. }
  497. }
  498. }
  499. }
  500. updatedIgnoredGearIds = this.findIgnoredGearIdsInTightenedChain(board);
  501. updatedAcknowledgedGears = board.getAcknowledgedGears(updatedIgnoredGearIds);
  502. chainPolygon = this.toPolygon(updatedSegments);
  503. updatedInnerGearIds = {};
  504. for (id in updatedAcknowledgedGears) {
  505. if (!__hasProp.call(updatedAcknowledgedGears, id)) continue;
  506. gear = updatedAcknowledgedGears[id];
  507. if (Util.isPointInsidePolygon(gear.location, chainPolygon)) {
  508. updatedInnerGearIds[id] = true;
  509. }
  510. }
  511. _ref2 = this.innerGearIds;
  512. for (gearId in _ref2) {
  513. if (!__hasProp.call(_ref2, gearId)) continue;
  514. if (!(gearId in updatedInnerGearIds) && (__indexOf.call(this.supportingGearIds, gearId) >= 0)) {
  515. return false;
  516. }
  517. }
  518. this.points = updatedPoints;
  519. this.segments = updatedSegments;
  520. this.ignoredGearIds = updatedIgnoredGearIds;
  521. this.innerGearIds = updatedInnerGearIds;
  522. this.supportingGearIds = (function() {
  523. var _len, _m, _results;
  524. _results = [];
  525. for (_m = 0, _len = gears.length; _m < _len; _m++) {
  526. gear = gears[_m];
  527. _results.push(gear.id);
  528. }
  529. return _results;
  530. })();
  531. return true;
  532. };
  533. Chain.prototype.tighten = function(board) {
  534. var acknowledgedGears, gear;
  535. this.ignoredGearIds = this.findIgnoredGearIds(board);
  536. acknowledgedGears = board.getAcknowledgedGears(this.ignoredGearIds);
  537. this.innerGearIds = Util.makeSetFromList((function() {
  538. var _i, _len, _ref, _results;
  539. _ref = Util.findGearsInsidePolygon(this.points, acknowledgedGears);
  540. _results = [];
  541. for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  542. gear = _ref[_i];
  543. _results.push(gear.id);
  544. }
  545. return _results;
  546. }).call(this));
  547. if (Object.keys(this.innerGearIds).length < 2) {
  548. return false;
  549. }
  550. this.direction = Util.findDirection(this.points);
  551. this.supportingGearIds = this.findSupportingGearIds(acknowledgedGears);
  552. return this.update(board);
  553. };
  554. Chain.prototype.clone = function() {
  555. var copy;
  556. copy = new Chain(this.points, this.id, this.group, this.level, Util.clone(this.connections));
  557. copy.segments = Util.clone(this.segments);
  558. copy.ignoredGearIds = Util.clone(this.ignoredGearIds);
  559. copy.innerGearIds = Util.clone(this.innerGearIds);
  560. copy.direction = this.direction;
  561. copy.supportingGearIds = Util.clone(this.supportingGearIds);
  562. return copy;
  563. };
  564. Chain.fromObject = function(obj) {
  565. var chain, createSegment, p, points, segment;
  566. createSegment = function(obj) {
  567. if (obj.center != null) {
  568. return ArcSegment.fromObject(obj);
  569. } else {
  570. return LineSegment.fromObject(obj);
  571. }
  572. };
  573. points = (function() {
  574. var _i, _len, _ref, _results;
  575. _ref = obj.points;
  576. _results = [];
  577. for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  578. p = _ref[_i];
  579. _results.push(new Point(p.x, p.y));
  580. }
  581. return _results;
  582. })();
  583. chain = new Chain(points, obj.id, obj.group, obj.level, obj.connections);
  584. chain.segments = (function() {
  585. var _i, _len, _ref, _results;
  586. _ref = obj.segments;
  587. _results = [];
  588. for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  589. segment = _ref[_i];
  590. _results.push(createSegment(segment));
  591. }
  592. return _results;
  593. })();
  594. chain.ignoredGearIds = obj.ignoredGearIds;
  595. chain.innerGearIds = obj.innerGearIds;
  596. chain.direction = obj.direction;
  597. chain.supportingGearIds = obj.supportingGearIds;
  598. return chain;
  599. };
  600. return Chain;
  601. })();
  602. window.gearsketch.model.Chain = Chain;
  603. Board = (function() {
  604. var AXIS_RADIUS, ConnectionType, EPSILON, MIN_STACKED_GEARS_TEETH_DIFFERENCE, MODULE, SNAPPING_DISTANCE;
  605. MODULE = Util.MODULE;
  606. AXIS_RADIUS = Util.AXIS_RADIUS;
  607. MIN_STACKED_GEARS_TEETH_DIFFERENCE = Util.MIN_STACKED_GEARS_TEETH_DIFFERENCE;
  608. SNAPPING_DISTANCE = Util.SNAPPING_DISTANCE;
  609. EPSILON = Util.EPSILON;
  610. ConnectionType = {
  611. ANY: "any",
  612. MESHING: "meshing",
  613. AXIS: "axis",
  614. CHAIN_INSIDE: "chain_inside",
  615. CHAIN_OUTSIDE: "chain_outside"
  616. };
  617. function Board(gears, chains) {
  618. this.gears = gears != null ? gears : {};
  619. this.chains = chains != null ? chains : {};
  620. }
  621. Board.prototype.restore = function(board) {
  622. var gear, id, _ref;
  623. _ref = this.gears;
  624. for (id in _ref) {
  625. if (!__hasProp.call(_ref, id)) continue;
  626. gear = _ref[id];
  627. gear.restore(board.gears[id]);
  628. }
  629. return this.chains = board.chains;
  630. };
  631. Board.prototype.restoreAfterDemo = function(board) {
  632. this.gears = board.gears;
  633. return this.chains = board.chains;
  634. };
  635. Board.prototype.clear = function() {
  636. this.gears = {};
  637. return this.chains = {};
  638. };
  639. Board.prototype.getNextGroup = function() {
  640. var gear, id, nextGroup, _ref;
  641. nextGroup = 0;
  642. _ref = this.gears;
  643. for (id in _ref) {
  644. if (!__hasProp.call(_ref, id)) continue;
  645. gear = _ref[id];
  646. nextGroup = Math.max(nextGroup, gear.group + 1);
  647. }
  648. return nextGroup;
  649. };
  650. Board.prototype.getGears = function() {
  651. return this.gears;
  652. };
  653. Board.prototype.getGearList = function() {
  654. var gear, id, _ref, _results;
  655. _ref = this.gears;
  656. _results = [];
  657. for (id in _ref) {
  658. if (!__hasProp.call(_ref, id)) continue;
  659. gear = _ref[id];
  660. _results.push(gear);
  661. }
  662. return _results;
  663. };
  664. Board.prototype.getAcknowledgedGears = function(ignoredGearIds) {
  665. var acknowledgedGears, gear, id, _ref;
  666. acknowledgedGears = {};
  667. _ref = this.gears;
  668. for (id in _ref) {
  669. if (!__hasProp.call(_ref, id)) continue;
  670. gear = _ref[id];
  671. if (!(id in ignoredGearIds)) {
  672. acknowledgedGears[id] = gear;
  673. }
  674. }
  675. return acknowledgedGears;
  676. };
  677. Board.prototype.getLevelScore = function(gear) {
  678. return 1000 * gear.group + gear.level;
  679. };
  680. Board.prototype.getGearsSortedByGroupAndLevel = function(gears) {
  681. var _this = this;
  682. if (gears == null) {
  683. gears = this.getGearList();
  684. }
  685. return gears.sort(function(g1, g2) {
  686. return _this.getLevelScore(g1) - _this.getLevelScore(g2);
  687. });
  688. };
  689. Board.prototype.removeConnection = function(turningObject1, turningObject2) {
  690. delete turningObject1.connections[turningObject2.id];
  691. return delete turningObject2.connections[turningObject1.id];
  692. };
  693. Board.prototype.removeAllConnections = function(turningObject) {
  694. var neighbor, neighborId, _ref;
  695. _ref = turningObject.connections;
  696. for (neighborId in _ref) {
  697. if (!__hasProp.call(_ref, neighborId)) continue;
  698. neighbor = this.getTurningObjects()[neighborId];
  699. this.removeConnection(turningObject, neighbor);
  700. }
  701. return this.updateGroupsAndLevels();
  702. };
  703. Board.prototype.findNearestAxis = function(gear) {
  704. var candidate, distance, id, nearestAxis, shortestDistance, _ref;
  705. nearestAxis = null;
  706. shortestDistance = Number.MAX_VALUE;
  707. _ref = this.gears;
  708. for (id in _ref) {
  709. if (!__hasProp.call(_ref, id)) continue;
  710. candidate = _ref[id];
  711. if (candidate !== gear) {
  712. distance = gear.location.distance(candidate.location);
  713. if (!nearestAxis || distance < (shortestDistance - EPSILON) || (distance < (shortestDistance + EPSILON) && candidate.numberOfTeeth < nearestAxis.numberOfTeeth)) {
  714. nearestAxis = candidate;
  715. shortestDistance = distance;
  716. }
  717. }
  718. }
  719. return nearestAxis;
  720. };
  721. Board.prototype.updateGroupsAndLevelsFrom = function(turningObjectId, group, level, updatedGroups, updatedLevels) {
  722. var connectionType, connections, gear, neighbor, neighborId, sameLevelConnectionTypes, turningObject, _results;
  723. turningObject = this.getTurningObjects()[turningObjectId];
  724. updatedGroups[turningObjectId] = group;
  725. updatedLevels[turningObjectId] = level;
  726. connections = turningObject.connections;
  727. sameLevelConnectionTypes = [ConnectionType.MESHING, ConnectionType.CHAIN_INSIDE, ConnectionType.CHAIN_OUTSIDE];
  728. _results = [];
  729. for (neighborId in connections) {
  730. if (!__hasProp.call(connections, neighborId)) continue;
  731. connectionType = connections[neighborId];
  732. if (!(neighborId in updatedGroups)) {
  733. if (__indexOf.call(sameLevelConnectionTypes, connectionType) >= 0) {
  734. _results.push(this.updateGroupsAndLevelsFrom(neighborId, group, level, updatedGroups, updatedLevels));
  735. } else {
  736. gear = this.gears[turningObjectId];
  737. neighbor = this.gears[neighborId];
  738. if (gear.numberOfTeeth > neighbor.numberOfTeeth) {
  739. _results.push(this.updateGroupsAndLevelsFrom(neighborId, group, level + 1, updatedGroups, updatedLevels));
  740. } else {
  741. _results.push(this.updateGroupsAndLevelsFrom(neighborId, group, level - 1, updatedGroups, updatedLevels));
  742. }
  743. }
  744. } else {
  745. _results.push(void 0);
  746. }
  747. }
  748. return _results;
  749. };
  750. Board.prototype.updateGroupsAndLevels = function() {
  751. var group, id, turningObject, updatedGroups, updatedLevels, _ref, _ref1;
  752. updatedGroups = {};
  753. updatedLevels = {};
  754. group = 0;
  755. _ref = this.gears;
  756. for (id in _ref) {
  757. if (!__hasProp.call(_ref, id)) continue;
  758. if (!(id in updatedGroups)) {
  759. this.updateGroupsAndLevelsFrom(id, group, 0, updatedGroups, updatedLevels);
  760. group++;
  761. }
  762. }
  763. _ref1 = this.getTurningObjects();
  764. for (id in _ref1) {
  765. if (!__hasProp.call(_ref1, id)) continue;
  766. turningObject = _ref1[id];
  767. turningObject.group = updatedGroups[id];
  768. turningObject.level = updatedLevels[id];
  769. }
  770. return null;
  771. };
  772. Board.prototype.addConnection = function(turningObject1, turningObject2, connectionType) {
  773. turningObject1.connections[turningObject2.id] = connectionType;
  774. turningObject2.connections[turningObject1.id] = connectionType;
  775. return this.updateGroupsAndLevels();
  776. };
  777. Board.prototype.findMeshingNeighbors = function(gear) {
  778. var candidate, candidateId, meshingNeighbors, _ref;
  779. meshingNeighbors = [];
  780. _ref = this.gears;
  781. for (candidateId in _ref) {
  782. if (!__hasProp.call(_ref, candidateId)) continue;
  783. candidate = _ref[candidateId];
  784. if (candidate !== gear && gear.edgeDistance(candidate) < EPSILON) {
  785. if ((candidate.group !== gear.group) || (candidate.level === gear.level)) {
  786. meshingNeighbors.push(candidate);
  787. }
  788. }
  789. }
  790. return meshingNeighbors;
  791. };
  792. Board.prototype.findRelativeAlignment = function(gear1, gear2) {
  793. var angle1, angle2, p1, p2, phase1, phase2, phaseSum, r1, r2, shift1, shift2, toothAngle1, toothAngle2;
  794. p1 = gear1.location;
  795. r1 = gear1.rotation;
  796. p2 = gear2.location;
  797. r2 = gear2.rotation;
  798. angle1 = Math.atan2(p2.y - p1.y, p2.x - p1.x);
  799. angle2 = angle1 + Math.PI;
  800. shift1 = Util.mod(angle1 - r1, 2 * Math.PI);
  801. shift2 = Util.mod(angle2 - r2, 2 * Math.PI);
  802. toothAngle1 = (2 * Math.PI) / gear1.numberOfTeeth;
  803. toothAngle2 = (2 * Math.PI) / gear2.numberOfTeeth;
  804. phase1 = (shift1 % toothAngle1) / toothAngle1;
  805. phase2 = (shift2 % toothAngle2) / toothAngle2;
  806. phaseSum = (phase1 + phase2) % 1;
  807. return (phaseSum - 0.25) * toothAngle1;
  808. };
  809. Board.prototype.alignGearTeeth = function(rotatingGear, meshingGear) {
  810. return rotatingGear.rotation += this.findRelativeAlignment(rotatingGear, meshingGear);
  811. };
  812. Board.prototype.areMeshingGearsAligned = function(gear1, gear2) {
  813. return Math.abs(this.findRelativeAlignment(gear1, gear2)) < EPSILON;
  814. };
  815. Board.prototype.rotateTurningObjectsFrom = function(turningObject, angle, rotatedTurningObjectIds) {
  816. var connectionType, neighbor, neighborId, ratio, _ref, _results;
  817. if (!(turningObject.id in rotatedTurningObjectIds)) {
  818. turningObject.rotation = Util.mod(turningObject.rotation + angle, 2 * Math.PI);
  819. rotatedTurningObjectIds[turningObject.id] = true;
  820. }
  821. _ref = turningObject.connections;
  822. _results = [];
  823. for (neighborId in _ref) {
  824. if (!__hasProp.call(_ref, neighborId)) continue;
  825. connectionType = _ref[neighborId];
  826. neighbor = this.getTurningObjects()[neighborId];
  827. if (!(neighborId in rotatedTurningObjectIds)) {
  828. ratio = this.calculateRatio(turningObject, neighbor, connectionType);
  829. _results.push(this.rotateTurningObjectsFrom(neighbor, angle * ratio, rotatedTurningObjectIds));
  830. } else {
  831. _results.push(void 0);
  832. }
  833. }
  834. return _results;
  835. };
  836. Board.prototype.alignMeshingGears = function(gear) {
  837. var angle, neighbor, neighbors, r, rotatedGearIds, _i, _len, _results;
  838. rotatedGearIds = {};
  839. rotatedGearIds[gear.id] = true;
  840. neighbors = this.findMeshingNeighbors(gear);
  841. _results = [];
  842. for (_i = 0, _len = neighbors.length; _i < _len; _i++) {
  843. neighbor = neighbors[_i];
  844. this.addConnection(gear, neighbor, ConnectionType.MESHING);
  845. r = neighbor.rotation;
  846. this.alignGearTeeth(neighbor, gear);
  847. angle = neighbor.rotation - r;
  848. rotatedGearIds[neighbor.id] = true;
  849. _results.push(this.rotateTurningObjectsFrom(neighbor, angle, rotatedGearIds));
  850. }
  851. return _results;
  852. };
  853. Board.prototype.connectToAxis = function(upperGear, lowerGear) {
  854. this.addConnection(upperGear, lowerGear, ConnectionType.AXIS);
  855. upperGear.location = lowerGear.location.clone();
  856. upperGear.rotation = lowerGear.rotation;
  857. return this.alignMeshingGears(upperGear);
  858. };
  859. Board.prototype.findNearestNeighbor = function(gear, gearIdsToIgnore) {
  860. var edgeDistance, nearestNeighbor, neighbor, neighborId, shortestEdgeDistance, _ref;
  861. if (gearIdsToIgnore == null) {
  862. gearIdsToIgnore = {};
  863. }
  864. nearestNeighbor = null;
  865. shortestEdgeDistance = Number.MAX_VALUE;
  866. _ref = this.gears;
  867. for (neighborId in _ref) {
  868. if (!__hasProp.call(_ref, neighborId)) continue;
  869. neighbor = _ref[neighborId];
  870. if (neighbor !== gear && !(neighborId in gearIdsToIgnore)) {
  871. edgeDistance = gear.edgeDistance(neighbor);
  872. if (edgeDistance < shortestEdgeDistance) {
  873. nearestNeighbor = neighbor;
  874. shortestEdgeDistance = edgeDistance;
  875. }
  876. }
  877. }
  878. return nearestNeighbor;
  879. };
  880. Board.prototype.connectToOneMeshingGear = function(gear, meshingGear) {
  881. var angle, delta;
  882. delta = gear.location.minus(meshingGear.location);
  883. angle = Math.atan2(delta.y, delta.x);
  884. gear.location = meshingGear.location.plus(Point.polar(angle, gear.pitchRadius + meshingGear.pitchRadius));
  885. this.alignGearTeeth(gear, meshingGear);
  886. return this.addConnection(gear, meshingGear, ConnectionType.MESHING);
  887. };
  888. Board.prototype.connectToTwoMeshingGears = function(gear, meshingGear1, meshingGear2) {
  889. var a, d, h, p0, p1, p2, p3_1, p3_2, p3x1, p3x2, p3y1, p3y2, r0, r1;
  890. p0 = meshingGear1.location;
  891. p1 = meshingGear2.location;
  892. r0 = meshingGear1.pitchRadius + gear.pitchRadius;
  893. r1 = meshingGear2.pitchRadius + gear.pitchRadius;
  894. d = p0.distance(p1);
  895. if (r0 + r1 < d || p0.distance(p1) < EPSILON) {
  896. if (gear.edgeDistance(meshingGear1) < gear.edgeDistance(meshingGear2)) {
  897. this.connectToOneMeshingGear(gear, meshingGear1);
  898. return;
  899. } else {
  900. this.connectToOneMeshingGear(gear, meshingGear2);
  901. return;
  902. }
  903. }
  904. a = (r0 * r0 - r1 * r1 + d * d) / (2 * d);
  905. h = Math.sqrt(r0 * r0 - a * a);
  906. p2 = p0.plus(p1.minus(p0).times(a / d));
  907. p3x1 = p2.x + h * (p1.y - p0.y) / d;
  908. p3y1 = p2.y - h * (p1.x - p0.x) / d;
  909. p3x2 = p2.x - h * (p1.y - p0.y) / d;
  910. p3y2 = p2.y + h * (p1.x - p0.x) / d;
  911. p3_1 = new Point(p3x1, p3y1);
  912. p3_2 = new Point(p3x2, p3y2);
  913. if (gear.location.distance(p3_1) < gear.location.distance(p3_2)) {
  914. gear.location = p3_1;
  915. } else {
  916. gear.location = p3_2;
  917. }
  918. return this.alignMeshingGears(gear);
  919. };
  920. Board.prototype.doChainsCrossNonSupportingGears = function() {
  921. var chain, id, _ref;
  922. _ref = this.chains;
  923. for (id in _ref) {
  924. if (!__hasProp.call(_ref, id)) continue;
  925. chain = _ref[id];
  926. if (chain.crossesNonSupportingGears(this)) {
  927. return true;
  928. }
  929. }
  930. return false;
  931. };
  932. Board.prototype.doChainsCrossEachOther = function(c1, c2) {
  933. if ((c1.group !== c2.group) || (c1.level === c2.level)) {
  934. if (c1.distanceToChain(c2) < Chain.WIDTH) {
  935. return true;
  936. }
  937. }
  938. return false;
  939. };
  940. Board.prototype.doesChainCrossAnyOtherChain = function(chain) {
  941. var chain2, id2, _ref;
  942. _ref = this.chains;
  943. for (id2 in _ref) {
  944. if (!__hasProp.call(_ref, id2)) continue;
  945. chain2 = _ref[id2];
  946. if (chain !== chain2) {
  947. if (this.doChainsCrossEachOther(chain, chain2)) {
  948. return true;
  949. }
  950. }
  951. }
  952. return false;
  953. };
  954. Board.prototype.doAnyChainsCrossEachOther = function() {
  955. var c1, c2, chain, chainList, i, id, j, numberOfChains, _i, _j, _ref, _ref1;
  956. chainList = (function() {
  957. var _ref, _results;
  958. _ref = this.chains;
  959. _results = [];
  960. for (id in _ref) {
  961. if (!__hasProp.call(_ref, id)) continue;
  962. chain = _ref[id];
  963. _results.push(chain);
  964. }
  965. return _results;
  966. }).call(this);
  967. numberOfChains = chainList.length;
  968. if (numberOfChains < 2) {
  969. return false;
  970. }
  971. for (i = _i = 0, _ref = numberOfChains - 1; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
  972. for (j = _j = _ref1 = i + 1; _ref1 <= numberOfChains ? _j < numberOfChains : _j > numberOfChains; j = _ref1 <= numberOfChains ? ++_j : --_j) {
  973. c1 = chainList[i];
  974. c2 = chainList[j];
  975. if (this.doChainsCrossEachOther(c1, c2)) {
  976. return true;
  977. }
  978. }
  979. }
  980. return false;
  981. };
  982. Board.prototype.areAllMeshingGearsAligned = function() {
  983. var g1, g2, gears, i, j, numberOfGears, _i, _j, _ref, _ref1;
  984. gears = this.getGearList();
  985. numberOfGears = gears.length;
  986. if (numberOfGears < 2) {
  987. return true;
  988. }
  989. for (i = _i = 0, _ref = numberOfGears - 1; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
  990. for (j = _j = _ref1 = i + 1; _ref1 <= numberOfGears ? _j < numberOfGears : _j > numberOfGears; j = _ref1 <= numberOfGears ? ++_j : --_j) {
  991. g1 = gears[i];
  992. g2 = gears[j];
  993. if (g1.connections[g2.id] === ConnectionType.MESHING) {
  994. if (!this.areMeshingGearsAligned(g1, g2)) {
  995. return false;
  996. }
  997. }
  998. }
  999. }
  1000. return true;
  1001. };
  1002. Board.prototype.calculateRatio = function(turningObject1, turningObject2, connectionType) {
  1003. if (connectionType === ConnectionType.AXIS) {
  1004. return 1;
  1005. } else if ((connectionType === ConnectionType.MESHING) || (connectionType === ConnectionType.CHAIN_OUTSIDE)) {
  1006. return -turningObject1.getCircumference() / turningObject2.getCircumference();
  1007. } else {
  1008. return turningObject1.getCircumference() / turningObject2.getCircumference();
  1009. }
  1010. };
  1011. Board.prototype.calculatePathRatio = function(path) {
  1012. var connectionType, i, pathLength, ratio, turningObject1, turningObject2, _i, _ref;
  1013. ratio = 1;
  1014. pathLength = path.length;
  1015. for (i = _i = 0, _ref = pathLength - 1; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
  1016. turningObject1 = path[i];
  1017. turningObject2 = path[i + 1];
  1018. connectionType = turningObject1.connections[turningObject2.id];
  1019. ratio *= this.calculateRatio(turningObject1, turningObject2, connectionType);
  1020. }
  1021. return ratio;
  1022. };
  1023. Board.prototype.areConnectionRatiosConsistent = function() {
  1024. var path, pathName, paths, ratio, ratios, _i, _len;
  1025. ratios = {};
  1026. paths = Util.findAllSimplePathsBetweenNeighbors(this.getTurningObjects());
  1027. for (_i = 0, _len = paths.length; _i < _len; _i++) {
  1028. path = paths[_i];
  1029. pathName = "" + path[0].id + "-" + path[path.length - 1].id;
  1030. ratio = this.calculatePathRatio(path);
  1031. if (ratios[pathName] == null) {
  1032. ratios[pathName] = ratio;
  1033. } else {
  1034. if (Math.abs(ratios[pathName] - ratio) > EPSILON) {
  1035. return false;
  1036. }
  1037. }
  1038. }
  1039. return true;
  1040. };
  1041. Board.prototype.isBoardValid = function() {
  1042. var axisDistance, combinedOuterRadius, gear1, gear2, group1, group2, id1, id2, level1, level2, maxOuterRadius, _ref, _ref1;
  1043. _ref = this.gears;
  1044. for (id1 in _ref) {
  1045. if (!__hasProp.call(_ref, id1)) continue;
  1046. gear1 = _ref[id1];
  1047. group1 = gear1.group;
  1048. level1 = gear1.level;
  1049. _ref1 = this.gears;
  1050. for (id2 in _ref1) {
  1051. if (!__hasProp.call(_ref1, id2)) continue;
  1052. gear2 = _ref1[id2];
  1053. if (gear1 !== gear2) {
  1054. group2 = gear2.group;
  1055. level2 = gear2.level;
  1056. axisDistance = gear1.location.distance(gear2.location);
  1057. maxOuterRadius = Math.max(gear1.outerRadius, gear2.outerRadius);
  1058. combinedOuterRadius = gear1.outerRadius + gear2.outerRadius;
  1059. if (axisDistance < EPSILON) {
  1060. if ((group1 !== group2) || (level1 === level2)) {
  1061. return false;
  1062. }
  1063. } else if (group1 === group2 && level1 === level2 && (gear1.connections[gear2.id] == null)) {
  1064. if (axisDistance < combinedOuterRadius) {
  1065. return false;
  1066. }
  1067. } else if (axisDistance < maxOuterRadius + AXIS_RADIUS) {
  1068. return false;
  1069. }
  1070. }
  1071. }
  1072. }
  1073. return !this.doChainsCrossNonSupportingGears() && !this.doAnyChainsCrossEachOther() && this.areAllMeshingGearsAligned() && this.areConnectionRatiosConsistent();
  1074. };
  1075. Board.prototype.placeGear = function(gear, location) {
  1076. var chain, id, nearestAxis, neighbor1, neighbor2, oldBoard, _ref;
  1077. oldBoard = this.clone();
  1078. this.removeAllConnections(gear);
  1079. gear.location = location.clone();
  1080. nearestAxis = this.findNearestAxis(gear);
  1081. if (nearestAxis && gear.location.distance(nearestAxis.location) < SNAPPING_DISTANCE && nearestAxis.numberOfTeeth - gear.numberOfTeeth > MIN_STACKED_GEARS_TEETH_DIFFERENCE) {
  1082. this.connectToAxis(gear, nearestAxis);
  1083. } else {
  1084. neighbor1 = this.findNearestNeighbor(gear);
  1085. if (neighbor1 && gear.edgeDistance(neighbor1) < SNAPPING_DISTANCE) {
  1086. neighbor2 = this.findNearestNeighbor(gear, Util.makeSet(neighbor1.id));
  1087. if (neighbor2 && gear.edgeDistance(neighbor2) < SNAPPING_DISTANCE) {
  1088. this.connectToTwoMeshingGears(gear, neighbor1, neighbor2);
  1089. } else {
  1090. this.connectToOneMeshingGear(gear, neighbor1);
  1091. }
  1092. }
  1093. }
  1094. _ref = this.chains;
  1095. for (id in _ref) {
  1096. if (!__hasProp.call(_ref, id)) continue;
  1097. chain = _ref[id];
  1098. if (chain.update(this)) {
  1099. this.updateChainConnections(chain);
  1100. } else {
  1101. this.restore(oldBoard);
  1102. return false;
  1103. }
  1104. }
  1105. if (this.isBoardValid()) {
  1106. return true;
  1107. } else {
  1108. this.restore(oldBoard);
  1109. return false;
  1110. }
  1111. };
  1112. Board.prototype.addGearToChains = function(gear) {
  1113. var chain, id, _ref, _results;
  1114. _ref = this.chains;
  1115. _results = [];
  1116. for (id in _ref) {
  1117. if (!__hasProp.call(_ref, id)) continue;
  1118. chain = _ref[id];
  1119. if (Util.isPointInsidePolygon(gear.location, chain.toPolygon())) {
  1120. _results.push(chain.innerGearIds[gear.id] = true);
  1121. } else {
  1122. _results.push(void 0);
  1123. }
  1124. }
  1125. return _results;
  1126. };
  1127. Board.prototype.removeGearFromChains = function(gear) {
  1128. var chain, id, _ref, _results;
  1129. _ref = this.chains;
  1130. _results = [];
  1131. for (id in _ref) {
  1132. if (!__hasProp.call(_ref, id)) continue;
  1133. chain = _ref[id];
  1134. if (!chain.removeGear(gear, this) || this.doesChainCrossAnyOtherChain(chain)) {
  1135. _results.push(this.removeChain(chain));
  1136. } else {
  1137. _results.push(this.updateChainConnections(chain));
  1138. }
  1139. }
  1140. return _results;
  1141. };
  1142. Board.prototype.addGear = function(gear) {
  1143. var oldBoard;
  1144. oldBoard = this.clone();
  1145. gear.group = this.getNextGroup();
  1146. this.gears[gear.id] = gear;
  1147. this.addGearToChains(gear);
  1148. if (!this.placeGear(gear, gear.location)) {
  1149. this.removeGear(gear);
  1150. this.restore(oldBoard);
  1151. return false;
  1152. } else {
  1153. return true;
  1154. }
  1155. };
  1156. Board.prototype.removeGear = function(gear) {
  1157. this.removeAllConnections(gear);
  1158. delete this.gears[gear.id];
  1159. return this.removeGearFromChains(gear);
  1160. };
  1161. Board.prototype.getGearAt = function(location, candidates) {
  1162. var candidate, distance, gear, id;
  1163. if (candidates == null) {
  1164. candidates = this.gears;
  1165. }
  1166. gear = null;
  1167. for (id in candidates) {
  1168. if (!__hasProp.call(candidates, id)) continue;
  1169. candidate = candidates[id];
  1170. distance = location.distance(candidate.location);
  1171. if (distance < candidate.outerRadius) {
  1172. if (!gear || candidate.numberOfTeeth < gear.numberOfTeeth) {
  1173. gear = candidate;
  1174. }
  1175. }
  1176. }
  1177. return gear;
  1178. };
  1179. Board.prototype.isTopLevelGear = function(gear) {
  1180. var connectionType, id, _ref;
  1181. _ref = gear.connections;
  1182. for (id in _ref) {
  1183. if (!__hasProp.call(_ref, id)) continue;
  1184. connectionType = _ref[id];
  1185. if (connectionType === ConnectionType.AXIS && this.gears[id].level > gear.level) {
  1186. return false;
  1187. }
  1188. }
  1189. return true;
  1190. };
  1191. Board.prototype.getTopLevelGears = function() {
  1192. var gear, id, topLevelGears, _ref;
  1193. topLevelGears = {};
  1194. _ref = this.gears;
  1195. for (id in _ref) {
  1196. if (!__hasProp.call(_ref, id)) continue;
  1197. gear = _ref[id];
  1198. if (this.isTopLevelGear(gear)) {
  1199. topLevelGears[id] = gear;
  1200. }
  1201. }
  1202. return topLevelGears;
  1203. };
  1204. Board.prototype.getTopLevelGearAt = function(location) {
  1205. return this.getGearAt(location, this.getTopLevelGears());
  1206. };
  1207. Board.prototype.getGearWithId = function(id) {
  1208. return this.gears[id];
  1209. };
  1210. Board.prototype.getGearsWithIds = function(ids) {
  1211. var id, _i, _len, _results;
  1212. _results = [];
  1213. for (_i = 0, _len = ids.length; _i < _len; _i++) {
  1214. id = ids[_i];
  1215. _results.push(this.gears[id]);
  1216. }
  1217. return _results;
  1218. };
  1219. Board.prototype.rotateAllTurningObjects = function(delta) {
  1220. var angle, gear, id, _ref, _results;
  1221. _ref = this.gears;
  1222. _results = [];
  1223. for (id in _ref) {
  1224. if (!__hasProp.call(_ref, id)) continue;
  1225. gear = _ref[id];
  1226. if (gear.momentum) {
  1227. angle = gear.momentum * (delta / 1000);
  1228. _results.push(this.rotateTurningObjectsFrom(gear, angle, {}));
  1229. } else {
  1230. _results.push(void 0);
  1231. }
  1232. }
  1233. return _results;
  1234. };
  1235. Board.prototype.addChainConnections = function(chain) {
  1236. var gearId, _i, _len, _ref, _results;
  1237. _ref = chain.supportingGearIds;
  1238. _results = [];
  1239. for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  1240. gearId = _ref[_i];
  1241. if (gearId in chain.innerGearIds) {
  1242. _results.push(this.addConnection(chain, this.getGearWithId(gearId), ConnectionType.CHAIN_INSIDE));
  1243. } else {
  1244. _results.push(this.addConnection(chain, this.getGearWithId(gearId), ConnectionType.CHAIN_OUTSIDE));
  1245. }
  1246. }
  1247. return _results;
  1248. };
  1249. Board.prototype.updateChainConnections = function(chain) {
  1250. this.removeAllConnections(chain);
  1251. return this.addChainConnections(chain);
  1252. };
  1253. Board.prototype.addChain = function(chain) {
  1254. var oldBoard;
  1255. oldBoard = this.clone();
  1256. this.chains[chain.id] = chain;
  1257. if (chain.tighten(this)) {
  1258. this.chains[chain.id] = chain;
  1259. this.addChainConnections(chain);
  1260. } else {
  1261. this.restore(oldBoard);
  1262. return false;
  1263. }
  1264. if (this.isBoardValid()) {
  1265. return true;
  1266. } else {
  1267. this.restore(oldBoard);
  1268. return false;
  1269. }
  1270. };
  1271. Board.prototype.removeChain = function(chain) {
  1272. this.removeAllConnections(chain);
  1273. return delete this.chains[chain.id];
  1274. };
  1275. Board.prototype.getChains = function() {
  1276. return this.chains;
  1277. };
  1278. Board.prototype.getChainsInGroupOnLevel = function(group, level) {
  1279. var chain, id, _ref, _results;
  1280. _ref = this.chains;
  1281. _results = [];
  1282. for (id in _ref) {
  1283. if (!__hasProp.call(_ref, id)) continue;
  1284. chain = _ref[id];
  1285. if ((chain.group === group) && (chain.level === level)) {
  1286. _results.push(chain);
  1287. }
  1288. }
  1289. return _results;
  1290. };
  1291. Board.prototype.getTurningObjects = function() {
  1292. var chain, gear, id, turningObjects, _ref, _ref1;
  1293. turningObjects = {};
  1294. _ref = this.gears;
  1295. for (id in _ref) {
  1296. if (!__hasProp.call(_ref, id)) continue;
  1297. gear = _ref[id];
  1298. turningObjects[id] = gear;
  1299. }
  1300. _ref1 = this.chains;
  1301. for (id in _ref1) {
  1302. if (!__hasProp.call(_ref1, id)) continue;
  1303. chain = _ref1[id];
  1304. turningObjects[id] = chain;
  1305. }
  1306. return turningObjects;
  1307. };
  1308. Board.prototype.clone = function() {
  1309. return {
  1310. gears: Util.clone(this.gears),
  1311. chains: Util.clone(this.chains)
  1312. };
  1313. };
  1314. Board.fromObject = function(obj) {
  1315. var board, chain, gear, id, _ref, _ref1;
  1316. board = new Board();
  1317. _ref = obj.gears;
  1318. for (id in _ref) {
  1319. gear = _ref[id];
  1320. board.gears[id] = Gear.fromObject(gear);
  1321. }
  1322. _ref1 = obj.chains;
  1323. for (id in _ref1) {
  1324. chain = _ref1[id];
  1325. board.chains[id] = Chain.fromObject(chain);
  1326. }
  1327. return board;
  1328. };
  1329. return Board;
  1330. })();
  1331. window.gearsketch.model.Board = Board;
  1332. }).call(this);
  1333. /*
  1334. //@ sourceMappingURL=gearsketch_model.map
  1335. */