// Generated by CoffeeScript 1.6.3 (function() { "use strict"; var ArcSegment, LineSegment, Point, Util, __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; }, __hasProp = {}.hasOwnProperty, __slice = [].slice; window.gearsketch = {}; Point = (function() { function Point(x, y) { this.x = x; this.y = y; } Point.prototype.plus = function(p) { return new Point(this.x + p.x, this.y + p.y); }; Point.prototype.minus = function(p) { return new Point(this.x - p.x, this.y - p.y); }; Point.prototype.times = function(n) { return new Point(n * this.x, n * this.y); }; Point.prototype.distance = function(p) { return Math.sqrt(Math.pow(this.x - p.x, 2) + Math.pow(this.y - p.y, 2)); }; Point.prototype.cross = function(p) { return this.x * p.y - this.y * p.x; }; Point.prototype.clone = function() { return new Point(this.x, this.y); }; Point.polar = function(theta, r) { return new Point(r * Math.cos(theta), r * Math.sin(theta)); }; Point.fromObject = function(obj) { return new Point(obj.x, obj.y); }; return Point; })(); window.gearsketch.Point = Point; ArcSegment = (function() { function ArcSegment(center, radius, startAngle, endAngle, direction) { this.center = center; this.radius = radius; this.startAngle = startAngle; this.endAngle = endAngle; this.direction = direction; this.start = this.pointOnCircle(startAngle); this.end = this.pointOnCircle(endAngle); } ArcSegment.prototype.getLength = function() { var angle; angle = this.direction === Util.Direction.CLOCKWISE ? Util.mod(this.endAngle - this.startAngle, 2 * Math.PI) : Util.mod(this.startAngle - this.endAngle, 2 * Math.PI); return angle * this.radius; }; ArcSegment.prototype.findPoint = function(distanceToGo) { var angle, angleToGo; angleToGo = distanceToGo / this.radius; angle = this.startAngle + (this.direction === Util.Direction.CLOCKWISE ? angleToGo : -angleToGo); return this.center.plus(Point.polar(angle, this.radius)); }; ArcSegment.prototype.pointOnCircle = function(angle) { return this.center.plus(Point.polar(angle, this.radius)); }; ArcSegment.prototype.containsAngle = function(angle) { if (this.direction === Util.Direction.CLOCKWISE) { return Util.mod(this.endAngle - this.startAngle, 2 * Math.PI) > Util.mod(angle - this.startAngle, 2 * Math.PI); } else { return Util.mod(this.startAngle - this.endAngle, 2 * Math.PI) > Util.mod(this.startAngle - angle, 2 * Math.PI); } }; ArcSegment.prototype.distanceToPoint = function(point) { var angle; angle = Math.atan2(point.y - this.center.y, point.x - this.center.x); if (this.containsAngle(angle)) { return Math.abs(point.distance(this.center) - this.radius); } else { return Math.min(point.distance(this.start), point.distance(this.end)); } }; ArcSegment.prototype.intersectsLineSegment = function(lineSegment) { var d, discriminant, dr, dx, dy, i1, i1x, i1y, i2, i2x, i2y, p1, p2; p1 = lineSegment.start.minus(this.center); p2 = lineSegment.end.minus(this.center); dx = p2.x - p1.x; dy = p2.y - p1.y; dr = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); if (dr === 0) { return false; } d = p1.x * p2.y - p2.x * p1.y; discriminant = Math.pow(this.radius, 2) * Math.pow(dr, 2) - Math.pow(d, 2); if (discriminant < 0) { return false; } else { i1x = (d * dy + Util.sign(dy) * dx * Math.sqrt(discriminant)) / Math.pow(dr, 2); i1y = (-d * dx + Math.abs(dy) * Math.sqrt(discriminant)) / Math.pow(dr, 2); i1 = new Point(i1x, i1y).plus(this.center); if (lineSegment.distanceToPoint(i1) < Util.EPSILON && this.distanceToPoint(i1) < Util.EPSILON) { return true; } i2x = (d * dy - Util.sign(dy) * dx * Math.sqrt(discriminant)) / Math.pow(dr, 2); i2y = (-d * dx - Math.abs(dy) * Math.sqrt(discriminant)) / Math.pow(dr, 2); i2 = new Point(i2x, i2y).plus(this.center); if (lineSegment.distanceToPoint(i2) < Util.EPSILON && this.distanceToPoint(i2) < Util.EPSILON) { return true; } return false; } }; ArcSegment.prototype.distanceToSegment = function(segment) { var angle1, angle2, centerDistance, pointNearestToCenter; if (segment instanceof ArcSegment) { if (this.center.distance(segment.center) > Util.EPSILON) { angle1 = Math.atan2(segment.center.y - this.center.y, segment.center.x - this.center.x); angle2 = Util.mod(angle1 + Math.PI, 2 * Math.PI); if (this.containsAngle(angle1) && segment.containsAngle(angle2)) { centerDistance = this.center.distance(segment.center); return Math.max(0, centerDistance - this.radius - segment.radius); } } return Math.min(this.distanceToPoint(segment.start), this.distanceToPoint(segment.end), segment.distanceToPoint(this.start), segment.distanceToPoint(this.end)); } else { if (this.intersectsLineSegment(segment)) { return 0; } else { pointNearestToCenter = segment.findNearestPoint(this.center); return Math.min(this.distanceToPoint(pointNearestToCenter), this.distanceToPoint(segment.start), this.distanceToPoint(segment.end), segment.distanceToPoint(this.start), segment.distanceToPoint(this.end)); } } }; ArcSegment.prototype.clone = function() { return new ArcSegment(this.center.clone(), this.radius, this.startAngle, this.endAngle, this.direction); }; ArcSegment.fromObject = function(obj) { return new ArcSegment(Point.fromObject(obj.center), obj.radius, obj.startAngle, obj.endAngle, obj.direction); }; return ArcSegment; })(); window.gearsketch.ArcSegment = ArcSegment; LineSegment = (function() { function LineSegment(start, end) { this.start = start; this.end = end; } LineSegment.prototype.getLength = function() { return this.start.distance(this.end); }; LineSegment.prototype.findPoint = function(distanceToGo) { var fraction; fraction = distanceToGo / this.start.distance(this.end); return this.start.plus(this.end.minus(this.start).times(fraction)); }; LineSegment.prototype.findNearestPoint = function(p) { var segmentLength, t; segmentLength = this.getLength(); if (segmentLength === 0) { return this.start; } else { t = ((p.x - this.start.x) * (this.end.x - this.start.x) + (p.y - this.start.y) * (this.end.y - this.start.y)) / Math.pow(segmentLength, 2); if (t < 0) { return this.start; } else if (t > 1) { return this.end; } else { return this.start.plus(this.end.minus(this.start).times(t)); } } }; LineSegment.prototype.distanceToPoint = function(point) { return point.distance(this.findNearestPoint(point)); }; LineSegment.prototype.findIntersection = function(lineSegment) { var crossRS, p, q, r, s, t, u; p = this.start; r = this.end.minus(p); q = lineSegment.start; s = lineSegment.end.minus(q); crossRS = r.cross(s); t = q.minus(p).cross(s) / crossRS; u = q.minus(p).cross(r) / crossRS; if (Math.abs(crossRS) > Util.EPSILON && 0 <= t && t <= 1 && 0 <= u && u <= 1) { return p.plus(r.times(t)); } else { return null; } }; LineSegment.prototype.distanceToSegment = function(segment) { if (segment instanceof LineSegment) { if (this.findIntersection(segment)) { return 0; } else { return Math.min(this.distanceToPoint(segment.start), this.distanceToPoint(segment.end), segment.distanceToPoint(this.start), segment.distanceToPoint(this.end)); } } else { return segment.distanceToSegment(this); } }; LineSegment.prototype.clone = function() { return new LineSegment(this.start.clone(), this.end.clone()); }; LineSegment.fromObject = function(obj) { return new LineSegment(Point.fromObject(obj.start), Point.fromObject(obj.end)); }; return LineSegment; })(); window.gearsketch.LineSegment = LineSegment; Util = (function() { function Util() {} Point = window.gearsketch.Point; Util.MODULE = 6; Util.AXIS_RADIUS = 1.5 * Util.MODULE; Util.MIN_STACKED_GEARS_TEETH_DIFFERENCE = 4; Util.SNAPPING_DISTANCE = 2 * Util.MODULE; Util.EPSILON = 0.000001; Util.Direction = { CLOCKWISE: "clockwise", COUNTER_CLOCKWISE: "counterclockwise" }; Util.Side = { LEFT: "left", RIGHT: "right" }; Util.clone = function(obj) { var copy, i, key, knownClasses, _i, _ref, _ref1; if ((obj == null) || (typeof obj !== "object")) { return obj; } knownClasses = ["Point", "Gear", "ArcSegment", "LineSegment", "Chain"]; if (_ref = obj.constructor.name, __indexOf.call(knownClasses, _ref) >= 0) { return obj.clone(); } if (obj instanceof Array) { copy = []; for (i = _i = 0, _ref1 = obj.length; 0 <= _ref1 ? _i < _ref1 : _i > _ref1; i = 0 <= _ref1 ? ++_i : --_i) { copy[i] = this.clone(obj[i]); } return copy; } if (obj instanceof Object) { copy = {}; for (key in obj) { if (!__hasProp.call(obj, key)) continue; copy[key] = this.clone(obj[key]); } return copy; } throw new Error("Unable to clone object. Its type is not supported."); }; Util.createUUID = function() { return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { var r, v; r = Math.random() * 16 | 0; v = c === "x" ? r : r & 0x3 | 0x8; return v.toString(16); }); }; Util.mod = function(a, b) { return (a % b + b) % b; }; Util.sign = function(x) { if (x < 0) { return -1; } else { return 1; } }; Util.addAll = function(set, elements) { var element, _i, _len; for (_i = 0, _len = elements.length; _i < _len; _i++) { element = elements[_i]; set[element] = true; } return set; }; Util.makeSetFromList = function(elements) { return this.addAll({}, elements); }; Util.makeSet = function() { var elements; elements = 1 <= arguments.length ? __slice.call(arguments, 0) : []; return this.makeSetFromList(elements); }; Util.findPointOnPath = function(path, distance) { var distanceToGo, i, j, numberOfPoints, segment, segmentLength; distanceToGo = distance; i = 0; numberOfPoints = path.length; while (distanceToGo > 0) { j = (i + 1) % numberOfPoints; segment = new LineSegment(path[i], path[j]); segmentLength = segment.getLength(); if (distanceToGo <= segmentLength) { return segment.findPoint(distanceToGo); } else { i = j; distanceToGo -= segmentLength; } } return null; }; Util.getLength = function(path, isPathClosed) { var finalIndex, i, j, length, numberOfPoints, _i; if (isPathClosed == null) { isPathClosed = true; } length = 0; numberOfPoints = path.length; finalIndex = numberOfPoints - (isPathClosed ? 0 : 1); for (i = _i = 0; 0 <= finalIndex ? _i < finalIndex : _i > finalIndex; i = 0 <= finalIndex ? ++_i : --_i) { j = (i + 1) % numberOfPoints; length += path[i].distance(path[j]); } return length; }; Util.isPointInsidePolygon = function(point, polygon) { var i, isPointInPolygon, j, numberOfVertices, pix, piy, pjx, pjy, x, y, _i; isPointInPolygon = false; x = point.x; y = point.y; numberOfVertices = polygon.length; j = numberOfVertices - 1; for (i = _i = 0; 0 <= numberOfVertices ? _i < numberOfVertices : _i > numberOfVertices; i = 0 <= numberOfVertices ? ++_i : --_i) { pix = polygon[i].x; piy = polygon[i].y; pjx = polygon[j].x; pjy = polygon[j].y; if (((piy > y) !== (pjy > y)) && (x < ((pjx - pix) * (y - piy) / (pjy - piy) + pix))) { isPointInPolygon = !isPointInPolygon; } j = i; } return isPointInPolygon; }; Util.isGearInsidePolygon = function(gear, polygon) { var edgePointAtAngle, edgePoints, i, _this = this; edgePointAtAngle = function(angle) { return gear.location.plus(Point.polar(angle, gear.innerRadius)); }; edgePoints = (function() { var _i, _results; _results = []; for (i = _i = 0; _i < 8; i = ++_i) { _results.push(edgePointAtAngle(0.25 * Math.PI * i)); } return _results; })(); return edgePoints.every(function(p) { return _this.isPointInsidePolygon(p, polygon); }); }; Util.findGearsInsidePolygon = function(polygon, gears) { var gear, id, _results; _results = []; for (id in gears) { if (!__hasProp.call(gears, id)) continue; gear = gears[id]; if (this.isGearInsidePolygon(gear, polygon)) { _results.push(gear); } } return _results; }; Util.doesGearIntersectLineSegment = function(gear, segment) { return segment.distanceToPoint(gear.location) < (gear.pitchRadius + Util.EPSILON); }; Util.findGearsIntersectingSegment = function(gears, segment) { var gear, id, _results; _results = []; for (id in gears) { if (!__hasProp.call(gears, id)) continue; gear = gears[id]; if (this.doesGearIntersectLineSegment(gear, segment)) { _results.push(gear); } } return _results; }; Util.pointPathDistance = function(point, path, isPathClosed) { var d, distance, finalIndex, i, j, numberOfPoints, segment, _i; if (isPathClosed == null) { isPathClosed = true; } distance = Number.MAX_VALUE; numberOfPoints = path.length; finalIndex = numberOfPoints - (isPathClosed ? 0 : 1); for (i = _i = 0; 0 <= finalIndex ? _i < finalIndex : _i > finalIndex; i = 0 <= finalIndex ? ++_i : --_i) { j = (i + 1) % numberOfPoints; segment = new LineSegment(path[i], path[j]); d = Math.max(0, segment.distanceToPoint(point)); distance = Math.min(distance, d); } return distance; }; Util.findNearestIntersectingGear = function(gears, lineSegment, ignoredGearIds) { var intersectingGear, intersectingGears, _i, _len, _this = this; if (ignoredGearIds == null) { ignoredGearIds = {}; } intersectingGears = this.findGearsIntersectingSegment(gears, lineSegment); intersectingGears.sort(function(g1, g2) { return g1.distanceToPoint(lineSegment.start) - g2.distanceToPoint(lineSegment.start); }); for (_i = 0, _len = intersectingGears.length; _i < _len; _i++) { intersectingGear = intersectingGears[_i]; if (!(intersectingGear.id in ignoredGearIds)) { return intersectingGear; } } return null; }; Util.findDirection = function(polygon) { var doubleArea, i, j, numberOfPoints, _i; numberOfPoints = polygon.length; doubleArea = 0; for (i = _i = 0; 0 <= numberOfPoints ? _i < numberOfPoints : _i > numberOfPoints; i = 0 <= numberOfPoints ? ++_i : --_i) { j = (i + 1) % numberOfPoints; doubleArea += polygon[i].x * polygon[j].y; doubleArea -= polygon[i].y * polygon[j].x; } if (doubleArea > 0) { return this.Direction.CLOCKWISE; } else { return this.Direction.COUNTER_CLOCKWISE; } }; Util.findTangentPoints = function(p, c, r) { var alpha, beta, d, l, tangentPoints; tangentPoints = {}; d = p.distance(c); if (Math.abs(d - r) < Util.EPSILON) { tangentPoints[this.Side.RIGHT] = p.clone(); tangentPoints[this.Side.LEFT] = p.clone(); } else { l = Math.sqrt(d * d - r * r); alpha = Math.atan2(c.y - p.y, c.x - p.x); beta = Math.asin(r / d); tangentPoints[this.Side.RIGHT] = p.plus(Point.polar(alpha + beta, l)); tangentPoints[this.Side.LEFT] = p.plus(Point.polar(alpha - beta, l)); } return tangentPoints; }; Util.findGearTangentPoints = function(p, gear) { return this.findTangentPoints(p, gear.location, gear.pitchRadius); }; Util.findExternalTangents = function(centers, radii) { var angle, largest, o1, o2, offset1, offset2, r1, r2, r3, ratio, tangentLine1, tangentLine2, tangentLines, tangentPoints, tpl, tpr; largest = radii[0] >= radii[1] ? 0 : 1; o1 = centers[largest]; o2 = centers[1 - largest]; r1 = radii[largest]; r2 = radii[1 - largest]; r3 = r1 - r2; if (r3 === 0) { tangentPoints = {}; tangentPoints[this.Side.LEFT] = o1; tangentPoints[this.Side.RIGHT] = o1; angle = Math.atan2(o2.y - o1.y, o2.x - o1.x); offset1 = Point.polar(angle + 0.5 * Math.PI, r1); offset2 = Point.polar(angle - 0.5 * Math.PI, r1); } else { tangentPoints = this.findTangentPoints(o2, o1, r3); ratio = r2 / r3; tpl = tangentPoints[this.Side.LEFT]; tpr = tangentPoints[this.Side.RIGHT]; offset1 = tpl.minus(o1).times(ratio); offset2 = tpr.minus(o1).times(ratio); } tangentLine1 = [tangentPoints[this.Side.LEFT].plus(offset1), o2.plus(offset1)]; tangentLine2 = [tangentPoints[this.Side.RIGHT].plus(offset2), o2.plus(offset2)]; tangentLines = {}; if (o1 === centers[0]) { tangentLines[this.Side.RIGHT] = new LineSegment(tangentLine1[0], tangentLine1[1]); tangentLines[this.Side.LEFT] = new LineSegment(tangentLine2[0], tangentLine2[1]); } else { tangentLines[this.Side.RIGHT] = new LineSegment(tangentLine2[1], tangentLine2[0]); tangentLines[this.Side.LEFT] = new LineSegment(tangentLine1[1], tangentLine1[0]); } return tangentLines; }; Util.findInternalTangents = function(centers, radii) { var largest, o1, o2, offset1, offset2, r1, r2, r3, ratio, tangentLine1, tangentLine2, tangentLines, tangentPoints, tpl, tpr; largest = radii[0] >= radii[1] ? 0 : 1; o1 = centers[largest]; o2 = centers[1 - largest]; r1 = radii[largest]; r2 = radii[1 - largest]; r3 = r1 + r2; tangentPoints = this.findTangentPoints(o2, o1, r3); ratio = r2 / r3; tpl = tangentPoints[this.Side.LEFT]; tpr = tangentPoints[this.Side.RIGHT]; offset1 = o1.minus(tpl).times(ratio); offset2 = o1.minus(tpr).times(ratio); tangentLine1 = [tpl.plus(offset1), o2.plus(offset1)]; tangentLine2 = [tpr.plus(offset2), o2.plus(offset2)]; tangentLines = {}; if (o1 === centers[0]) { tangentLines[this.Side.RIGHT] = new LineSegment(tangentLine1[0], tangentLine1[1]); tangentLines[this.Side.LEFT] = new LineSegment(tangentLine2[0], tangentLine2[1]); } else { tangentLines[this.Side.RIGHT] = new LineSegment(tangentLine1[1], tangentLine1[0]); tangentLines[this.Side.LEFT] = new LineSegment(tangentLine2[1], tangentLine2[0]); } return tangentLines; }; Util.findExternalTangentsOfGears = function(gear1, gear2) { return this.findExternalTangents([gear1.location, gear2.location], [gear1.pitchRadius, gear2.pitchRadius]); }; Util.findInternalTangentsOfGears = function(gear1, gear2) { return this.findInternalTangents([gear1.location, gear2.location], [gear1.pitchRadius, gear2.pitchRadius]); }; Util.findTangentLine = function(gear1, gear2, innerGearIds, direction) { var gear1isInnerGear, side; gear1isInnerGear = gear1.id in innerGearIds; if (gear1isInnerGear === (direction === this.Direction.CLOCKWISE)) { side = this.Side.LEFT; } else { side = this.Side.RIGHT; } if (gear1isInnerGear === (gear2.id in innerGearIds)) { return this.findExternalTangentsOfGears(gear1, gear2)[side]; } else { return this.findInternalTangentsOfGears(gear1, gear2)[side]; } }; Util.findAllSimplePathsForNodes = function(turningObjects, goalNode, nodesVisited) { var currentNode, neighbor, neighborId, paths, updatedNodesVisited, _ref; paths = []; currentNode = nodesVisited[nodesVisited.length - 1]; _ref = currentNode.connections; for (neighborId in _ref) { if (!__hasProp.call(_ref, neighborId)) continue; neighbor = turningObjects[neighborId]; if (__indexOf.call(nodesVisited, neighbor) < 0) { updatedNodesVisited = nodesVisited.slice(0); updatedNodesVisited.push(neighbor); if (neighbor === goalNode) { paths.push(updatedNodesVisited); } else { paths = paths.concat(this.findAllSimplePathsForNodes(turningObjects, goalNode, updatedNodesVisited)); } } } return paths; }; Util.findAllSimplePathsBetweenNeighbors = function(turningObjects) { var i, id, j, nodes, paths, turningObject, _i, _j, _k, _ref, _ref1, _ref2, _ref3; paths = []; nodes = (function() { var _results; _results = []; for (id in turningObjects) { if (!__hasProp.call(turningObjects, id)) continue; turningObject = turningObjects[id]; _results.push(turningObject); } return _results; })(); if (nodes.length < 2) { return []; } for (i = _i = 0, _ref = nodes.length - 1; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { for (j = _j = _ref1 = i + 1, _ref2 = nodes.length; _ref1 <= _ref2 ? _j < _ref2 : _j > _ref2; j = _ref1 <= _ref2 ? ++_j : --_j) { if (nodes[i].connections[nodes[j].id] != null) { paths = paths.concat(this.findAllSimplePathsForNodes(turningObjects, nodes[j], [nodes[i]])); } } } for (i = _k = 0, _ref3 = paths.length; 0 <= _ref3 ? _k < _ref3 : _k > _ref3; i = 0 <= _ref3 ? ++_k : --_k) { paths.push(paths[i].slice(0).reverse()); } return paths; }; Util.sendGetRequest = function(url) { var request; request = new XMLHttpRequest(); request.open("GET", url, false); request.send(null); return request.responseText; }; Util.sendPostRequest = function(data, url, callback) { var request; request = new XMLHttpRequest(); request.open("POST", url, true); request.setRequestHeader("Content-type", "application/json; charset=UTF-8"); request.onload = callback; return request.send(data); }; return Util; })(); window.gearsketch.Util = Util; (function() { var lastTime, vendor, vendors, _i, _len; lastTime = 0; vendors = ["ms", "moz", "webkit", "o"]; for (_i = 0, _len = vendors.length; _i < _len; _i++) { vendor = vendors[_i]; if (!(!window.requestAnimationFrame)) { continue; } window.requestAnimationFrame = window[vendor + "RequestAnimationFrame"]; window.cancelAnimationFrame = window[vendor + "CancelAnimationFrame"] || window[vendor + "CancelRequestAnimationFrame"]; } if (!window.requestAnimationFrame) { window.requestAnimationFrame = function(callback) { var currTime, id, timeToCall; currTime = new Date().getTime(); timeToCall = Math.max(0, 16 - (currTime - lastTime)); id = window.setTimeout((function() { return callback(currTime + timeToCall); }), timeToCall); lastTime = currTime + timeToCall; return id; }; } if (!window.cancelAnimationFrame) { return window.cancelAnimationFrame = function(id) { return clearTimeout(id); }; } })(); (function() { if ((Function.prototype.name == null) && (Object.defineProperty != null)) { return Object.defineProperty(Function.prototype, "name", { get: function() { var funcNameRegex, results; funcNameRegex = /function\s([^(]{1,})\(/; results = funcNameRegex.exec(this.toString()); if ((results != null) && results.length > 1) { return results[1].trim(); } else { return ""; } }, set: function(value) {} }); } })(); }).call(this); /* //@ sourceMappingURL=gearsketch_util.map */