Skip to content

Instantly share code, notes, and snippets.

@bollwyvl
Created November 19, 2012 23:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bollwyvl/4114675 to your computer and use it in GitHub Desktop.
Save bollwyvl/4114675 to your computer and use it in GitHub Desktop.
HTML5 Canvas Drafting
// Taken from:
// http://cautery.blogspot.com/2012/05/compass-and-straightedge-geometry-meets.html
// Things I'm allowed to do:
// http://en.wikipedia.org/wiki/File:Basic-construction-demo.png
var cnv, ctx; // Canvas, 2d context
var cr = Math.PI * 2;
var Type = {POINT : 0, LINE : 1, CIRCLE : 2};
var col, sm, interval, label;
var pointRadius = 3;
var textOffsetX = 5;
var textOffsetY = -5;
var canvasBounds = {};
function Point(x, y) {
this.x = x;
this.y = y;
this.type = Type.POINT;
this.draw = function() {
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.arc(this.x, this.y, pointRadius, 0, cr, false);
ctx.fill();
}
}
function Circle(center, R) {
// Add orientation stuff
this.center = center; // a Point
this.R = R;
this.radius = dist(center, R);
this.r2 = this.radius * this.radius;
this.type = Type.CIRCLE;
this.draw = function() {
ctx.beginPath();
ctx.moveTo(this.center.x + this.radius, this.center.y);
ctx.arc(this.center.x, this.center.y, this.radius, 0, cr, false);
ctx.stroke();
}
this.getCircleIntersect = function(circle) {
var d = dist(this.center, circle.center);
if (d > this.radius + circle.radius) {
return new Intersect([], "Circles do not intersect");
}
if (d < Math.abs(this.radius - circle.radius) || d == 0) {
return new Intersect([], "One circle encloses the other");
}
var a = (this.r2 - circle.r2 + d * d) / (2 * d);
var h = Math.sqrt(this.r2 - a * a);
var midX = this.center.x + a * (circle.center.x - this.center.x) / d;
var midY = this.center.y + a * (circle.center.y - this.center.y) / d;
var hyy = h * (circle.center.y - this.center.y) / d;
var hxx = h * (circle.center.x - this.center.x) / d
var points = []
points[0] = new Point(midX + hyy, midY - hxx);
// if d = r1 + r2, circles are tangent, else add second intersect point
if (d < this.radius + circle.radius) {
points[1] = new Point(midX - hyy, midY + hxx);
}
return new Intersect(points, "");
}
this.getLineIntersect = function(line) {
var x1 = line.A.x - this.center.x;
var x2 = line.B.x - this.center.x;
var y1 = line.A.y - this.center.y;
var y2 = line.B.y - this.center.y;
var dx = x2 - x1;
var dy = y2 - y1;
var dr = Math.sqrt(dx * dx + dy * dy);
var dr2 = dr * dr;
var D = x1 * y2 - x2 * y1;
var D2 = D * D;
var sgn = dy < 0 ? -1 : 1;
var disc = this.r2 * dr2 - D2;
if (disc < 0) return new Intersect([], "Line does not intersect circle");
var points = [];
var tmpSqrt = Math.sqrt(this.r2 * dr2 - D2);
var x = (D * dy + sgn * dx * tmpSqrt)/ dr2 + this.center.x;
var y = (-D * dx + Math.abs(dy) * tmpSqrt) / dr2 + this.center.y;
points[0] = new Point(x, y);
if (disc > 0) {
x = (D * dy - sgn * dx * tmpSqrt)/ dr2 + this.center.x;
y = (-D * dx - Math.abs(dy) * tmpSqrt) / dr2 + this.center.y;
points[1] = new Point(x, y);
}
return new Intersect(points, "");
}
}
function Line(A, B) {
this.A = A; // A and B are Points
this.B = B;
var extended = false;
this.type = Type.LINE;
this.extend = function() {
if (extended) return;
extended = true;
// move points A and B to edge of canvas
var i = this.getLineIntersect(canvasBounds.top);
if (i.points.length == 0) i = this.getLineIntersect(canvasBounds.left);
var j = this.getLineIntersect(canvasBounds.bottom);
if (j.points.length == 0) j = this.getLineIntersect(canvasBounds.right);
this.A = i.points[0];
this.B = j.points[0];
}
this.draw = function() {
ctx.beginPath();
ctx.moveTo(this.A.x, this.A.y);
ctx.lineTo(this.B.x, this.B.y);
ctx.stroke();
}
this.getLineIntersect = function(line) {
var x1 = this.A.x;
var x2 = this.B.x;
var x3 = line.A.x;
var x4 = line.B.x;
var y1 = this.A.y;
var y2 = this.B.y;
var y3 = line.A.y;
var y4 = line.B.y;
var denom = det(x1 - x2, y1 - y2, x3 - x4, y3 - y4);
if (denom == 0) return new Intersect([], "Lines don't intersect");
var leftA = det(x1, y1, x2, y2);
var leftC = det(x3, y3, x4, y4);
var x = det(leftA, x1 - x2, leftC, x3 - x4) / denom;
var y = det(leftA, y1 - y2, leftC, y3 - y4) / denom;
return new Intersect([new Point(x, y)], "");
}
}
function det(a, b, c, d) {
// Determinate in format |a b|
// |c d|
return a * d - b * c;
}
function dist(A, B) {
var x = A.x - B.x;
var y = A.y - B.y;
return Math.sqrt(x * x + y * y);
}
function Intersect(points, message) {
this.points = points;
this.message = message;
}
function init() {
if (interval != null) clearInterval(interval);
cnv = document.getElementById('cnv');
var tl = new Point(0, 0);
var tr = new Point(cnv.width, 0);
var bl = new Point(0, cnv.height);
var br = new Point(cnv.width, cnv.height);
canvasBounds.top = new Line(tl, tr);
canvasBounds.bottom = new Line(bl, br);
canvasBounds.left = new Line(tl, bl);
canvasBounds.right = new Line(tr, br);
ctx = cnv.getContext('2d');
ctx.translate(.5, .5);
ctx.strokeStyle = "black";
col = {};
sm = new StepManager();
bisect();
}
function frame() {
sm.frame();
}
function drawBoard() {
// Clear board
ctx.clearRect(0, 0, cnv.width, cnv.height)
// Draw current label, if any
if (label != "") ctx.fillText(label, 20, 20);
// Iterate through collection (col) of points, lines and circles, and call their draw() methods
for (var e in col) {
var elem = col[e];
elem.draw();
// ...and label the points
if (elem.type == Type.POINT) {
ctx.fillText(e, elem.x + textOffsetX, elem.y + textOffsetY);
}
}
}
function run() {
col = {};
if (interval != null) clearInterval(interval);
sm.init(document.getElementById('ta').value.split(/\n+/));
interval = setInterval(frame, document.getElementById('delay').value);
}
function StepManager() {
var index, done, i, steps;
this.init = function(s) {
steps = s;
i = null;
index = 0;
done = false;
label = "";
drawBoard();
}
function point(x, y, name) {
y = parseInt(y);
if (x == 'i') col[name] = i.points[y];
else col[name] = new Point(parseInt(x), y);
}
function line(a, b) {
var name = '_ln_' + a + '_' + b;
col[name] = new Line(col[a], col[b]);
}
function extend(a, b) {
var name = '_ln_' + a + '_' + b;
col[name].extend();
}
function circle(a, b) {
var name = '_cr_' + a + '_' + b;
col[name] = new Circle(col[a], col[b]);
}
function getObj(type, a, b) {
return col[nameFromElems(type, a, b)];
}
function intersect(typeA, a, b, typeB, c, d) {
var objA = getObj(typeA, a, b);
var objB = getObj(typeB, c, d);
if (typeB == "circle") i = objA.getCircleIntersect(objB);
else i = objA.getLineIntersect(objB)
}
function nameFromElems(type, a, b) {
if (type == "circle")return '_cr_' + a + '_' + b;
if (type == "line") return '_ln_' + a + '_' + b;
return a;
}
function deleteObj(type, a, b) {
delete(col[nameFromElems(type, a, b)]);
}
function display(msg) {
label = msg;
}
this.frame = function() {
if (done == true) {
clearInterval(interval);
}
else this.nextStep();
drawBoard();
}
this.nextStep = function() {
if (index >= steps.length) {
done = true;
display("done");
return;
}
var step = steps[index++];
if (step.charAt(0) == '#') this.nextStep();
else if (step.charAt(0) == ':') {
display(step.substr(1));
} else {
var elems = step.split(' ');
switch(elems[0]) {
case '': return;
case 'point':
point(elems[1], elems[2], elems[3]);
break;
case 'circle':
circle(elems[1], elems[2]);
break;
case 'line':
line(elems[1], elems[2]);
break;
case 'extend':
extend(elems[1], elems[2]);
break;
case 'intersect':
intersect(elems[1], elems[2], elems[3], elems[4], elems[5], elems[6]);
this.nextStep();
break;
case 'delete':
deleteObj(elems[1], elems[2], elems[3]);
break;
default:
display("Unknown step type: " + elems[0]);
done = true;
return;
}
}
}
}
function bisect() {
document.getElementById('ta').value = "#Bisect an angle\n:Draw angle ABC\npoint 280 100 A\npoint 320 240 B\npoint 420 240 C\nline A B\nline B C\n:Draw circle at B with C radius\ncircle B C\n:Mark intersect point of circle and line AB as D\nintersect circle B C line A B\npoint i 1 D\n:Draw circles DB and CB\ncircle D B\ncircle C B\n:Mark intersect point of two new circles (other than B) as E\nintersect circle D B circle C B\npoint i 0 E\n:Draw bisecting line BE\nline B E\nextend B E\n:Erase chaff\ndelete circle D B\ndelete circle C B\ndelete point D\ndelete circle B C\n";
}
function hep() {
document.getElementById('ta').value = "#Construct a regular 17-gon\n:Draw origin and P1, circle, and diameter\npoint 320 300 O\npoint 490 300 P1\ncircle O P1\nline O P1\nextend O P1\n\n:Find perpendicular bisector of diameter\nintersect circle O P1 line O P1\npoint i 1 tmp\ncircle tmp P1\ncircle P1 tmp\nintersect circle tmp P1 circle P1 tmp\npoint i 0 b1\nline b1 O\nintersect circle O P1 line b1 O\npoint i 1 B\ndelete circle tmp P1\ndelete circle P1 tmp\ndelete point tmp\ndelete line b1 O\ndelete point b1\nline O B\n\n:Create point J 1/4 of the way up OB\ncircle B O\nintersect circle B O circle O P1\npoint i 1 b1\npoint i 0 b2\nline b1 b2\nintersect line O B line b1 b2\npoint i 0 tmp\ndelete circle B O\ndelete line b1 b2\ndelete point b1\ndelete point b2\ncircle O tmp\ncircle tmp O\nintersect circle O tmp circle tmp O\npoint i 0 b1\npoint i 1 b2\nline b1 b2\nline O tmp\nintersect line b1 b2 line O tmp\npoint i 0 J\ndelete circle O tmp\ndelete circle tmp O\ndelete point tmp\ndelete line b1 b2\ndelete point b1\ndelete point b2\ndelete line O tmp\n\n:Create line JP1 and find E on bisector line where OJE is 1/4 of OJP1\nline J P1\ncircle J O\nintersect circle J O line J P1\npoint i 0 tmp\ncircle O J\ncircle tmp J\nintersect circle O J circle tmp J\ndelete point tmp\npoint i 1 t\nline J t\ndelete circle tmp J\nintersect circle J O line J t\npoint i 0 tmp\nline J tmp\ndelete line J t\ndelete point t\ndelete circle J O\ncircle tmp J\nintersect circle O J circle tmp J\npoint i 1 t\nline J t\ndelete circle O J\ndelete circle tmp J\ndelete line J tmp\ndelete point tmp\nintersect line O P1 line J t\npoint i 0 E\nline J E\ndelete line J t\ndelete point t\n\n:create F on circle bisect line such that EJF is 45 degrees\ncircle J E\nextend J E\nintersect circle J E line J E\npoint i 1 tmp\ncircle tmp E\ncircle E tmp\nintersect circle tmp E circle E tmp\npoint i 1 t\nline J t\ndelete circle tmp E\ndelete circle E tmp\ndelete point tmp\nline J E\nintersect circle J E line J t\npoint i 0 tmp\ndelete line J t\ndelete point t\ncircle E J\ncircle tmp J\nintersect circle E J circle tmp J\npoint i 0 t\nline J t\ndelete circle E J\ndelete circle tmp J\ndelete point tmp\ndelete circle J E\nintersect line O P1 line J t\npoint i 0 F\nline J F\ndelete line J t\ndelete point t\n\n:Create circle with diameter F P1\ncircle F P1\ncircle P1 F\nintersect circle F P1 circle P1 F\npoint i 0 b1\npoint i 1 b2\nline b1 b2\nintersect line b1 b2 line O P1\npoint i 0 t\ndelete circle F P1\ndelete circle P1 F\ndelete line b1 b2\ndelete point b1\ndelete point b2\ncircle t P1\nintersect circle t P1 line O B\npoint i 1 K\ndelete point t\ncircle E K\nintersect circle E K line O P1\npoint i 0 N4\n\n:Create perpendicular line through OP1 at N4\ncircle N4 E\nintersect circle N4 E line O P1\npoint i 0 t\ncircle t E\ncircle E t\nintersect circle t E circle E t\npoint i 1 b1\nline N4 b1\ndelete circle t E\ndelete circle E t\ndelete circle N4 E\ndelete point t\nextend line N4 b1\n\n:Perpendicular line at N4 cuts circle O P1 at P4\nintersect circle O P1 line N4 b1\npoint i 1 P4\nline N4 P4\ndelete line N4 b1\ndelete point b1\n\n:Delete artifacts\ndelete circle t P1\ndelete circle E K\ndelete point K\ndelete line J F\ndelete point F\ndelete line J E\ndelete point E\ndelete line J P1\ndelete point J\ndelete line N4 P4\ndelete point N4\ndelete line O B\ndelete point B\n\n:Create remaining 15 points using P1/P4 as initial radius\ncircle P4 P1\nintersect circle O P1 circle P4 P1\npoint i 0 P7\ndelete circle P4 P1\ncircle P7 P4\nintersect circle O P1 circle P7 P4\npoint i 0 P10\ndelete circle P7 P4\ncircle P10 P7\nintersect circle O P1 circle P10 P7\npoint i 0 P13\ndelete circle P10 P7\ncircle P13 P10\nintersect circle O P1 circle P13 P10\npoint i 0 P16\ndelete circle P13 P10\ncircle P16 P13\nintersect circle O P1 circle P16 P13\npoint i 0 P2\ndelete circle P16 P13\ncircle P2 P16\nintersect circle O P1 circle P2 P16\npoint i 0 P5\ndelete circle P2 P16\ncircle P5 P2\nintersect circle O P1 circle P5 P2\npoint i 0 P8\ndelete circle P5 P2\ncircle P8 P5\nintersect circle O P1 circle P8 P5\npoint i 0 P11\ndelete circle P8 P5\ncircle P11 P8\nintersect circle O P1 circle P11 P8\npoint i 0 P14\ndelete circle P11 P8\ncircle P14 P11\nintersect circle O P1 circle P14 P11\npoint i 0 P17\ndelete circle P14 P11\ncircle P17 P14\nintersect circle O P1 circle P17 P14\npoint i 0 P3\ndelete circle P17 P14\ncircle P3 P17\nintersect circle O P1 circle P3 P17\npoint i 0 P6\ndelete circle P3 P17\ncircle P6 P3\nintersect circle O P1 circle P6 P3\npoint i 0 P9\ndelete circle P6 P3\ncircle P9 P6\nintersect circle O P1 circle P9 P6\npoint i 0 P12\ndelete circle P9 P6\ncircle P12 P9\nintersect circle O P1 circle P12 P9\npoint i 0 P15\ndelete circle P12 P9\n\n:Connect the dots\nline P1 P2\nline P2 P3\nline P3 P4\nline P4 P5\nline P5 P6\nline P6 P7\nline P7 P8\nline P8 P9\nline P9 P10\nline P10 P11\nline P11 P12\nline P12 P13\nline P13 P14\nline P14 P15\nline P15 P16\nline P16 P17\nline P17 P1\ndelete circle O P1\ndelete line O P1\ndelete point O";
}
function triangle() {
document.getElementById('ta').value = "#Construct an equilateral triangle\n:Draw origin and P0, circle, and diameter\npoint 320 300 O\npoint 490 300 P0\ncircle O P0\nline O P0\nextend O P0\n\n:Find perpendicular bisector of diameter\nintersect circle O P0 line O P0\npoint i 1 tmp\ncircle tmp P0\ncircle P0 tmp\nintersect circle tmp P0 circle P0 tmp\npoint i 0 b1\nline b1 O\nintersect circle O P0 line b1 O\npoint i 1 B\ndelete circle tmp P0\ndelete circle P0 tmp\ndelete point tmp\ndelete line b1 O\ndelete point b1\nline O B\n\n:Circle BO cuts circle O P0 at top two points of triangle\ncircle B O\nintersect circle O P0 circle B O\npoint i 0 P1\npoint i 1 P2\nline P1 P2\ndelete circle B O\n\n:Extend line OB to other end of circle to find final point of triangle\nextend O B\nintersect circle O P0 line O B\npoint i 0 P3\nline P1 P3\nline P3 P2\ndelete line O B\ndelete point B\ndelete line O P0\ndelete point P0\ndelete point O\ndelete circle O P0";
}
function pentagon() {
document.getElementById('ta').value = "#Construct a regular pentagon\n:Draw origin and P1, circle, and diameter\npoint 320 300 O\npoint 490 300 P1\ncircle O P1\nline O P1\nextend O P1\n\n:Find perpendicular bisector of diameter\nintersect circle O P1 line O P1\npoint i 1 tmp\ncircle tmp P1\ncircle P1 tmp\nintersect circle tmp P1 circle P1 tmp\npoint i 0 b1\nline b1 O\nintersect circle O P1 line b1 O\npoint i 1 B\ndelete circle tmp P1\ndelete circle P1 tmp\ndelete point tmp\ndelete line b1 O\ndelete point b1\nline O B\n\n:Bisect line OB at point D, connect to P1\ncircle B O\nintersect circle B O circle O P1\npoint i 1 b1\npoint i 0 b2\nline b1 b2\nintersect line O B line b1 b2\npoint i 0 D\ndelete circle B O\ndelete line b1 b2\ndelete point b1\ndelete point b2\nline D P1\n\n:Bisect angle ODP1, connect to line OP1 as N2\ncircle D O\nintersect circle D O line D P1\npoint i 0 tmp\ndelete circle D O\ncircle O D\ncircle tmp D\nintersect circle O D circle tmp D\npoint i 1 t\nline D t\ndelete circle O D\ndelete circle tmp D\ndelete point tmp\nintersect line O P1 line D t\npoint i 0 N2\nline D N2\ndelete line D t\ndelete point t\n\n:Line at N2 perpendicular to line OP1 is point P2\ncircle N2 O\nintersect circle N2 O line O P1\npoint i 0 t\ncircle O t\ncircle t O\ndelete circle N2 O\nintersect circle O t circle t O\npoint i 0 b1\nline b1 N2\nextend b1 N2\ndelete circle O t\ndelete circle t O\ndelete point t\nintersect circle O P1 line b1 N2\npoint i 1 P2\ndelete point b1\nline N2 P2\ndelete line b1 N2\n\n:Delete artifacts\ndelete line N2 P2\ndelete line D N2\ndelete point N2\ndelete line D P1\ndelete point D\ndelete line O B\ndelete point B\n\n:Create remaining 3 points using P1/P2 as initial radius\ncircle P2 P1\nintersect circle O P1 circle P2 P1\npoint i 0 P3\ndelete circle P2 P1\ncircle P3 P2\nintersect circle O P1 circle P3 P2\npoint i 0 P4\ndelete circle P3 P2\ncircle P4 P3\nintersect circle O P1 circle P4 P3\npoint i 0 P5\ndelete circle P4 P3\n\n:Connect the dots\nline P1 P2\nline P2 P3\nline P3 P4\nline P4 P5\nline P5 P1\ndelete line O P1\ndelete circle O P1\ndelete point O";
}
init();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment