// 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
|
|
*/
|