// Generated by CoffeeScript 1.6.3
|
|
(function() {
|
|
"use strict";
|
|
var ArcSegment, Board, Chain, FPS, Gear, GearSketch, LineSegment, MIN_GEAR_TEETH, MIN_MOMENTUM, Point, Util,
|
|
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
|
__hasProp = {}.hasOwnProperty,
|
|
__slice = [].slice;
|
|
|
|
Point = window.gearsketch.Point;
|
|
|
|
ArcSegment = window.gearsketch.ArcSegment;
|
|
|
|
LineSegment = window.gearsketch.LineSegment;
|
|
|
|
Util = window.gearsketch.Util;
|
|
|
|
Gear = window.gearsketch.model.Gear;
|
|
|
|
Chain = window.gearsketch.model.Chain;
|
|
|
|
Board = window.gearsketch.model.Board;
|
|
|
|
FPS = 60;
|
|
|
|
MIN_GEAR_TEETH = 8;
|
|
|
|
MIN_MOMENTUM = 0.2;
|
|
|
|
GearSketch = (function() {
|
|
var AXIS_RADIUS, BUTTON_INFO, MODULE, MovementAction, MovementType;
|
|
|
|
MODULE = Util.MODULE;
|
|
|
|
AXIS_RADIUS = Util.AXIS_RADIUS;
|
|
|
|
BUTTON_INFO = [["gearButton", "GearIcon.png"], ["chainButton", "ChainIcon.png"], ["momentumButton", "MomentumIcon.png"], ["playButton", "PlayIcon.png"], ["clearButton", "ClearIcon.png"], ["cloudButton", "CloudIcon.png"], ["helpButton", "HelpIcon.png"]];
|
|
|
|
MovementAction = {
|
|
PEN_DOWN: "penDown",
|
|
PEN_UP: "penUp",
|
|
PEN_TAP: "penTap"
|
|
};
|
|
|
|
MovementType = {
|
|
STRAIGHT: "straight",
|
|
CIRCLE: "circle",
|
|
LEFT_HALF_CIRCLE: "leftHalfCircle",
|
|
RIGHT_HALF_CIRCLE: "rightHalfCircle"
|
|
};
|
|
|
|
GearSketch.prototype.buttons = {};
|
|
|
|
GearSketch.prototype.loadedButtons = 0;
|
|
|
|
GearSketch.prototype.areButtonsLoaded = false;
|
|
|
|
GearSketch.prototype.selectedButton = BUTTON_INFO[0][0];
|
|
|
|
GearSketch.prototype.gearImages = {};
|
|
|
|
GearSketch.prototype.isPenDown = false;
|
|
|
|
GearSketch.prototype.stroke = [];
|
|
|
|
GearSketch.prototype.offset = new Point();
|
|
|
|
GearSketch.prototype.message = "";
|
|
|
|
GearSketch.prototype.messageColor = "black";
|
|
|
|
GearSketch.prototype.pointerLocation = new Point();
|
|
|
|
GearSketch.prototype.currentDemoMovement = 0;
|
|
|
|
GearSketch.prototype.movementCompletion = 0;
|
|
|
|
GearSketch.prototype.restTimer = 0;
|
|
|
|
function GearSketch(showButtons) {
|
|
if (showButtons == null) {
|
|
showButtons = true;
|
|
}
|
|
this.update = __bind(this.update, this);
|
|
this.updateAndDrawNoRAF = __bind(this.updateAndDrawNoRAF, this);
|
|
this.updateAndDraw = __bind(this.updateAndDraw, this);
|
|
this.loadButtons();
|
|
this.showButtons = showButtons;
|
|
this.loadDemoPointer();
|
|
this.loadBoard();
|
|
this.canvas = document.getElementById("gearsketch_canvas");
|
|
this.canvasOffsetX = this.canvas.getBoundingClientRect().left;
|
|
this.canvasOffsetY = this.canvas.getBoundingClientRect().top;
|
|
this.isDemoPlaying = false;
|
|
this.updateCanvasSize();
|
|
this.addCanvasListeners();
|
|
this.lastUpdateTime = new Date().getTime();
|
|
this.updateAndDrawNoRAF();
|
|
}
|
|
|
|
GearSketch.prototype.buttonLoaded = function() {
|
|
this.loadedButtons++;
|
|
if (this.loadedButtons === BUTTON_INFO.length) {
|
|
return this.areButtonsLoaded = true;
|
|
}
|
|
};
|
|
|
|
GearSketch.prototype.loadButtons = function() {
|
|
var button, file, name, x, y, _i, _len, _ref, _results,
|
|
_this = this;
|
|
x = y = 20;
|
|
_results = [];
|
|
for (_i = 0, _len = BUTTON_INFO.length; _i < _len; _i++) {
|
|
_ref = BUTTON_INFO[_i], name = _ref[0], file = _ref[1];
|
|
button = new Image();
|
|
button.name = name;
|
|
button.onload = function() {
|
|
return _this.buttonLoaded();
|
|
};
|
|
button.src = "img/" + file;
|
|
button.location = new Point(x, y);
|
|
button.padding = 3;
|
|
this.buttons[name] = button;
|
|
_results.push(x += 80);
|
|
}
|
|
return _results;
|
|
};
|
|
|
|
GearSketch.prototype.loadDemoPointer = function() {
|
|
var image,
|
|
_this = this;
|
|
image = new Image();
|
|
image.onload = function() {
|
|
return _this.pointerImage = image;
|
|
};
|
|
return image.src = "img/hand.png";
|
|
};
|
|
|
|
GearSketch.prototype.loadBoard = function() {
|
|
var boardJSON, error, gear, hash, id, _ref, _results;
|
|
this.board = (function() {
|
|
if (parent.location.hash.length > 1) {
|
|
try {
|
|
hash = parent.location.hash.substr(1);
|
|
boardJSON = Util.sendGetRequest("boards/" + hash + ".txt");
|
|
return Board.fromObject(JSON.parse(boardJSON));
|
|
} catch (_error) {
|
|
error = _error;
|
|
this.displayMessage("Error: could not load board", "red", 2000);
|
|
return new Board();
|
|
}
|
|
} else {
|
|
return new Board();
|
|
}
|
|
}).call(this);
|
|
_ref = this.board.getGears();
|
|
_results = [];
|
|
for (id in _ref) {
|
|
gear = _ref[id];
|
|
_results.push(this.addGearImage(gear));
|
|
}
|
|
return _results;
|
|
};
|
|
|
|
GearSketch.prototype.displayMessage = function(message, color, time) {
|
|
var _this = this;
|
|
if (color == null) {
|
|
color = "black";
|
|
}
|
|
if (time == null) {
|
|
time = 0;
|
|
}
|
|
this.message = message;
|
|
this.messageColor = color;
|
|
if (time > 0) {
|
|
return setTimeout((function() {
|
|
return _this.clearMessage();
|
|
}), time);
|
|
}
|
|
};
|
|
|
|
GearSketch.prototype.clearMessage = function() {
|
|
return this.message = "";
|
|
};
|
|
|
|
GearSketch.prototype.selectButton = function(buttonName) {
|
|
return this.selectedButton = buttonName;
|
|
};
|
|
|
|
GearSketch.prototype.shouldShowButtons = function() {
|
|
return this.showButtons || this.isDemoPlaying;
|
|
};
|
|
|
|
GearSketch.prototype.addCanvasListeners = function() {
|
|
var canvasEventHandler,
|
|
_this = this;
|
|
canvasEventHandler = Hammer(this.canvas, {
|
|
drag_min_distance: 1
|
|
});
|
|
canvasEventHandler.on("touch", (function(e) {
|
|
return _this.forwardPenDownEvent.call(_this, e);
|
|
}));
|
|
canvasEventHandler.on("drag", (function(e) {
|
|
return _this.forwardPenMoveEvent.call(_this, e);
|
|
}));
|
|
return canvasEventHandler.on("release", (function(e) {
|
|
return _this.forwardPenUpEvent.call(_this, e);
|
|
}));
|
|
};
|
|
|
|
GearSketch.prototype.forwardPenDownEvent = function(event) {
|
|
var x, y;
|
|
event.gesture.preventDefault();
|
|
if (this.isDemoPlaying) {
|
|
return this.stopDemo();
|
|
} else {
|
|
x = event.gesture.center.pageX - this.canvasOffsetX;
|
|
y = event.gesture.center.pageY - this.canvasOffsetY;
|
|
return this.handlePenDown(x, y);
|
|
}
|
|
};
|
|
|
|
GearSketch.prototype.forwardPenMoveEvent = function(event) {
|
|
var x, y;
|
|
event.gesture.preventDefault();
|
|
if (!this.isDemoPlaying) {
|
|
x = event.gesture.center.pageX - this.canvasOffsetX;
|
|
y = event.gesture.center.pageY - this.canvasOffsetY;
|
|
return this.handlePenMove(x, y);
|
|
}
|
|
};
|
|
|
|
GearSketch.prototype.forwardPenUpEvent = function(event) {
|
|
if (!this.isDemoPlaying) {
|
|
return this.handlePenUp();
|
|
}
|
|
};
|
|
|
|
GearSketch.prototype.handlePenDown = function(x, y) {
|
|
var button, point;
|
|
point = new Point(x, y);
|
|
if (this.isPenDown) {
|
|
return this.handlePenUp();
|
|
} else {
|
|
button = this.getButtonAt(x, y);
|
|
if (button) {
|
|
if (button.name === "clearButton") {
|
|
parent.location.hash = "";
|
|
return this.board.clear();
|
|
} else if (button.name === "cloudButton") {
|
|
return this.uploadBoard();
|
|
} else if (button.name === "helpButton") {
|
|
return this.playDemo();
|
|
} else {
|
|
return this.selectButton(button.name);
|
|
}
|
|
} else if (this.selectedButton === "gearButton") {
|
|
this.selectedGear = this.board.getTopLevelGearAt(point);
|
|
if (this.selectedGear != null) {
|
|
this.offset = point.minus(this.selectedGear.location);
|
|
} else if (this.board.getGearAt(point) == null) {
|
|
this.stroke.push(point);
|
|
}
|
|
return this.isPenDown = true;
|
|
} else if (this.selectedButton === "chainButton") {
|
|
this.stroke.push(point);
|
|
return this.isPenDown = true;
|
|
} else if (this.selectedButton === "momentumButton") {
|
|
this.selectedGear = this.board.getGearAt(point);
|
|
if (this.selectedGear) {
|
|
this.selectedGear.momentum = 0;
|
|
this.selectedGearMomentum = this.calculateMomentumFromCoords(this.selectedGear, x, y);
|
|
}
|
|
return this.isPenDown = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
GearSketch.prototype.handlePenMove = function(x, y) {
|
|
var canPlaceGear, goalLocation, point;
|
|
point = new Point(x, y);
|
|
if (this.isPenDown) {
|
|
if (this.selectedButton === "gearButton") {
|
|
if (this.selectedGear) {
|
|
goalLocation = point.minus(this.offset);
|
|
canPlaceGear = this.board.placeGear(this.selectedGear, goalLocation);
|
|
if (canPlaceGear) {
|
|
return this.goalLocationGear = null;
|
|
} else {
|
|
return this.goalLocationGear = new Gear(goalLocation, this.selectedGear.rotation, this.selectedGear.numberOfTeeth, this.selectedGear.id, this.selectedGear.hue);
|
|
}
|
|
} else if (this.stroke.length > 0) {
|
|
return this.stroke.push(point);
|
|
}
|
|
} else if (this.selectedButton === "chainButton") {
|
|
return this.stroke.push(point);
|
|
} else if (this.selectedButton === "momentumButton") {
|
|
if (this.selectedGear) {
|
|
return this.selectedGearMomentum = this.calculateMomentumFromCoords(this.selectedGear, x, y);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
GearSketch.prototype.handlePenUp = function() {
|
|
if (this.isPenDown) {
|
|
if (this.selectedButton === "gearButton") {
|
|
if (!((this.selectedGear != null) || this.stroke.length === 0)) {
|
|
this.processGearStroke();
|
|
}
|
|
} else if (this.selectedButton === "chainButton") {
|
|
this.processChainStroke();
|
|
} else if (this.selectedButton === "momentumButton") {
|
|
if (this.selectedGear) {
|
|
if (Math.abs(this.selectedGearMomentum) > MIN_MOMENTUM) {
|
|
this.selectedGear.momentum = this.selectedGearMomentum;
|
|
} else {
|
|
this.selectedGear.momentum = 0;
|
|
}
|
|
}
|
|
this.selectedGearMomentum = 0;
|
|
}
|
|
this.selectedGear = null;
|
|
this.goalLocationGear = null;
|
|
return this.isPenDown = false;
|
|
}
|
|
};
|
|
|
|
GearSketch.prototype.isButtonAt = function(x, y, button) {
|
|
return x > button.location.x && x < button.location.x + button.width + 2 * button.padding && y > button.location.y && y < button.location.y + button.height + 2 * button.padding;
|
|
};
|
|
|
|
GearSketch.prototype.getButtonAt = function(x, y) {
|
|
var button, buttonName, _ref;
|
|
if (!this.shouldShowButtons()) {
|
|
return null;
|
|
}
|
|
_ref = this.buttons;
|
|
for (buttonName in _ref) {
|
|
if (!__hasProp.call(_ref, buttonName)) continue;
|
|
button = _ref[buttonName];
|
|
if (this.isButtonAt(x, y, button)) {
|
|
return button;
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
GearSketch.prototype.normalizeStroke = function(stroke) {
|
|
var MIN_POINT_DISTANCE, normalizedStroke, p1, p2, strokeTail, _i, _len;
|
|
MIN_POINT_DISTANCE = 10;
|
|
normalizedStroke = [];
|
|
if (stroke.length > 0) {
|
|
p1 = stroke[0], strokeTail = 2 <= stroke.length ? __slice.call(stroke, 1) : [];
|
|
normalizedStroke.push(p1);
|
|
for (_i = 0, _len = strokeTail.length; _i < _len; _i++) {
|
|
p2 = strokeTail[_i];
|
|
if (p1.distance(p2) > MIN_POINT_DISTANCE) {
|
|
normalizedStroke.push(p2);
|
|
p1 = p2;
|
|
}
|
|
}
|
|
}
|
|
return normalizedStroke;
|
|
};
|
|
|
|
GearSketch.prototype.createGearFromStroke = function(stroke) {
|
|
var area, doubleArea, height, i, idealTrueAreaRatio, j, maxX, maxY, minX, minY, numberOfPoints, p, radius, sumX, sumY, t, width, x, y, _i, _j, _len;
|
|
numberOfPoints = stroke.length;
|
|
if (numberOfPoints > 0) {
|
|
sumX = 0;
|
|
sumY = 0;
|
|
minX = Number.MAX_VALUE;
|
|
maxX = Number.MIN_VALUE;
|
|
minY = Number.MAX_VALUE;
|
|
maxY = Number.MIN_VALUE;
|
|
for (_i = 0, _len = stroke.length; _i < _len; _i++) {
|
|
p = stroke[_i];
|
|
sumX += p.x;
|
|
sumY += p.y;
|
|
minX = Math.min(minX, p.x);
|
|
maxX = Math.max(maxX, p.x);
|
|
minY = Math.min(minY, p.y);
|
|
maxY = Math.max(maxY, p.y);
|
|
}
|
|
width = maxX - minX;
|
|
height = maxY - minY;
|
|
t = Math.floor(0.5 * (width + height) / MODULE);
|
|
doubleArea = 0;
|
|
for (i = _j = 0; 0 <= numberOfPoints ? _j < numberOfPoints : _j > numberOfPoints; i = 0 <= numberOfPoints ? ++_j : --_j) {
|
|
j = (i + 1) % numberOfPoints;
|
|
doubleArea += stroke[i].x * stroke[j].y;
|
|
doubleArea -= stroke[i].y * stroke[j].x;
|
|
}
|
|
area = Math.abs(doubleArea) / 2;
|
|
radius = 0.25 * ((maxX - minX) + (maxY - minY));
|
|
idealTrueAreaRatio = (Math.PI * Math.pow(radius, 2)) / area;
|
|
if (idealTrueAreaRatio > 0.80 && idealTrueAreaRatio < 1.20 && t > MIN_GEAR_TEETH) {
|
|
x = sumX / numberOfPoints;
|
|
y = sumY / numberOfPoints;
|
|
return new Gear(new Point(x, y), 0, t);
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
GearSketch.prototype.removeStrokedGears = function(stroke) {
|
|
var gear, id, _ref, _results;
|
|
_ref = this.board.getTopLevelGears();
|
|
_results = [];
|
|
for (id in _ref) {
|
|
if (!__hasProp.call(_ref, id)) continue;
|
|
gear = _ref[id];
|
|
if (Util.pointPathDistance(gear.location, stroke, false) < gear.innerRadius) {
|
|
_results.push(this.board.removeGear(gear));
|
|
} else {
|
|
_results.push(void 0);
|
|
}
|
|
}
|
|
return _results;
|
|
};
|
|
|
|
GearSketch.prototype.processGearStroke = function() {
|
|
var gear, isGearAdded, normalizedStroke;
|
|
normalizedStroke = this.normalizeStroke(this.stroke);
|
|
gear = this.createGearFromStroke(normalizedStroke);
|
|
if (gear != null) {
|
|
isGearAdded = this.board.addGear(gear);
|
|
if (isGearAdded && !(gear.numberOfTeeth in this.gearImages)) {
|
|
this.addGearImage(gear);
|
|
}
|
|
} else {
|
|
this.removeStrokedGears(normalizedStroke);
|
|
}
|
|
return this.stroke = [];
|
|
};
|
|
|
|
GearSketch.prototype.gearImageLoaded = function(numberOfTeeth, image) {
|
|
return this.gearImages[numberOfTeeth] = image;
|
|
};
|
|
|
|
GearSketch.prototype.addGearImage = function(gear) {
|
|
var ctx, gearCanvas, gearCopy, image, size,
|
|
_this = this;
|
|
gearCanvas = document.createElement("canvas");
|
|
size = 2 * (gear.outerRadius + MODULE);
|
|
gearCanvas.height = size;
|
|
gearCanvas.width = size;
|
|
ctx = gearCanvas.getContext("2d");
|
|
gearCopy = new Gear(new Point(0.5 * size, 0.5 * size), 0, gear.numberOfTeeth, gear.id, null, null, null, null, gear.hue);
|
|
this.drawGear(ctx, gearCopy);
|
|
image = new Image();
|
|
image.onload = function() {
|
|
return _this.gearImageLoaded(gear.numberOfTeeth, image);
|
|
};
|
|
return image.src = gearCanvas.toDataURL("image/png");
|
|
};
|
|
|
|
GearSketch.prototype.removeStrokedChains = function(stroke) {
|
|
var chain, id, _ref, _results;
|
|
_ref = this.board.getChains();
|
|
_results = [];
|
|
for (id in _ref) {
|
|
if (!__hasProp.call(_ref, id)) continue;
|
|
chain = _ref[id];
|
|
if (chain.intersectsPath(stroke)) {
|
|
_results.push(this.board.removeChain(chain));
|
|
} else {
|
|
_results.push(void 0);
|
|
}
|
|
}
|
|
return _results;
|
|
};
|
|
|
|
GearSketch.prototype.processChainStroke = function() {
|
|
var chain, gearsInChain, normalizedStroke;
|
|
normalizedStroke = this.normalizeStroke(this.stroke);
|
|
this.stroke = [];
|
|
gearsInChain = Util.findGearsInsidePolygon(normalizedStroke, this.board.getGears());
|
|
if (normalizedStroke.length >= 3 && gearsInChain.length > 0) {
|
|
chain = new Chain(normalizedStroke);
|
|
return this.board.addChain(chain);
|
|
} else if (normalizedStroke.length >= 2) {
|
|
return this.removeStrokedChains(normalizedStroke);
|
|
}
|
|
};
|
|
|
|
GearSketch.prototype.calculateMomentumFromCoords = function(gear, x, y) {
|
|
var angle, angleFromTop;
|
|
angle = Math.atan2(y - gear.location.y, x - gear.location.x);
|
|
angleFromTop = angle + 0.5 * Math.PI;
|
|
if (angleFromTop < Math.PI) {
|
|
return angleFromTop;
|
|
} else {
|
|
return angleFromTop - 2 * Math.PI;
|
|
}
|
|
};
|
|
|
|
GearSketch.prototype.updateAndDraw = function() {
|
|
var _this = this;
|
|
return setTimeout((function() {
|
|
requestAnimationFrame(_this.updateAndDraw);
|
|
_this.update();
|
|
return _this.draw();
|
|
}), 1000 / FPS);
|
|
};
|
|
|
|
GearSketch.prototype.updateAndDrawNoRAF = function() {
|
|
var _this = this;
|
|
this.update();
|
|
this.draw();
|
|
return setTimeout((function() {
|
|
return _this.updateAndDrawNoRAF();
|
|
}), 1000 / FPS);
|
|
};
|
|
|
|
GearSketch.prototype.update = function() {
|
|
var delta, updateTime;
|
|
updateTime = new Date().getTime();
|
|
delta = updateTime - this.lastUpdateTime;
|
|
if (this.selectedButton === "playButton") {
|
|
this.board.rotateAllTurningObjects(delta);
|
|
}
|
|
if (this.isDemoPlaying) {
|
|
this.updateDemo(delta);
|
|
}
|
|
return this.lastUpdateTime = updateTime;
|
|
};
|
|
|
|
GearSketch.prototype.drawGear = function(ctx, gear, color) {
|
|
var angleStep, gearImage, i, innerPoints, numberOfTeeth, outerPoints, r, rotation, x, y, _i, _j, _k, _ref, _ref1;
|
|
if (color == null) {
|
|
color = "black";
|
|
}
|
|
_ref = gear.location, x = _ref.x, y = _ref.y;
|
|
rotation = gear.rotation;
|
|
numberOfTeeth = gear.numberOfTeeth;
|
|
gearImage = this.gearImages[gear.numberOfTeeth];
|
|
if (color === "black" && (gearImage != null)) {
|
|
gearImage = this.gearImages[gear.numberOfTeeth];
|
|
ctx.save();
|
|
ctx.translate(x, y);
|
|
ctx.rotate(rotation);
|
|
ctx.drawImage(gearImage, -0.5 * gearImage.width, -0.5 * gearImage.height);
|
|
ctx.restore();
|
|
return;
|
|
}
|
|
angleStep = 2 * Math.PI / numberOfTeeth;
|
|
innerPoints = [];
|
|
outerPoints = [];
|
|
for (i = _i = 0; 0 <= numberOfTeeth ? _i < numberOfTeeth : _i > numberOfTeeth; i = 0 <= numberOfTeeth ? ++_i : --_i) {
|
|
for (r = _j = 0; _j < 4; r = ++_j) {
|
|
if (r === 0 || r === 3) {
|
|
innerPoints.push(Point.polar((i + 0.25 * r) * angleStep, gear.innerRadius));
|
|
} else {
|
|
outerPoints.push(Point.polar((i + 0.25 * r) * angleStep, gear.outerRadius));
|
|
}
|
|
}
|
|
}
|
|
ctx.save();
|
|
|
|
if (color === "black") {
|
|
if (!gear.hue) {
|
|
gear.hue = Math.floor(Math.random()*360);
|
|
}
|
|
ctx.fillStyle = "hsla(" + gear.hue + ", 85%, 60%, 0.8)";
|
|
} else {
|
|
ctx.fillStyle = "hsla(0, 0%, 90%, 0.2)";
|
|
}
|
|
|
|
ctx.strokeStyle = color;
|
|
ctx.lineWidth = 2;
|
|
ctx.translate(x, y);
|
|
ctx.rotate(rotation);
|
|
ctx.beginPath();
|
|
ctx.moveTo(gear.innerRadius, 0);
|
|
for (i = _k = 0, _ref1 = numberOfTeeth * 2; 0 <= _ref1 ? _k < _ref1 : _k > _ref1; i = 0 <= _ref1 ? ++_k : --_k) {
|
|
if (i % 2 === 0) {
|
|
ctx.lineTo(innerPoints[i].x, innerPoints[i].y);
|
|
ctx.lineTo(outerPoints[i].x, outerPoints[i].y);
|
|
} else {
|
|
ctx.lineTo(outerPoints[i].x, outerPoints[i].y);
|
|
ctx.lineTo(innerPoints[i].x, innerPoints[i].y);
|
|
}
|
|
}
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
ctx.beginPath();
|
|
ctx.moveTo(AXIS_RADIUS, 0);
|
|
ctx.arc(0, 0, AXIS_RADIUS, 0, 2 * Math.PI, true);
|
|
ctx.closePath();
|
|
ctx.stroke();
|
|
ctx.beginPath();
|
|
ctx.moveTo(AXIS_RADIUS, 0);
|
|
ctx.lineTo(gear.innerRadius, 0);
|
|
ctx.closePath();
|
|
ctx.stroke();
|
|
return ctx.restore();
|
|
};
|
|
|
|
GearSketch.prototype.drawButton = function(ctx, button) {
|
|
var height, padding, radius, width, x, y, _ref;
|
|
_ref = button.location, x = _ref.x, y = _ref.y;
|
|
padding = button.padding;
|
|
ctx.save();
|
|
ctx.translate(x, y);
|
|
ctx.beginPath();
|
|
radius = 10;
|
|
width = button.width + 2 * padding;
|
|
height = button.height + 2 * padding;
|
|
ctx.moveTo(radius, 0);
|
|
ctx.lineTo(width - radius, 0);
|
|
ctx.quadraticCurveTo(width, 0, width, radius);
|
|
ctx.lineTo(width, height - radius);
|
|
ctx.quadraticCurveTo(width, height, width - radius, height);
|
|
ctx.lineTo(radius, height);
|
|
ctx.quadraticCurveTo(0, height, 0, height - radius);
|
|
ctx.lineTo(0, radius);
|
|
ctx.quadraticCurveTo(0, 0, radius, 0);
|
|
if (button.name === this.selectedButton) {
|
|
ctx.fillStyle = "rgba(50, 150, 255, 0.8)";
|
|
} else {
|
|
ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
|
|
}
|
|
ctx.fill();
|
|
ctx.lineWidth = 1;
|
|
ctx.strokeStyle = "black";
|
|
ctx.stroke();
|
|
ctx.drawImage(button, padding, padding);
|
|
return ctx.restore();
|
|
};
|
|
|
|
GearSketch.prototype.drawMomentum = function(ctx, gear, momentum, color) {
|
|
var angle, head, headX, headY, length, p1, p2, pitchRadius, sign, top;
|
|
if (color == null) {
|
|
color = "red";
|
|
}
|
|
pitchRadius = gear.pitchRadius;
|
|
top = new Point(gear.location.x, gear.location.y - pitchRadius);
|
|
ctx.save();
|
|
ctx.lineWidth = 5;
|
|
ctx.lineCap = "round";
|
|
ctx.strokeStyle = color;
|
|
ctx.translate(top.x, top.y);
|
|
ctx.beginPath();
|
|
ctx.arc(0, pitchRadius, pitchRadius, -0.5 * Math.PI, momentum - 0.5 * Math.PI, momentum < 0);
|
|
ctx.stroke();
|
|
length = 15;
|
|
angle = 0.2 * Math.PI;
|
|
headX = -Math.cos(momentum + 0.5 * Math.PI) * pitchRadius;
|
|
headY = pitchRadius - Math.sin(momentum + 0.5 * Math.PI) * pitchRadius;
|
|
head = new Point(headX, headY);
|
|
sign = Util.sign(momentum);
|
|
p1 = head.minus(Point.polar(momentum + angle, sign * length));
|
|
ctx.beginPath();
|
|
ctx.moveTo(headX, headY);
|
|
ctx.lineTo(p1.x, p1.y);
|
|
ctx.stroke();
|
|
p2 = head.minus(Point.polar(momentum - angle, sign * length));
|
|
ctx.beginPath();
|
|
ctx.moveTo(headX, headY);
|
|
ctx.lineTo(p2.x, p2.y);
|
|
ctx.stroke();
|
|
return ctx.restore();
|
|
};
|
|
|
|
GearSketch.prototype.drawChain = function(ctx, chain) {
|
|
var isCounterClockwise, point, segment, _i, _j, _len, _len1, _ref, _ref1;
|
|
ctx.save();
|
|
ctx.lineWidth = Chain.WIDTH;
|
|
ctx.lineCap = "round";
|
|
ctx.strokeStyle = "rgb(0, 0, 255)";
|
|
ctx.moveTo(chain.segments[0].start.x, chain.segments[0].start.y);
|
|
_ref = chain.segments;
|
|
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
|
segment = _ref[_i];
|
|
if (segment instanceof ArcSegment) {
|
|
isCounterClockwise = segment.direction === Util.Direction.COUNTER_CLOCKWISE;
|
|
ctx.beginPath();
|
|
ctx.arc(segment.center.x, segment.center.y, segment.radius, segment.startAngle, segment.endAngle, isCounterClockwise);
|
|
ctx.stroke();
|
|
} else {
|
|
ctx.beginPath();
|
|
ctx.moveTo(segment.start.x, segment.start.y);
|
|
ctx.lineTo(segment.end.x, segment.end.y);
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
ctx.fillStyle = "white";
|
|
_ref1 = chain.findPointsOnChain(25);
|
|
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
|
|
point = _ref1[_j];
|
|
ctx.beginPath();
|
|
ctx.arc(point.x, point.y, 3, 0, 2 * Math.PI, true);
|
|
ctx.fill();
|
|
}
|
|
return ctx.restore();
|
|
};
|
|
|
|
GearSketch.prototype.drawDemoPointer = function(ctx, location) {
|
|
return ctx.drawImage(this.pointerImage, location.x - 0.5 * this.pointerImage.width, location.y);
|
|
};
|
|
|
|
GearSketch.prototype.draw = function() {
|
|
var arrow, arrowsToDraw, buttonName, chain, ctx, gear, i, momentum, shouldDrawChainsAndArrows, sortedGears, _i, _j, _k, _l, _len, _len1, _ref, _ref1, _ref2, _ref3;
|
|
if (this.canvas.getContext != null) {
|
|
this.updateCanvasSize();
|
|
ctx = this.canvas.getContext("2d");
|
|
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
sortedGears = this.board.getGearsSortedByGroupAndLevel();
|
|
arrowsToDraw = [];
|
|
for (i = _i = 0, _ref = sortedGears.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
|
|
gear = sortedGears[i];
|
|
momentum = gear.momentum;
|
|
if (gear === this.selectedGear && this.goalLocationGear) {
|
|
this.drawGear(ctx, gear, "grey");
|
|
if (momentum) {
|
|
arrowsToDraw.push([gear, momentum, "grey"]);
|
|
}
|
|
} else {
|
|
this.drawGear(ctx, gear);
|
|
if (momentum) {
|
|
arrowsToDraw.push([gear, momentum, "red"]);
|
|
}
|
|
}
|
|
shouldDrawChainsAndArrows = (i === sortedGears.length - 1) || (this.board.getLevelScore(gear) !== this.board.getLevelScore(sortedGears[i + 1]));
|
|
if (shouldDrawChainsAndArrows) {
|
|
_ref1 = this.board.getChainsInGroupOnLevel(gear.group, gear.level);
|
|
for (_j = 0, _len = _ref1.length; _j < _len; _j++) {
|
|
chain = _ref1[_j];
|
|
this.drawChain(ctx, chain);
|
|
}
|
|
for (_k = 0, _len1 = arrowsToDraw.length; _k < _len1; _k++) {
|
|
arrow = arrowsToDraw[_k];
|
|
this.drawMomentum(ctx, arrow[0], arrow[1], arrow[2]);
|
|
}
|
|
arrowsToDraw = [];
|
|
}
|
|
}
|
|
if (this.goalLocationGear) {
|
|
this.drawGear(ctx, this.goalLocationGear, "red");
|
|
}
|
|
if ((this.selectedGear != null) && this.selectedGearMomentum) {
|
|
this.drawMomentum(ctx, this.selectedGear, this.selectedGearMomentum);
|
|
}
|
|
if (this.stroke.length > 0) {
|
|
ctx.save();
|
|
if (this.selectedButton === "gearButton") {
|
|
ctx.strokeStyle = "black";
|
|
ctx.lineWidth = 2;
|
|
} else {
|
|
ctx.strokeStyle = "blue";
|
|
ctx.lineWidth = 4;
|
|
}
|
|
ctx.beginPath();
|
|
ctx.moveTo(this.stroke[0].x, this.stroke[0].y);
|
|
for (i = _l = 1, _ref2 = this.stroke.length; 1 <= _ref2 ? _l < _ref2 : _l > _ref2; i = 1 <= _ref2 ? ++_l : --_l) {
|
|
ctx.lineTo(this.stroke[i].x, this.stroke[i].y);
|
|
}
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
}
|
|
if (this.areButtonsLoaded && this.shouldShowButtons()) {
|
|
_ref3 = this.buttons;
|
|
for (buttonName in _ref3) {
|
|
if (!__hasProp.call(_ref3, buttonName)) continue;
|
|
this.drawButton(ctx, this.buttons[buttonName]);
|
|
}
|
|
}
|
|
if (this.message.length > 0) {
|
|
ctx.save();
|
|
ctx.fillStyle = this.messageColor;
|
|
ctx.font = "bold 20px Arial";
|
|
ctx.fillText(this.message, 20, 120);
|
|
ctx.restore();
|
|
}
|
|
if (this.isDemoPlaying && this.pointerImage) {
|
|
return this.drawDemoPointer(ctx, this.pointerLocation);
|
|
}
|
|
}
|
|
};
|
|
|
|
GearSketch.prototype.updateCanvasSize = function() {
|
|
this.canvas.width = this.canvas.parentElement.getBoundingClientRect().width;
|
|
this.canvas.height = this.canvas.parentElement.getBoundingClientRect().height;
|
|
this.buttons["clearButton"].location.x = Math.max(this.canvas.width - 260, this.buttons["playButton"].location.x + 80);
|
|
this.buttons["cloudButton"].location.x = this.buttons["clearButton"].location.x + 80;
|
|
return this.buttons["helpButton"].location.x = this.buttons["cloudButton"].location.x + 80;
|
|
};
|
|
|
|
GearSketch.prototype.loadDemoMovements = function() {
|
|
return this.demoMovements = [
|
|
{
|
|
from: this.getButtonCenter("helpButton"),
|
|
to: this.getButtonCenter("gearButton"),
|
|
atEnd: MovementAction.PEN_TAP,
|
|
type: MovementType.STRAIGHT,
|
|
duration: 2000
|
|
}, {
|
|
to: new Point(300, 200),
|
|
type: MovementType.STRAIGHT,
|
|
duration: 1500
|
|
}, {
|
|
atStart: MovementAction.PEN_DOWN,
|
|
atEnd: MovementAction.PEN_UP,
|
|
type: MovementType.CIRCLE,
|
|
radius: 100,
|
|
duration: 1500
|
|
}, {
|
|
to: new Point(500, 200),
|
|
type: MovementType.STRAIGHT,
|
|
duration: 1000
|
|
}, {
|
|
atStart: MovementAction.PEN_DOWN,
|
|
atEnd: MovementAction.PEN_UP,
|
|
type: MovementType.CIRCLE,
|
|
radius: 40,
|
|
duration: 1000
|
|
}, {
|
|
to: new Point(500, 240),
|
|
type: MovementType.STRAIGHT,
|
|
duration: 500
|
|
}, {
|
|
to: new Point(300, 300),
|
|
atStart: MovementAction.PEN_DOWN,
|
|
atEnd: MovementAction.PEN_UP,
|
|
type: MovementType.STRAIGHT,
|
|
duration: 1500
|
|
}, {
|
|
to: new Point(100, 180),
|
|
type: MovementType.STRAIGHT,
|
|
duration: 1000
|
|
}, {
|
|
atStart: MovementAction.PEN_DOWN,
|
|
atEnd: MovementAction.PEN_UP,
|
|
type: MovementType.CIRCLE,
|
|
radius: 90,
|
|
duration: 1000
|
|
}, {
|
|
to: new Point(100, 260),
|
|
type: MovementType.STRAIGHT,
|
|
duration: 500
|
|
}, {
|
|
to: new Point(180, 260),
|
|
atStart: MovementAction.PEN_DOWN,
|
|
atEnd: MovementAction.PEN_UP,
|
|
type: MovementType.STRAIGHT,
|
|
duration: 1500
|
|
}, {
|
|
to: new Point(550, 220),
|
|
type: MovementType.STRAIGHT,
|
|
duration: 1500
|
|
}, {
|
|
atStart: MovementAction.PEN_DOWN,
|
|
atEnd: MovementAction.PEN_UP,
|
|
type: MovementType.CIRCLE,
|
|
radius: 80,
|
|
duration: 1000
|
|
}, {
|
|
to: this.getButtonCenter("chainButton"),
|
|
atEnd: MovementAction.PEN_TAP,
|
|
type: MovementType.STRAIGHT,
|
|
duration: 1500
|
|
}, {
|
|
to: new Point(280, 150),
|
|
type: MovementType.STRAIGHT,
|
|
duration: 1500
|
|
}, {
|
|
atStart: MovementAction.PEN_DOWN,
|
|
type: MovementType.LEFT_HALF_CIRCLE,
|
|
radius: 140,
|
|
duration: 1500,
|
|
pause: 0
|
|
}, {
|
|
to: new Point(600, 400),
|
|
type: MovementType.STRAIGHT,
|
|
duration: 1000,
|
|
pause: 0
|
|
}, {
|
|
type: MovementType.RIGHT_HALF_CIRCLE,
|
|
radius: 110,
|
|
duration: 1000,
|
|
pause: 0
|
|
}, {
|
|
to: new Point(280, 150),
|
|
atEnd: MovementAction.PEN_UP,
|
|
type: MovementType.STRAIGHT,
|
|
duration: 1000
|
|
}, {
|
|
to: this.getButtonCenter("momentumButton"),
|
|
atEnd: MovementAction.PEN_TAP,
|
|
type: MovementType.STRAIGHT,
|
|
duration: 1500
|
|
}, {
|
|
to: new Point(185, 180),
|
|
type: MovementType.STRAIGHT,
|
|
duration: 1500
|
|
}, {
|
|
to: new Point(150, 190),
|
|
atStart: MovementAction.PEN_DOWN,
|
|
atEnd: MovementAction.PEN_UP,
|
|
type: MovementType.STRAIGHT,
|
|
duration: 1000
|
|
}, {
|
|
to: this.getButtonCenter("playButton"),
|
|
atEnd: MovementAction.PEN_TAP,
|
|
type: MovementType.STRAIGHT,
|
|
duration: 1500
|
|
}, {
|
|
to: this.getButtonCenter("chainButton"),
|
|
atEnd: MovementAction.PEN_TAP,
|
|
type: MovementType.STRAIGHT,
|
|
duration: 3000
|
|
}, {
|
|
to: new Point(425, 250),
|
|
type: MovementType.STRAIGHT,
|
|
duration: 1000
|
|
}, {
|
|
to: new Point(525, 150),
|
|
atStart: MovementAction.PEN_DOWN,
|
|
atEnd: MovementAction.PEN_UP,
|
|
type: MovementType.STRAIGHT,
|
|
duration: 1000
|
|
}, {
|
|
to: this.getButtonCenter("gearButton"),
|
|
atEnd: MovementAction.PEN_TAP,
|
|
type: MovementType.STRAIGHT,
|
|
duration: 1500
|
|
}, {
|
|
to: new Point(20, 250),
|
|
type: MovementType.STRAIGHT,
|
|
duration: 1000
|
|
}, {
|
|
to: new Point(650, 300),
|
|
atStart: MovementAction.PEN_DOWN,
|
|
atEnd: MovementAction.PEN_UP,
|
|
type: MovementType.STRAIGHT,
|
|
duration: 1500
|
|
}, {
|
|
to: new Point(425, 200),
|
|
type: MovementType.STRAIGHT,
|
|
duration: 1000
|
|
}, {
|
|
to: new Point(200, 400),
|
|
atStart: MovementAction.PEN_DOWN,
|
|
atEnd: MovementAction.PEN_UP,
|
|
type: MovementType.STRAIGHT,
|
|
duration: 1500
|
|
}
|
|
];
|
|
};
|
|
|
|
GearSketch.prototype.getButtonCenter = function(buttonName) {
|
|
var button, buttonCorner;
|
|
button = this.buttons[buttonName];
|
|
buttonCorner = new Point(button.location.x, button.location.y);
|
|
return buttonCorner.plus(new Point(0.5 * button.width + button.padding, 0.5 * button.height + button.padding));
|
|
};
|
|
|
|
GearSketch.prototype.updateDemo = function(delta) {
|
|
var movement;
|
|
if (this.restTimer > 0) {
|
|
this.restTimer = Math.max(this.restTimer - delta, 0);
|
|
return;
|
|
} else if (this.currentDemoMovement === this.demoMovements.length) {
|
|
this.stopDemo();
|
|
return;
|
|
}
|
|
movement = this.demoMovements[this.currentDemoMovement];
|
|
if (this.movementCompletion === 0) {
|
|
if (movement.from == null) {
|
|
movement.from = this.pointerLocation;
|
|
}
|
|
if (movement.pause == null) {
|
|
movement.pause = 500;
|
|
}
|
|
this.pointerLocation = movement.from.clone();
|
|
if (movement.atStart === MovementAction.PEN_DOWN) {
|
|
this.handlePenDown(this.pointerLocation.x, this.pointerLocation.y);
|
|
}
|
|
}
|
|
if (this.movementCompletion < 1) {
|
|
this.movementCompletion = Math.min(1, this.movementCompletion + delta / movement.duration);
|
|
this.updatePointerLocation(movement, this.movementCompletion);
|
|
this.handlePenMove(this.pointerLocation.x, this.pointerLocation.y);
|
|
}
|
|
if (this.movementCompletion === 1) {
|
|
if (movement.atEnd === MovementAction.PEN_TAP) {
|
|
this.handlePenDown(this.pointerLocation.x, this.pointerLocation.y);
|
|
this.handlePenUp();
|
|
} else if (movement.atEnd === MovementAction.PEN_UP) {
|
|
this.handlePenUp();
|
|
}
|
|
this.restTimer = movement.pause;
|
|
this.movementCompletion = 0;
|
|
return this.currentDemoMovement++;
|
|
}
|
|
};
|
|
|
|
GearSketch.prototype.updatePointerLocation = function(movement, movementCompletion) {
|
|
var angle, center, delta;
|
|
if (movement.type === MovementType.STRAIGHT) {
|
|
delta = movement.to.minus(movement.from);
|
|
return this.pointerLocation = movement.from.plus(delta.times(movementCompletion));
|
|
} else if (movement.type === MovementType.CIRCLE) {
|
|
center = new Point(movement.from.x, movement.from.y + movement.radius);
|
|
return this.pointerLocation = center.plus(Point.polar(Math.PI - (movementCompletion - 0.25) * 2 * Math.PI, movement.radius));
|
|
} else if (movement.type === MovementType.LEFT_HALF_CIRCLE) {
|
|
center = new Point(movement.from.x, movement.from.y + movement.radius);
|
|
angle = 1.5 * Math.PI - movementCompletion * Math.PI;
|
|
return this.pointerLocation = center.plus(Point.polar(angle, movement.radius));
|
|
} else if (movement.type === MovementType.RIGHT_HALF_CIRCLE) {
|
|
center = new Point(movement.from.x, movement.from.y - movement.radius);
|
|
angle = 0.5 * Math.PI - movementCompletion * Math.PI;
|
|
return this.pointerLocation = center.plus(Point.polar(angle, movement.radius));
|
|
}
|
|
};
|
|
|
|
GearSketch.prototype.playDemo = function() {
|
|
this.loadDemoMovements();
|
|
this.boardBackup = this.board.clone();
|
|
this.board.clear();
|
|
this.currentDemoMovement = 0;
|
|
this.movementCompletion = 0;
|
|
this.isDemoPlaying = true;
|
|
return this.displayMessage("click anywhere to stop the demo");
|
|
};
|
|
|
|
GearSketch.prototype.stopDemo = function() {
|
|
this.isDemoPlaying = false;
|
|
this.restTimer = 0;
|
|
this.stroke = [];
|
|
this.selectedGear = null;
|
|
this.selectedIcon = "gearIcon";
|
|
this.board.restoreAfterDemo(this.boardBackup);
|
|
return this.clearMessage();
|
|
};
|
|
|
|
GearSketch.prototype.boardUploaded = function(event) {
|
|
parent.location.hash = event.target.responseText.trim();
|
|
return this.displayMessage("Board saved. Share it by copying the text in your address bar.", "black", 4000);
|
|
};
|
|
|
|
GearSketch.prototype.uploadBoard = function() {
|
|
var boardJSON,
|
|
_this = this;
|
|
boardJSON = JSON.stringify(this.board);
|
|
return Util.sendPostRequest(boardJSON, "upload_board.php", (function(event) {
|
|
return _this.boardUploaded(event);
|
|
}));
|
|
};
|
|
|
|
return GearSketch;
|
|
|
|
})();
|
|
|
|
window.gearsketch.GearSketch = GearSketch;
|
|
|
|
}).call(this);
|
|
|
|
/*
|
|
//@ sourceMappingURL=gearsketch_main.map
|
|
*/
|