// Generated by CoffeeScript 1.6.3 (function() { "use strict"; var ArcSegment, Board, Chain, Gear, LineSegment, Point, Util, __hasProp = {}.hasOwnProperty, __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; }; Point = window.gearsketch.Point; ArcSegment = window.gearsketch.ArcSegment; LineSegment = window.gearsketch.LineSegment; Util = window.gearsketch.Util; window.gearsketch.model = {}; Gear = (function() { function Gear(location, rotation, numberOfTeeth, id, momentum, group, level, connections, hue) { this.location = location; this.rotation = rotation; this.numberOfTeeth = numberOfTeeth; this.id = id; this.momentum = momentum != null ? momentum : 0; this.group = group != null ? group : 0; this.level = level != null ? level : 0; this.connections = connections != null ? connections : {}; if (this.id == null) { this.id = Util.createUUID(); } this.pitchRadius = Util.MODULE * (0.5 * this.numberOfTeeth); this.innerRadius = Util.MODULE * (0.5 * this.numberOfTeeth - 1.25); this.outerRadius = Util.MODULE * (0.5 * this.numberOfTeeth + 1); this.hue = hue != null? hue : Math.floor(Math.random()*360); } Gear.prototype.getCircumference = function() { return 2 * Math.PI * this.pitchRadius; }; Gear.prototype.distanceToPoint = function(point) { return Math.max(0, this.location.distance(point) - this.pitchRadius); }; Gear.prototype.edgeDistance = function(gear) { var axisDistance; axisDistance = this.location.distance(gear.location); return Math.abs(axisDistance - this.pitchRadius - gear.pitchRadius); }; Gear.prototype.restore = function(gear) { this.location = gear.location; this.rotation = gear.rotation; this.momentum = gear.momentum; this.group = gear.group; this.level = gear.level; return this.connections = gear.connections; }; Gear.prototype.clone = function() { return new Gear(this.location.clone(), this.rotation, this.numberOfTeeth, this.id, this.momentum, this.group, this.level, Util.clone(this.connections), this.hue); }; Gear.fromObject = function(obj) { return new Gear(Point.fromObject(obj.location), obj.rotation, obj.numberOfTeeth, obj.id, obj.momentum, obj.group, obj.level, obj.connections, obj.hue); }; return Gear; })(); window.gearsketch.model.Gear = Gear; Chain = (function() { Chain.WIDTH = 8; Chain.prototype.points = []; Chain.prototype.segments = []; function Chain(stroke, id, group, level, connections) { this.id = id; this.group = group != null ? group : 0; this.level = level != null ? level : 0; this.connections = connections != null ? connections : {}; if (this.id == null) { this.id = Util.createUUID(); } this.points = Util.clone(stroke); this.rotation = 0; } Chain.prototype.getLength = function() { return this.segments.reduce((function(total, segment) { return total + segment.getLength(); }), 0); }; Chain.prototype.getCircumference = function() { return this.getLength(); }; Chain.prototype.getStartingPoint = function() { if (this.direction === Util.Direction.CLOCKWISE) { return this.rotation / (2 * Math.PI) * this.getLength(); } else { return -this.rotation / (2 * Math.PI) * this.getLength(); } }; Chain.prototype.findPointOnChain = function(distance) { var distanceToGo, length, segment, segmentLength, _i, _len, _ref; length = this.getLength(); distanceToGo = Util.mod(distance + this.getStartingPoint(), length); _ref = this.segments; for (_i = 0, _len = _ref.length; _i < _len; _i++) { segment = _ref[_i]; segmentLength = segment.getLength(); if (distanceToGo < segmentLength) { return segment.findPoint(distanceToGo); } else { distanceToGo -= segmentLength; } } return null; }; Chain.prototype.findPointsOnChain = function(numberOfPoints) { var delta, p, _i, _results; delta = this.getLength() / numberOfPoints; _results = []; for (p = _i = 0; 0 <= numberOfPoints ? _i < numberOfPoints : _i > numberOfPoints; p = 0 <= numberOfPoints ? ++_i : --_i) { _results.push(this.findPointOnChain(p * delta)); } return _results; }; Chain.prototype.distanceToPoint = function(point) { var segment; return Math.min.apply(null, (function() { var _i, _len, _ref, _results; _ref = this.segments; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { segment = _ref[_i]; _results.push(segment.distanceToPoint(point)); } return _results; }).call(this)); }; Chain.prototype.distanceToSegment = function(s) { var segment; return Math.min.apply(null, (function() { var _i, _len, _ref, _results; _ref = this.segments; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { segment = _ref[_i]; _results.push(segment.distanceToSegment(s)); } return _results; }).call(this)); }; Chain.prototype.distanceToChain = function(chain) { var segment; return Math.min.apply(null, (function() { var _i, _len, _ref, _results; _ref = this.segments; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { segment = _ref[_i]; _results.push(chain.distanceToSegment(segment)); } return _results; }).call(this)); }; Chain.prototype.intersectsPath = function(path) { var i, j, _i, _ref; for (i = _i = 0, _ref = path.length - 1; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { j = i + 1; if (this.distanceToSegment(new LineSegment(path[i], path[j])) === 0) { return true; } } return false; }; Chain.prototype.crossesNonSupportingGears = function(board) { var gear, id, _ref; _ref = board.getGears(); for (id in _ref) { if (!__hasProp.call(_ref, id)) continue; gear = _ref[id]; if (!(__indexOf.call(this.supportingGearIds, id) >= 0) && !(id in this.ignoredGearIds)) { if (this.distanceToPoint(gear.location) < gear.pitchRadius + Util.EPSILON) { return true; } } } return false; }; Chain.prototype.findPointOnSupportingGear = function(gearIndex, incoming) { if (incoming) { return this.points[Util.mod(2 * gearIndex - 1, this.points.length)]; } else { return this.points[2 * gearIndex]; } }; Chain.prototype.removeGear = function(gear, board) { var acknowledgedGears, afterIndex, beforeGear, beforeIndex, g, gears, index, numberOfGears, path, replacementGears; while ((index = this.supportingGearIds.indexOf(gear.id)) !== -1) { gears = board.getGearsWithIds(this.supportingGearIds); numberOfGears = gears.length; beforeIndex = Util.mod(index - 1, numberOfGears); beforeGear = gears[beforeIndex]; afterIndex = Util.mod(index + 1, numberOfGears); acknowledgedGears = board.getAcknowledgedGears(this.ignoredGearIds); path = [this.findPointOnSupportingGear(index, true), this.findPointOnSupportingGear(index, false), this.findPointOnSupportingGear(afterIndex, true)]; replacementGears = this.findSupportingGearsOnPath(acknowledgedGears, beforeGear, path, 0, false); gears.splice.apply(gears, [index, 1].concat(replacementGears)); this.removeRepeatedGears(gears); this.supportingGearIds = (function() { var _i, _len, _results; _results = []; for (_i = 0, _len = gears.length; _i < _len; _i++) { g = gears[_i]; _results.push(g.id); } return _results; })(); } return this.update(board); }; Chain.prototype.findChainTangentSide = function(gear) { if ((this.direction === Util.Direction.CLOCKWISE) === (gear.id in this.innerGearIds)) { return Util.Side.LEFT; } else { return Util.Side.RIGHT; } }; Chain.prototype.findReverseChainTangentSide = function(gear) { if (this.findChainTangentSide(gear) === Util.Side.LEFT) { return Util.Side.RIGHT; } else { return Util.Side.LEFT; } }; Chain.prototype.findFirstSupportingGearOnPath = function(path, gears) { var a, b, d, pathLength, stepSize, supportingGear; stepSize = 10; pathLength = Util.getLength(path); supportingGear = null; a = path[0]; d = 0; while (d < pathLength && (supportingGear == null)) { d += stepSize; b = Util.findPointOnPath(path, d); supportingGear = Util.findNearestIntersectingGear(gears, new LineSegment(a, b)); } return [supportingGear, d]; }; Chain.prototype.findSupportingGearsOnPath = function(gears, firstSupportingGear, path, startDistance, isClosed) { var a, b, d, lineSegment, nextSupportingGear, pathLength, stepSize, supportingGear, supportingGears, tangentPoint, tangentSide; if (startDistance == null) { startDistance = 0; } if (isClosed == null) { isClosed = true; } stepSize = 10; pathLength = Util.getLength(path, isClosed); supportingGear = firstSupportingGear; supportingGears = []; a = firstSupportingGear.location; d = startDistance; while (d < pathLength) { d += stepSize; b = Util.findPointOnPath(path, d); tangentSide = this.findReverseChainTangentSide(supportingGear); tangentPoint = Util.findGearTangentPoints(b, supportingGear)[tangentSide]; if (tangentPoint != null) { a = tangentPoint; } lineSegment = new LineSegment(a, b); nextSupportingGear = Util.findNearestIntersectingGear(gears, lineSegment, Util.makeSet(supportingGear.id)); if (nextSupportingGear != null) { supportingGear = nextSupportingGear; supportingGears.push(supportingGear); } } return supportingGears; }; Chain.prototype.removeRepeatedGears = function(gearsList) { var g1, g2, i, j, numberOfGears, numberOfNoops; numberOfNoops = 0; i = 0; while (numberOfNoops < (numberOfGears = gearsList.length)) { j = (i + 1) % numberOfGears; g1 = gearsList[i]; g2 = gearsList[j]; if (g1 === g2) { gearsList.splice(j, 1); numberOfNoops = 0; } else { numberOfNoops++; i = (i + 1) % numberOfGears; } } return gearsList; }; Chain.prototype.containsSuccessiveOverlappingGears = function(gearsList) { var g1, g2, i, j, numberOfGears, _i; numberOfGears = gearsList.length; for (i = _i = 0; 0 <= numberOfGears ? _i < numberOfGears : _i > numberOfGears; i = 0 <= numberOfGears ? ++_i : --_i) { j = (i + 1) % numberOfGears; g1 = gearsList[i]; g2 = gearsList[j]; if (g1.location.distance(g2.location) < (g1.outerRadius + g2.outerRadius)) { return true; } } return false; }; Chain.prototype.findSupportingGearIds = function(gears) { var finalSegment, firstSupportingGear, gear, lastSupportingGear, nextSupportingGears, startDistance, supportingGears, tangentPoint, tangentSide, _i, _len, _ref, _ref1, _results; _ref = this.findFirstSupportingGearOnPath(this.points, gears), firstSupportingGear = _ref[0], startDistance = _ref[1]; supportingGears = [firstSupportingGear]; nextSupportingGears = this.findSupportingGearsOnPath(gears, firstSupportingGear, this.points, startDistance); supportingGears = supportingGears.concat(nextSupportingGears); tangentSide = this.findChainTangentSide(firstSupportingGear); tangentPoint = Util.findGearTangentPoints(this.points[0], firstSupportingGear)[tangentSide]; if (tangentPoint != null) { finalSegment = [this.points[0], tangentPoint]; lastSupportingGear = supportingGears[supportingGears.length - 1]; nextSupportingGears = this.findSupportingGearsOnPath(gears, lastSupportingGear, finalSegment, 0, false); supportingGears = supportingGears.concat(nextSupportingGears); } _ref1 = this.removeRepeatedGears(supportingGears); _results = []; for (_i = 0, _len = _ref1.length; _i < _len; _i++) { gear = _ref1[_i]; _results.push(gear.id); } return _results; }; Chain.prototype.findIgnoredGearIds = function(board) { var acknowledgedLevels, currentDistance, currentLevel, d, distance, gear, gears, group, id, ignoredGearIds, level, levels, minDistances, _ref; gears = board.getGears(); minDistances = {}; for (id in gears) { if (!__hasProp.call(gears, id)) continue; gear = gears[id]; group = gear.group; level = gear.level; d = Util.pointPathDistance(gear.location, this.points) - gear.pitchRadius; if ((((_ref = minDistances[group]) != null ? _ref[level] : void 0) == null) || d < minDistances[group][level]) { if (minDistances[group] == null) { minDistances[group] = {}; } minDistances[group][level] = d; } } acknowledgedLevels = {}; for (group in minDistances) { if (!__hasProp.call(minDistances, group)) continue; levels = minDistances[group]; for (level in levels) { if (!__hasProp.call(levels, level)) continue; distance = levels[level]; currentLevel = acknowledgedLevels[group]; if (currentLevel == null) { acknowledgedLevels[group] = parseInt(level, 10); } else if (distance > 0) { currentDistance = minDistances[group][currentLevel]; if (currentDistance < 0 || distance < currentDistance) { acknowledgedLevels[group] = parseInt(level, 10); } } } } ignoredGearIds = {}; for (id in gears) { if (!__hasProp.call(gears, id)) continue; gear = gears[id]; if (acknowledgedLevels[gear.group] !== gear.level) { ignoredGearIds[id] = true; } } return ignoredGearIds; }; Chain.prototype.findIgnoredGearIdsInTightenedChain = function(board) { var gear, gearId, group, groups, id, level, updatedIgnoredGearIds, _i, _len, _ref, _ref1; groups = {}; _ref = this.supportingGearIds; for (_i = 0, _len = _ref.length; _i < _len; _i++) { gearId = _ref[_i]; gear = board.getGearWithId(gearId); group = gear.group; level = gear.level; if (groups[group] == null) { groups[group] = {}; } groups[group][level] = true; } updatedIgnoredGearIds = {}; _ref1 = board.getGears(); for (id in _ref1) { if (!__hasProp.call(_ref1, id)) continue; gear = _ref1[id]; group = gear.group; level = gear.level; if ((groups[group] != null) && (groups[group][level] == null)) { updatedIgnoredGearIds[id] = true; } } return this.ignoredGearIds = updatedIgnoredGearIds; }; Chain.prototype.toPolygon = function(segments) { var polygon, segment, _i, _len; if (segments == null) { segments = this.segments; } polygon = []; for (_i = 0, _len = segments.length; _i < _len; _i++) { segment = segments[_i]; if (segment instanceof LineSegment) { polygon.push(segment.start); } else { polygon.push(segment.findPoint(0)); polygon.push(segment.findPoint(0.5 * segment.getLength())); } } return polygon; }; Chain.prototype.update = function(board, gears) { 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; if (gears == null) { gears = board.getGearsWithIds(this.supportingGearIds); } if (gears.length < 2) { return false; } if (this.containsSuccessiveOverlappingGears(gears)) { return false; } updatedIgnoredGearIds = this.findIgnoredGearIdsInTightenedChain(board); acknowledgedGears = board.getAcknowledgedGears(updatedIgnoredGearIds); i = 0; while (i < (numberOfGears = gears.length)) { j = (i + 1) % numberOfGears; k = (i + 2) % numberOfGears; g1 = gears[i]; g2 = gears[j]; g3 = gears[k]; lineSegment1 = Util.findTangentLine(g1, g2, this.innerGearIds, this.direction); lineSegment2 = Util.findTangentLine(g2, g3, this.innerGearIds, this.direction); intersection = lineSegment1.findIntersection(lineSegment2); if (intersection != null) { tangentSideG1 = this.findReverseChainTangentSide(g1); tangentPointG1 = Util.findGearTangentPoints(intersection, g1)[tangentSideG1]; tangentSideG3 = this.findChainTangentSide(g3); tangentPointG3 = Util.findGearTangentPoints(intersection, g3)[tangentSideG3]; path = [tangentPointG1, intersection, tangentPointG3]; replacementGears = this.findSupportingGearsOnPath(acknowledgedGears, g1, path, 0, false); if (__indexOf.call(replacementGears, g2) >= 0) { return false; } gears.splice.apply(gears, [j, 1].concat(replacementGears)); this.removeRepeatedGears(gears); return this.update(board, gears); } gear = Util.findNearestIntersectingGear(acknowledgedGears, lineSegment1, Util.makeSet(g1.id, g2.id)); if (gear != null) { gears.splice(j, 0, gear); if (this.containsSuccessiveOverlappingGears(gears)) { return false; } } i++; } updatedPoints = []; for (i = _i = 0; 0 <= numberOfGears ? _i < numberOfGears : _i > numberOfGears; i = 0 <= numberOfGears ? ++_i : --_i) { j = (i + 1) % numberOfGears; g1 = gears[i]; g2 = gears[j]; tangentLine = Util.findTangentLine(g1, g2, this.innerGearIds, this.direction); updatedPoints.push(tangentLine.start, tangentLine.end); } updatedSegments = []; for (i = _j = 0; 0 <= numberOfGears ? _j < numberOfGears : _j > numberOfGears; i = 0 <= numberOfGears ? ++_j : --_j) { p0 = updatedPoints[2 * i]; p1 = updatedPoints[2 * i + 1]; p2 = updatedPoints[2 * ((i + 1) % numberOfGears)]; gear = gears[(i + 1) % numberOfGears]; lineSegment = new LineSegment(p0, p1); arcStart = Math.atan2(p1.y - gear.location.y, p1.x - gear.location.x); arcEnd = Math.atan2(p2.y - gear.location.y, p2.x - gear.location.x); direction = (this.direction === Util.Direction.CLOCKWISE) === (gear.id in this.innerGearIds) ? Util.Direction.CLOCKWISE : Util.Direction.COUNTER_CLOCKWISE; arcSegment = new ArcSegment(gear.location, gear.pitchRadius, arcStart, arcEnd, direction); updatedSegments.push(lineSegment, arcSegment); } numberOfSegments = updatedSegments.length; for (i = _k = 0, _ref = numberOfSegments - 2; 0 <= _ref ? _k < _ref : _k > _ref; i = 0 <= _ref ? ++_k : --_k) { for (j = _l = _ref1 = i + 2; _ref1 <= numberOfSegments ? _l < numberOfSegments : _l > numberOfSegments; j = _ref1 <= numberOfSegments ? ++_l : --_l) { if (i !== 0 || j !== numberOfSegments - 1) { s1 = updatedSegments[i]; s2 = updatedSegments[j]; if (s1.distanceToSegment(s2) < Chain.WIDTH) { if ((i + 2) === j) { middleSegment = updatedSegments[i + 1]; if ((middleSegment instanceof ArcSegment) && (middleSegment.getLength() < 2 * Chain.WIDTH)) { continue; } } if (((j + 2) % numberOfSegments) === i) { middleSegment = updatedSegments[(j + 1) % numberOfSegments]; if ((middleSegment instanceof ArcSegment) && (middleSegment.getLength() < 2 * Chain.WIDTH)) { continue; } } return false; } } } } updatedIgnoredGearIds = this.findIgnoredGearIdsInTightenedChain(board); updatedAcknowledgedGears = board.getAcknowledgedGears(updatedIgnoredGearIds); chainPolygon = this.toPolygon(updatedSegments); updatedInnerGearIds = {}; for (id in updatedAcknowledgedGears) { if (!__hasProp.call(updatedAcknowledgedGears, id)) continue; gear = updatedAcknowledgedGears[id]; if (Util.isPointInsidePolygon(gear.location, chainPolygon)) { updatedInnerGearIds[id] = true; } } _ref2 = this.innerGearIds; for (gearId in _ref2) { if (!__hasProp.call(_ref2, gearId)) continue; if (!(gearId in updatedInnerGearIds) && (__indexOf.call(this.supportingGearIds, gearId) >= 0)) { return false; } } this.points = updatedPoints; this.segments = updatedSegments; this.ignoredGearIds = updatedIgnoredGearIds; this.innerGearIds = updatedInnerGearIds; this.supportingGearIds = (function() { var _len, _m, _results; _results = []; for (_m = 0, _len = gears.length; _m < _len; _m++) { gear = gears[_m]; _results.push(gear.id); } return _results; })(); return true; }; Chain.prototype.tighten = function(board) { var acknowledgedGears, gear; this.ignoredGearIds = this.findIgnoredGearIds(board); acknowledgedGears = board.getAcknowledgedGears(this.ignoredGearIds); this.innerGearIds = Util.makeSetFromList((function() { var _i, _len, _ref, _results; _ref = Util.findGearsInsidePolygon(this.points, acknowledgedGears); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { gear = _ref[_i]; _results.push(gear.id); } return _results; }).call(this)); if (Object.keys(this.innerGearIds).length < 2) { return false; } this.direction = Util.findDirection(this.points); this.supportingGearIds = this.findSupportingGearIds(acknowledgedGears); return this.update(board); }; Chain.prototype.clone = function() { var copy; copy = new Chain(this.points, this.id, this.group, this.level, Util.clone(this.connections)); copy.segments = Util.clone(this.segments); copy.ignoredGearIds = Util.clone(this.ignoredGearIds); copy.innerGearIds = Util.clone(this.innerGearIds); copy.direction = this.direction; copy.supportingGearIds = Util.clone(this.supportingGearIds); return copy; }; Chain.fromObject = function(obj) { var chain, createSegment, p, points, segment; createSegment = function(obj) { if (obj.center != null) { return ArcSegment.fromObject(obj); } else { return LineSegment.fromObject(obj); } }; points = (function() { var _i, _len, _ref, _results; _ref = obj.points; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { p = _ref[_i]; _results.push(new Point(p.x, p.y)); } return _results; })(); chain = new Chain(points, obj.id, obj.group, obj.level, obj.connections); chain.segments = (function() { var _i, _len, _ref, _results; _ref = obj.segments; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { segment = _ref[_i]; _results.push(createSegment(segment)); } return _results; })(); chain.ignoredGearIds = obj.ignoredGearIds; chain.innerGearIds = obj.innerGearIds; chain.direction = obj.direction; chain.supportingGearIds = obj.supportingGearIds; return chain; }; return Chain; })(); window.gearsketch.model.Chain = Chain; Board = (function() { var AXIS_RADIUS, ConnectionType, EPSILON, MIN_STACKED_GEARS_TEETH_DIFFERENCE, MODULE, SNAPPING_DISTANCE; MODULE = Util.MODULE; AXIS_RADIUS = Util.AXIS_RADIUS; MIN_STACKED_GEARS_TEETH_DIFFERENCE = Util.MIN_STACKED_GEARS_TEETH_DIFFERENCE; SNAPPING_DISTANCE = Util.SNAPPING_DISTANCE; EPSILON = Util.EPSILON; ConnectionType = { ANY: "any", MESHING: "meshing", AXIS: "axis", CHAIN_INSIDE: "chain_inside", CHAIN_OUTSIDE: "chain_outside" }; function Board(gears, chains) { this.gears = gears != null ? gears : {}; this.chains = chains != null ? chains : {}; } Board.prototype.restore = function(board) { var gear, id, _ref; _ref = this.gears; for (id in _ref) { if (!__hasProp.call(_ref, id)) continue; gear = _ref[id]; gear.restore(board.gears[id]); } return this.chains = board.chains; }; Board.prototype.restoreAfterDemo = function(board) { this.gears = board.gears; return this.chains = board.chains; }; Board.prototype.clear = function() { this.gears = {}; return this.chains = {}; }; Board.prototype.getNextGroup = function() { var gear, id, nextGroup, _ref; nextGroup = 0; _ref = this.gears; for (id in _ref) { if (!__hasProp.call(_ref, id)) continue; gear = _ref[id]; nextGroup = Math.max(nextGroup, gear.group + 1); } return nextGroup; }; Board.prototype.getGears = function() { return this.gears; }; Board.prototype.getGearList = function() { var gear, id, _ref, _results; _ref = this.gears; _results = []; for (id in _ref) { if (!__hasProp.call(_ref, id)) continue; gear = _ref[id]; _results.push(gear); } return _results; }; Board.prototype.getAcknowledgedGears = function(ignoredGearIds) { var acknowledgedGears, gear, id, _ref; acknowledgedGears = {}; _ref = this.gears; for (id in _ref) { if (!__hasProp.call(_ref, id)) continue; gear = _ref[id]; if (!(id in ignoredGearIds)) { acknowledgedGears[id] = gear; } } return acknowledgedGears; }; Board.prototype.getLevelScore = function(gear) { return 1000 * gear.group + gear.level; }; Board.prototype.getGearsSortedByGroupAndLevel = function(gears) { var _this = this; if (gears == null) { gears = this.getGearList(); } return gears.sort(function(g1, g2) { return _this.getLevelScore(g1) - _this.getLevelScore(g2); }); }; Board.prototype.removeConnection = function(turningObject1, turningObject2) { delete turningObject1.connections[turningObject2.id]; return delete turningObject2.connections[turningObject1.id]; }; Board.prototype.removeAllConnections = function(turningObject) { var neighbor, neighborId, _ref; _ref = turningObject.connections; for (neighborId in _ref) { if (!__hasProp.call(_ref, neighborId)) continue; neighbor = this.getTurningObjects()[neighborId]; this.removeConnection(turningObject, neighbor); } return this.updateGroupsAndLevels(); }; Board.prototype.findNearestAxis = function(gear) { var candidate, distance, id, nearestAxis, shortestDistance, _ref; nearestAxis = null; shortestDistance = Number.MAX_VALUE; _ref = this.gears; for (id in _ref) { if (!__hasProp.call(_ref, id)) continue; candidate = _ref[id]; if (candidate !== gear) { distance = gear.location.distance(candidate.location); if (!nearestAxis || distance < (shortestDistance - EPSILON) || (distance < (shortestDistance + EPSILON) && candidate.numberOfTeeth < nearestAxis.numberOfTeeth)) { nearestAxis = candidate; shortestDistance = distance; } } } return nearestAxis; }; Board.prototype.updateGroupsAndLevelsFrom = function(turningObjectId, group, level, updatedGroups, updatedLevels) { var connectionType, connections, gear, neighbor, neighborId, sameLevelConnectionTypes, turningObject, _results; turningObject = this.getTurningObjects()[turningObjectId]; updatedGroups[turningObjectId] = group; updatedLevels[turningObjectId] = level; connections = turningObject.connections; sameLevelConnectionTypes = [ConnectionType.MESHING, ConnectionType.CHAIN_INSIDE, ConnectionType.CHAIN_OUTSIDE]; _results = []; for (neighborId in connections) { if (!__hasProp.call(connections, neighborId)) continue; connectionType = connections[neighborId]; if (!(neighborId in updatedGroups)) { if (__indexOf.call(sameLevelConnectionTypes, connectionType) >= 0) { _results.push(this.updateGroupsAndLevelsFrom(neighborId, group, level, updatedGroups, updatedLevels)); } else { gear = this.gears[turningObjectId]; neighbor = this.gears[neighborId]; if (gear.numberOfTeeth > neighbor.numberOfTeeth) { _results.push(this.updateGroupsAndLevelsFrom(neighborId, group, level + 1, updatedGroups, updatedLevels)); } else { _results.push(this.updateGroupsAndLevelsFrom(neighborId, group, level - 1, updatedGroups, updatedLevels)); } } } else { _results.push(void 0); } } return _results; }; Board.prototype.updateGroupsAndLevels = function() { var group, id, turningObject, updatedGroups, updatedLevels, _ref, _ref1; updatedGroups = {}; updatedLevels = {}; group = 0; _ref = this.gears; for (id in _ref) { if (!__hasProp.call(_ref, id)) continue; if (!(id in updatedGroups)) { this.updateGroupsAndLevelsFrom(id, group, 0, updatedGroups, updatedLevels); group++; } } _ref1 = this.getTurningObjects(); for (id in _ref1) { if (!__hasProp.call(_ref1, id)) continue; turningObject = _ref1[id]; turningObject.group = updatedGroups[id]; turningObject.level = updatedLevels[id]; } return null; }; Board.prototype.addConnection = function(turningObject1, turningObject2, connectionType) { turningObject1.connections[turningObject2.id] = connectionType; turningObject2.connections[turningObject1.id] = connectionType; return this.updateGroupsAndLevels(); }; Board.prototype.findMeshingNeighbors = function(gear) { var candidate, candidateId, meshingNeighbors, _ref; meshingNeighbors = []; _ref = this.gears; for (candidateId in _ref) { if (!__hasProp.call(_ref, candidateId)) continue; candidate = _ref[candidateId]; if (candidate !== gear && gear.edgeDistance(candidate) < EPSILON) { if ((candidate.group !== gear.group) || (candidate.level === gear.level)) { meshingNeighbors.push(candidate); } } } return meshingNeighbors; }; Board.prototype.findRelativeAlignment = function(gear1, gear2) { var angle1, angle2, p1, p2, phase1, phase2, phaseSum, r1, r2, shift1, shift2, toothAngle1, toothAngle2; p1 = gear1.location; r1 = gear1.rotation; p2 = gear2.location; r2 = gear2.rotation; angle1 = Math.atan2(p2.y - p1.y, p2.x - p1.x); angle2 = angle1 + Math.PI; shift1 = Util.mod(angle1 - r1, 2 * Math.PI); shift2 = Util.mod(angle2 - r2, 2 * Math.PI); toothAngle1 = (2 * Math.PI) / gear1.numberOfTeeth; toothAngle2 = (2 * Math.PI) / gear2.numberOfTeeth; phase1 = (shift1 % toothAngle1) / toothAngle1; phase2 = (shift2 % toothAngle2) / toothAngle2; phaseSum = (phase1 + phase2) % 1; return (phaseSum - 0.25) * toothAngle1; }; Board.prototype.alignGearTeeth = function(rotatingGear, meshingGear) { return rotatingGear.rotation += this.findRelativeAlignment(rotatingGear, meshingGear); }; Board.prototype.areMeshingGearsAligned = function(gear1, gear2) { return Math.abs(this.findRelativeAlignment(gear1, gear2)) < EPSILON; }; Board.prototype.rotateTurningObjectsFrom = function(turningObject, angle, rotatedTurningObjectIds) { var connectionType, neighbor, neighborId, ratio, _ref, _results; if (!(turningObject.id in rotatedTurningObjectIds)) { turningObject.rotation = Util.mod(turningObject.rotation + angle, 2 * Math.PI); rotatedTurningObjectIds[turningObject.id] = true; } _ref = turningObject.connections; _results = []; for (neighborId in _ref) { if (!__hasProp.call(_ref, neighborId)) continue; connectionType = _ref[neighborId]; neighbor = this.getTurningObjects()[neighborId]; if (!(neighborId in rotatedTurningObjectIds)) { ratio = this.calculateRatio(turningObject, neighbor, connectionType); _results.push(this.rotateTurningObjectsFrom(neighbor, angle * ratio, rotatedTurningObjectIds)); } else { _results.push(void 0); } } return _results; }; Board.prototype.alignMeshingGears = function(gear) { var angle, neighbor, neighbors, r, rotatedGearIds, _i, _len, _results; rotatedGearIds = {}; rotatedGearIds[gear.id] = true; neighbors = this.findMeshingNeighbors(gear); _results = []; for (_i = 0, _len = neighbors.length; _i < _len; _i++) { neighbor = neighbors[_i]; this.addConnection(gear, neighbor, ConnectionType.MESHING); r = neighbor.rotation; this.alignGearTeeth(neighbor, gear); angle = neighbor.rotation - r; rotatedGearIds[neighbor.id] = true; _results.push(this.rotateTurningObjectsFrom(neighbor, angle, rotatedGearIds)); } return _results; }; Board.prototype.connectToAxis = function(upperGear, lowerGear) { this.addConnection(upperGear, lowerGear, ConnectionType.AXIS); upperGear.location = lowerGear.location.clone(); upperGear.rotation = lowerGear.rotation; return this.alignMeshingGears(upperGear); }; Board.prototype.findNearestNeighbor = function(gear, gearIdsToIgnore) { var edgeDistance, nearestNeighbor, neighbor, neighborId, shortestEdgeDistance, _ref; if (gearIdsToIgnore == null) { gearIdsToIgnore = {}; } nearestNeighbor = null; shortestEdgeDistance = Number.MAX_VALUE; _ref = this.gears; for (neighborId in _ref) { if (!__hasProp.call(_ref, neighborId)) continue; neighbor = _ref[neighborId]; if (neighbor !== gear && !(neighborId in gearIdsToIgnore)) { edgeDistance = gear.edgeDistance(neighbor); if (edgeDistance < shortestEdgeDistance) { nearestNeighbor = neighbor; shortestEdgeDistance = edgeDistance; } } } return nearestNeighbor; }; Board.prototype.connectToOneMeshingGear = function(gear, meshingGear) { var angle, delta; delta = gear.location.minus(meshingGear.location); angle = Math.atan2(delta.y, delta.x); gear.location = meshingGear.location.plus(Point.polar(angle, gear.pitchRadius + meshingGear.pitchRadius)); this.alignGearTeeth(gear, meshingGear); return this.addConnection(gear, meshingGear, ConnectionType.MESHING); }; Board.prototype.connectToTwoMeshingGears = function(gear, meshingGear1, meshingGear2) { var a, d, h, p0, p1, p2, p3_1, p3_2, p3x1, p3x2, p3y1, p3y2, r0, r1; p0 = meshingGear1.location; p1 = meshingGear2.location; r0 = meshingGear1.pitchRadius + gear.pitchRadius; r1 = meshingGear2.pitchRadius + gear.pitchRadius; d = p0.distance(p1); if (r0 + r1 < d || p0.distance(p1) < EPSILON) { if (gear.edgeDistance(meshingGear1) < gear.edgeDistance(meshingGear2)) { this.connectToOneMeshingGear(gear, meshingGear1); return; } else { this.connectToOneMeshingGear(gear, meshingGear2); return; } } a = (r0 * r0 - r1 * r1 + d * d) / (2 * d); h = Math.sqrt(r0 * r0 - a * a); p2 = p0.plus(p1.minus(p0).times(a / d)); p3x1 = p2.x + h * (p1.y - p0.y) / d; p3y1 = p2.y - h * (p1.x - p0.x) / d; p3x2 = p2.x - h * (p1.y - p0.y) / d; p3y2 = p2.y + h * (p1.x - p0.x) / d; p3_1 = new Point(p3x1, p3y1); p3_2 = new Point(p3x2, p3y2); if (gear.location.distance(p3_1) < gear.location.distance(p3_2)) { gear.location = p3_1; } else { gear.location = p3_2; } return this.alignMeshingGears(gear); }; Board.prototype.doChainsCrossNonSupportingGears = function() { var chain, id, _ref; _ref = this.chains; for (id in _ref) { if (!__hasProp.call(_ref, id)) continue; chain = _ref[id]; if (chain.crossesNonSupportingGears(this)) { return true; } } return false; }; Board.prototype.doChainsCrossEachOther = function(c1, c2) { if ((c1.group !== c2.group) || (c1.level === c2.level)) { if (c1.distanceToChain(c2) < Chain.WIDTH) { return true; } } return false; }; Board.prototype.doesChainCrossAnyOtherChain = function(chain) { var chain2, id2, _ref; _ref = this.chains; for (id2 in _ref) { if (!__hasProp.call(_ref, id2)) continue; chain2 = _ref[id2]; if (chain !== chain2) { if (this.doChainsCrossEachOther(chain, chain2)) { return true; } } } return false; }; Board.prototype.doAnyChainsCrossEachOther = function() { var c1, c2, chain, chainList, i, id, j, numberOfChains, _i, _j, _ref, _ref1; chainList = (function() { var _ref, _results; _ref = this.chains; _results = []; for (id in _ref) { if (!__hasProp.call(_ref, id)) continue; chain = _ref[id]; _results.push(chain); } return _results; }).call(this); numberOfChains = chainList.length; if (numberOfChains < 2) { return false; } for (i = _i = 0, _ref = numberOfChains - 1; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { for (j = _j = _ref1 = i + 1; _ref1 <= numberOfChains ? _j < numberOfChains : _j > numberOfChains; j = _ref1 <= numberOfChains ? ++_j : --_j) { c1 = chainList[i]; c2 = chainList[j]; if (this.doChainsCrossEachOther(c1, c2)) { return true; } } } return false; }; Board.prototype.areAllMeshingGearsAligned = function() { var g1, g2, gears, i, j, numberOfGears, _i, _j, _ref, _ref1; gears = this.getGearList(); numberOfGears = gears.length; if (numberOfGears < 2) { return true; } for (i = _i = 0, _ref = numberOfGears - 1; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { for (j = _j = _ref1 = i + 1; _ref1 <= numberOfGears ? _j < numberOfGears : _j > numberOfGears; j = _ref1 <= numberOfGears ? ++_j : --_j) { g1 = gears[i]; g2 = gears[j]; if (g1.connections[g2.id] === ConnectionType.MESHING) { if (!this.areMeshingGearsAligned(g1, g2)) { return false; } } } } return true; }; Board.prototype.calculateRatio = function(turningObject1, turningObject2, connectionType) { if (connectionType === ConnectionType.AXIS) { return 1; } else if ((connectionType === ConnectionType.MESHING) || (connectionType === ConnectionType.CHAIN_OUTSIDE)) { return -turningObject1.getCircumference() / turningObject2.getCircumference(); } else { return turningObject1.getCircumference() / turningObject2.getCircumference(); } }; Board.prototype.calculatePathRatio = function(path) { var connectionType, i, pathLength, ratio, turningObject1, turningObject2, _i, _ref; ratio = 1; pathLength = path.length; for (i = _i = 0, _ref = pathLength - 1; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { turningObject1 = path[i]; turningObject2 = path[i + 1]; connectionType = turningObject1.connections[turningObject2.id]; ratio *= this.calculateRatio(turningObject1, turningObject2, connectionType); } return ratio; }; Board.prototype.areConnectionRatiosConsistent = function() { var path, pathName, paths, ratio, ratios, _i, _len; ratios = {}; paths = Util.findAllSimplePathsBetweenNeighbors(this.getTurningObjects()); for (_i = 0, _len = paths.length; _i < _len; _i++) { path = paths[_i]; pathName = "" + path[0].id + "-" + path[path.length - 1].id; ratio = this.calculatePathRatio(path); if (ratios[pathName] == null) { ratios[pathName] = ratio; } else { if (Math.abs(ratios[pathName] - ratio) > EPSILON) { return false; } } } return true; }; Board.prototype.isBoardValid = function() { var axisDistance, combinedOuterRadius, gear1, gear2, group1, group2, id1, id2, level1, level2, maxOuterRadius, _ref, _ref1; _ref = this.gears; for (id1 in _ref) { if (!__hasProp.call(_ref, id1)) continue; gear1 = _ref[id1]; group1 = gear1.group; level1 = gear1.level; _ref1 = this.gears; for (id2 in _ref1) { if (!__hasProp.call(_ref1, id2)) continue; gear2 = _ref1[id2]; if (gear1 !== gear2) { group2 = gear2.group; level2 = gear2.level; axisDistance = gear1.location.distance(gear2.location); maxOuterRadius = Math.max(gear1.outerRadius, gear2.outerRadius); combinedOuterRadius = gear1.outerRadius + gear2.outerRadius; if (axisDistance < EPSILON) { if ((group1 !== group2) || (level1 === level2)) { return false; } } else if (group1 === group2 && level1 === level2 && (gear1.connections[gear2.id] == null)) { if (axisDistance < combinedOuterRadius) { return false; } } else if (axisDistance < maxOuterRadius + AXIS_RADIUS) { return false; } } } } return !this.doChainsCrossNonSupportingGears() && !this.doAnyChainsCrossEachOther() && this.areAllMeshingGearsAligned() && this.areConnectionRatiosConsistent(); }; Board.prototype.placeGear = function(gear, location) { var chain, id, nearestAxis, neighbor1, neighbor2, oldBoard, _ref; oldBoard = this.clone(); this.removeAllConnections(gear); gear.location = location.clone(); nearestAxis = this.findNearestAxis(gear); if (nearestAxis && gear.location.distance(nearestAxis.location) < SNAPPING_DISTANCE && nearestAxis.numberOfTeeth - gear.numberOfTeeth > MIN_STACKED_GEARS_TEETH_DIFFERENCE) { this.connectToAxis(gear, nearestAxis); } else { neighbor1 = this.findNearestNeighbor(gear); if (neighbor1 && gear.edgeDistance(neighbor1) < SNAPPING_DISTANCE) { neighbor2 = this.findNearestNeighbor(gear, Util.makeSet(neighbor1.id)); if (neighbor2 && gear.edgeDistance(neighbor2) < SNAPPING_DISTANCE) { this.connectToTwoMeshingGears(gear, neighbor1, neighbor2); } else { this.connectToOneMeshingGear(gear, neighbor1); } } } _ref = this.chains; for (id in _ref) { if (!__hasProp.call(_ref, id)) continue; chain = _ref[id]; if (chain.update(this)) { this.updateChainConnections(chain); } else { this.restore(oldBoard); return false; } } if (this.isBoardValid()) { return true; } else { this.restore(oldBoard); return false; } }; Board.prototype.addGearToChains = function(gear) { var chain, id, _ref, _results; _ref = this.chains; _results = []; for (id in _ref) { if (!__hasProp.call(_ref, id)) continue; chain = _ref[id]; if (Util.isPointInsidePolygon(gear.location, chain.toPolygon())) { _results.push(chain.innerGearIds[gear.id] = true); } else { _results.push(void 0); } } return _results; }; Board.prototype.removeGearFromChains = function(gear) { var chain, id, _ref, _results; _ref = this.chains; _results = []; for (id in _ref) { if (!__hasProp.call(_ref, id)) continue; chain = _ref[id]; if (!chain.removeGear(gear, this) || this.doesChainCrossAnyOtherChain(chain)) { _results.push(this.removeChain(chain)); } else { _results.push(this.updateChainConnections(chain)); } } return _results; }; Board.prototype.addGear = function(gear) { var oldBoard; oldBoard = this.clone(); gear.group = this.getNextGroup(); this.gears[gear.id] = gear; this.addGearToChains(gear); if (!this.placeGear(gear, gear.location)) { this.removeGear(gear); this.restore(oldBoard); return false; } else { return true; } }; Board.prototype.removeGear = function(gear) { this.removeAllConnections(gear); delete this.gears[gear.id]; return this.removeGearFromChains(gear); }; Board.prototype.getGearAt = function(location, candidates) { var candidate, distance, gear, id; if (candidates == null) { candidates = this.gears; } gear = null; for (id in candidates) { if (!__hasProp.call(candidates, id)) continue; candidate = candidates[id]; distance = location.distance(candidate.location); if (distance < candidate.outerRadius) { if (!gear || candidate.numberOfTeeth < gear.numberOfTeeth) { gear = candidate; } } } return gear; }; Board.prototype.isTopLevelGear = function(gear) { var connectionType, id, _ref; _ref = gear.connections; for (id in _ref) { if (!__hasProp.call(_ref, id)) continue; connectionType = _ref[id]; if (connectionType === ConnectionType.AXIS && this.gears[id].level > gear.level) { return false; } } return true; }; Board.prototype.getTopLevelGears = function() { var gear, id, topLevelGears, _ref; topLevelGears = {}; _ref = this.gears; for (id in _ref) { if (!__hasProp.call(_ref, id)) continue; gear = _ref[id]; if (this.isTopLevelGear(gear)) { topLevelGears[id] = gear; } } return topLevelGears; }; Board.prototype.getTopLevelGearAt = function(location) { return this.getGearAt(location, this.getTopLevelGears()); }; Board.prototype.getGearWithId = function(id) { return this.gears[id]; }; Board.prototype.getGearsWithIds = function(ids) { var id, _i, _len, _results; _results = []; for (_i = 0, _len = ids.length; _i < _len; _i++) { id = ids[_i]; _results.push(this.gears[id]); } return _results; }; Board.prototype.rotateAllTurningObjects = function(delta) { var angle, gear, id, _ref, _results; _ref = this.gears; _results = []; for (id in _ref) { if (!__hasProp.call(_ref, id)) continue; gear = _ref[id]; if (gear.momentum) { angle = gear.momentum * (delta / 1000); _results.push(this.rotateTurningObjectsFrom(gear, angle, {})); } else { _results.push(void 0); } } return _results; }; Board.prototype.addChainConnections = function(chain) { var gearId, _i, _len, _ref, _results; _ref = chain.supportingGearIds; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { gearId = _ref[_i]; if (gearId in chain.innerGearIds) { _results.push(this.addConnection(chain, this.getGearWithId(gearId), ConnectionType.CHAIN_INSIDE)); } else { _results.push(this.addConnection(chain, this.getGearWithId(gearId), ConnectionType.CHAIN_OUTSIDE)); } } return _results; }; Board.prototype.updateChainConnections = function(chain) { this.removeAllConnections(chain); return this.addChainConnections(chain); }; Board.prototype.addChain = function(chain) { var oldBoard; oldBoard = this.clone(); this.chains[chain.id] = chain; if (chain.tighten(this)) { this.chains[chain.id] = chain; this.addChainConnections(chain); } else { this.restore(oldBoard); return false; } if (this.isBoardValid()) { return true; } else { this.restore(oldBoard); return false; } }; Board.prototype.removeChain = function(chain) { this.removeAllConnections(chain); return delete this.chains[chain.id]; }; Board.prototype.getChains = function() { return this.chains; }; Board.prototype.getChainsInGroupOnLevel = function(group, level) { var chain, id, _ref, _results; _ref = this.chains; _results = []; for (id in _ref) { if (!__hasProp.call(_ref, id)) continue; chain = _ref[id]; if ((chain.group === group) && (chain.level === level)) { _results.push(chain); } } return _results; }; Board.prototype.getTurningObjects = function() { var chain, gear, id, turningObjects, _ref, _ref1; turningObjects = {}; _ref = this.gears; for (id in _ref) { if (!__hasProp.call(_ref, id)) continue; gear = _ref[id]; turningObjects[id] = gear; } _ref1 = this.chains; for (id in _ref1) { if (!__hasProp.call(_ref1, id)) continue; chain = _ref1[id]; turningObjects[id] = chain; } return turningObjects; }; Board.prototype.clone = function() { return { gears: Util.clone(this.gears), chains: Util.clone(this.chains) }; }; Board.fromObject = function(obj) { var board, chain, gear, id, _ref, _ref1; board = new Board(); _ref = obj.gears; for (id in _ref) { gear = _ref[id]; board.gears[id] = Gear.fromObject(gear); } _ref1 = obj.chains; for (id in _ref1) { chain = _ref1[id]; board.chains[id] = Chain.fromObject(chain); } return board; }; return Board; })(); window.gearsketch.model.Board = Board; }).call(this); /* //@ sourceMappingURL=gearsketch_model.map */