random() < 0.5 ? randRange(10, 80) * PI / 180 : randRange(100, 170) * PI / 180 tan(ROT) 0

Construct a line perpendicular to the given line.

init({ range: [[-5, 5], [-5, 5]], scale: 50 }); addMouseLayer(); addConstruction("construction"); var a = applyRefFrame([4, 0], ROT); var b = applyRefFrame([-4, 0], ROT); addDummyStraightedge(a, b);
Use the compass and straightedge tools to construct a line perpendicular to the given line.
_.map(_.filter(construction.tools, function(tool) { return tool.dummy !== true; }), function(tool) { if (tool.first != null) { return { first: { coord: [ tool.first.coord[0], tool.first.coord[1] ] }, second: { coord: [ tool.second.coord[0], tool.second.coord[1] ] } }; } else if (tool.center != null) { return { center: { coord: [ tool.center.coord[0], tool.center.coord[1] ] }, radius: tool.radius }; } })
// If there's only one element, it's the given line if (guess.length === 0) { return ""; } // first determine if there is a perp. line perp = null; _.each(guess, function(tool) { if (tool.first != null) { ang = atan2( tool.second.coord[1] - tool.first.coord[1], tool.second.coord[0] - tool.first.coord[0]); deg = ang * 180 / PI; origDeg = ROT * 180 / PI; if (abs(deg-origDeg+90) < 7 || abs(deg-origDeg-90) < 7) { perp = tool; } } }); if (perp == null) { return false; } // next make sure there are two compasses, // each of which are centered on the line comps = _.filter(guess, function(tool) { return tool.center != null; }); if (comps.length < 2) { return false; } onLine = []; _.each(comps, function(comp1) { _.each(comps, function(comp2) { thisSlope = (comp1.center.coord[1] - comp2.center.coord[1]) / (comp1.center.coord[0] - comp2.center.coord[0]); thisYInt = comp1.center.coord[1] - thisSlope*comp1.center.coord[0]; if (abs(thisSlope - SLOPE) < 0.5 && abs(thisYInt) < 0.1) { onLine = [comp1,comp2]; } }); }); // Really, Javascript? [] !== []? Fine. if (onLine.length < 2) { return false; } // now we know that the slope of the straightedge // is good, and the two compasses are an on the line, // so if the straightedge has the same y-intercept // as the y-intercept of the line going between // the two points of intersection of the two compasses var a = onLine[0].center.coord[0]; var b = onLine[0].center.coord[1]; var c = onLine[1].center.coord[0]; var d = onLine[1].center.coord[1]; var r = onLine[0].radius; var s = onLine[1].radius; var e = c - a; var f = d - b; var p = sqrt(pow(e,2) + pow(f,2)); var k = (pow(p,2) + pow(r,2) - pow(s,2))/(2*p); var x1 = a + e * k / p + (f / p) * sqrt(pow(r, 2) - pow(k, 2)); var y1 = b + f * k / p - (e / p) * sqrt(pow(r, 2) - pow(k, 2)); interYInt = y1 + x1 * (1 / SLOPE); perpYInt = perp.first.coord[1] + perp.first.coord[0] * (1 / SLOPE); // give some leeway for the y-int when the slope // is high return abs(interYInt - perpYInt) < 1;
showConstructionGuess(guess);
graph.perp = raphael.set(); graph.perp.push(line( applyRefFrame([0, 10], ROT), applyRefFrame([0, -10], ROT), { strokeWidth: 1, stroke: BLUE })).toBack();

We could just draw a line and try to make it perpendicular, but then we have no guarantee that it's actually perfectly perpendicular.

How can you guarantee that a line is perpendicular?

graph.perpPoints = raphael.set(); graph.hintLines = raphael.set(); style({ fill: BLUE, stroke: null, }, function() { graph.perpPoints.push(circle( applyRefFrame([0, -1], ROT), 0.08)); graph.perpPoints.push(circle( applyRefFrame([0, 1], ROT), 0.08)); }); graph.perp.push(drawHintLine( applyRefFrame([0, 0], ROT), applyRefFrame([0, 1], ROT), 1)); graph.perp.push(drawHintLine( applyRefFrame([0, 0], ROT), applyRefFrame([0, -1], ROT), 1)); graph.hintLines.push(drawHintLine( applyRefFrame([0, -1], ROT), applyRefFrame([1, 0], ROT), 2)); graph.hintLines.push(drawHintLine( applyRefFrame([0, 1], ROT), applyRefFrame([1, 0], ROT), 2)); graph.hintLines.push(drawHintLine( applyRefFrame([0, -1],ROT), applyRefFrame([-2, 0],ROT), 3)); graph.hintLines.push(drawHintLine( applyRefFrame([0, 1],ROT), applyRefFrame([-2, 0],ROT), 3)); graph.perp.toBack(); graph.hintLines.toBack(); graph.perpPoints.toBack();

If we pick two points on the perpendicular line which are an equal distance from the intersection, they will also be the same distance from every other point on the line we started with.

graph.perp.remove();

If we don't already have the perpendicular line, is there another way to find the blue points?

circle(applyRefFrame([1, 0], ROT), 0.08, { fill: GRAY, stroke: null }); circle(applyRefFrame([1, 0], ROT), eDist(applyRefFrame([0, -1], ROT), applyRefFrame([1, 0], ROT)), { stroke: GRAY, strokeWidth: 1, fill: "none", strokeDasharray: "- " });

If we use the compass to put a circle somewhere on the line, the circle will include all points that are the same distance from that point, including the two blue points.

circle(applyRefFrame([-2, 0], ROT), 0.08, { fill: GRAY, stroke: null }); circle(applyRefFrame([-2, 0], ROT), eDist(applyRefFrame([0, -1], ROT), applyRefFrame([-2, 0], ROT)), { stroke: GRAY, strokeWidth: 1, fill: "none", strokeDasharray: "- " });

We can add a second circle somewhere else on the line that intersects with the first circle.

graph.hintLines.remove();

The points where the two circles intersect can be used to draw a perpendicular line.

graph.perpPoints.attr({fill: GRAY}); line(applyRefFrame([0, 10], ROT), applyRefFrame([0, -10], ROT), { strokeWidth: 1, stroke: GRAY, strokeDasharray: "- " }).toBack();

Use a straightedge to connect the two points where the circles intersect. This line is perpendicular to the given line.

Construct a perpendicular bisector of the line segment \overline{AB}.

init({ range: [[-5,5],[-5,5]], scale: 50 }); addMouseLayer(); addConstruction("construction"); var a = applyRefFrame([1.5, 0], ROT); var b = applyRefFrame([-1.5, 0], ROT); a = [roundToNearest(0.01, a[0]), roundToNearest(0.01, a[1])]; b = [roundToNearest(0.01, b[0]), roundToNearest(0.01, b[1])]; addDummyStraightedge(a, b, false); addDummyPoint(a); addDummyPoint(b); var offset = "above"; if ((ROT * 180 / PI) > 50 && (ROT * 180 / PI) < 90) { offset = "left"; } if ((ROT * 180 / PI) < 130 && (ROT * 180 / PI) > 90) { offset = "right"; } label(a, "A", offset); label(b, "B", offset);
Use the compass and straightedge tools to construct a perpendicular bisector of \overline{AB}.
_.map(_.filter(construction.tools, function(tool) { return tool.dummy !== true; }), function(tool) { if (tool.first != null) { return { first: { coord: [ tool.first.coord[0], tool.first.coord[1] ] }, second: { coord: [ tool.second.coord[0], tool.second.coord[1] ] } }; } else if (tool.center != null) { return { center: { coord: [ tool.center.coord[0], tool.center.coord[1] ] }, radius: tool.radius }; } })
// If there's only one element, it's the given line if (guess.length === 0) { return ""; } // first determine if there is a perp. line perp = null; _.each(guess, function(tool) { if (tool.first != null) { ang = atan2( tool.second.coord[1] - tool.first.coord[1], tool.second.coord[0] - tool.first.coord[0]); deg = ang * 180 / PI; origDeg = ROT * 180 / PI; if (abs(deg - origDeg + 90) < 7 || abs(deg - origDeg - 90) < 7) { perp = tool; } } }); if (perp == null) { return false; } // next make sure there are two compasses, // each of which are centered on the line, // with equal radius comps = _.filter(guess, function(tool) { return tool.center != null; }); if (comps.length < 2) { return false; } onLine = []; _.each(comps, function(comp1) { _.each(comps, function(comp2) { thisSlope = (comp1.center.coord[1] - comp2.center.coord[1]) / (comp1.center.coord[0] - comp2.center.coord[0]); thisYInt = comp1.center.coord[1] - thisSlope*comp1.center.coord[0]; if (abs(thisSlope - SLOPE) < 0.5 && abs(thisYInt) < 0.1 && abs(comp1.radius - comp2.radius) < 0.1) { onLine = [comp1,comp2]; } }); }); // Really, Javascript? [] !== []? Fine. if (onLine.length < 2) { return false; } // now we know that the slope of the straightedge // is good, and the two compasses are an on the line, // so if the straightedge has the same y-intercept // as the y-intercept of the line going between // the two points of intersection of the two compasses interYInt = 0; perpYInt = perp.first.coord[1] + perp.first.coord[0]*(1/SLOPE); return abs(interYInt - perpYInt) < 0.5;
showConstructionGuess(guess);
graph.perp = raphael.set(); graph.perp.push(line( applyRefFrame([0, 10], ROT), applyRefFrame([0, -10], ROT), { strokeWidth: 1, stroke: BLUE })).toBack();

We could just draw a line and try to get it right, but then we have no guarantee that it's actually perfectly perpendicular or that it bisects the segment at exactly the midpoint.

How can you guarantee that a line is really a perpendicular bisector?

graph.hintLines = raphael.set(); style({ fill: BLUE, stroke: null, }, function() { graph.perpPoint1 = circle( applyRefFrame([0, -1], ROT), 0.08); graph.perpPoint2 = circle( applyRefFrame([0, 1], ROT), 0.08); }); graph.perp.push(drawHintLine( applyRefFrame([0, 0], ROT), applyRefFrame([0, 1], ROT), 1)); graph.perp.push(drawHintLine( applyRefFrame([0, 0], ROT), applyRefFrame([0, -1], ROT), 1)); graph.hintLines.push(drawHintLine( applyRefFrame([0, -1], ROT), applyRefFrame([1.5, 0], ROT), 2)); graph.hintLines.push(drawHintLine( applyRefFrame([0, 1], ROT), applyRefFrame([1.5, 0], ROT), 2)); graph.hintLines.push(drawHintLine( applyRefFrame([0, -1],ROT), applyRefFrame([-1.5, 0],ROT), 2)); graph.hintLines.push(drawHintLine( applyRefFrame([0, 1],ROT), applyRefFrame([-1.5, 0],ROT), 2)); graph.perp.toBack(); graph.hintLines.toBack(); graph.perpPoint1.toBack(); graph.perpPoint2.toBack();

If we pick two points on the perpendicular bisector which are an equal distance from the intersection, they will also be the same distance from both endpoints of the segment we started with.

graph.perp.remove();

If we don't already have the perpendicular bisector, is there another way to find the blue points?

circle(applyRefFrame([1.5, 0], ROT), 0.08, { fill: GRAY, stroke: null }); graph.compass1 = circle(applyRefFrame([1.5, 0], ROT), eDist(applyRefFrame([0, -1], ROT), applyRefFrame([1.5, 0], ROT)), { stroke: GRAY, strokeWidth: 1, fill: "none", strokeDasharray: "- " }).toBack();

If we use the compass to put a circle centered at point A, the circle will include all points that are the same distance from point A, including the two blue points.

circle(applyRefFrame([-1.5, 0], ROT), 0.08, { fill: GRAY, stroke: null }); graph.compass2 = circle(applyRefFrame([-1.5, 0], ROT), eDist(applyRefFrame([0, -1], ROT), applyRefFrame([-2, 0], ROT)), { stroke: GRAY, strokeWidth: 1, fill: "none", strokeDasharray: "- " }).toBack();

We can add a second circle at point B that intersects with the first circle.

graph.hintLines.remove();

But wait! We can use these circles to draw a perpendicular line, but not a bisector! That's because the two circles are different sizes.

graph.compass1.animate({ rx: scaleVector(3)[0], ry: scaleVector(3)[1] }, 250); graph.compass2.animate({ rx: scaleVector(3)[0], ry: scaleVector(3)[1] }, 250); graph.perpPoint1.animate({ cx: scalePoint(applyRefFrame( [0, -3/2 * sqrt(3)], ROT))[0], cy: scalePoint(applyRefFrame( [0, -3/2 * sqrt(3)], ROT))[1] }, 250); graph.perpPoint2.animate({ cx: scalePoint(applyRefFrame( [0, 3/2 * sqrt(3)], ROT))[0], cy: scalePoint(applyRefFrame( [0, 3/2 * sqrt(3)], ROT))[1] }, 250);

One nice way to make the circles the same size is to set the radii equal to the distance between A and B. You can do this by setting the center at one point and the edge of the circle at the other.

graph.perpPoint1.attr({fill: GRAY}); graph.perpPoint2.attr({fill: GRAY}); line(applyRefFrame([0, 10], ROT), applyRefFrame([0, -10], ROT), { strokeWidth: 1, stroke: GRAY, strokeDasharray: "- " }).toBack();

Use a straightedge to connect the two points where the circles intersect. This line is the perpendicular bisector of \overline{AB}.

randRange(30, 80) * PI / 180 [randRange(-3, 0), 0] 1 CENTER [CENTER[0] + 4, CENTER[1]] (function() { var c = applyRefFrame([4, 0], ROT); c = [c[0] + CENTER[0], c[1] + CENTER[1]]; c = [roundToNearest(0.01, c[0]), roundToNearest(0.01, c[1])]; return c; })()

Construct an angle bisector for the given angle.

init({ range: [[-5, 5], [-2, 5]], scale: 50 }); addMouseLayer(); addConstruction("construction"); addDummyRay(A, B); addDummyRay(A, C);
Use the compass and straightedge tools to construct an angle bisector.
_.map(_.filter(construction.tools, function(tool) { return tool.dummy !== true; }), function(tool) { if (tool.first != null) { return { first: { coord: [ tool.first.coord[0], tool.first.coord[1] ] }, second: { coord: [ tool.second.coord[0], tool.second.coord[1] ] } }; } else if (tool.center != null) { return { center: { coord: [ tool.center.coord[0], tool.center.coord[1] ] }, radius: tool.radius }; } })
if (guess.length === 0) { return ""; } // first determine if there is a line bisecting var bisect = null; // var guess = construction.tools; _.each(guess, function(tool) { if (tool.first != null) { ang = atan2( (tool.second.coord[1] - tool.first.coord[1]), (tool.second.coord[0] - tool.first.coord[0])); ang = ang < 0 ? ang + PI : ang; if (abs((ROT / 2) - ang) < 3 * PI / 180) { bisect = tool; } } }); if (bisect == null) { return false; } // next make sure there is a compass with // center on the center of the angle such that // its intersections with the angle are the // centers of two further compasses (yup) var middle = null; var bottom = null; var top = null; var comps = _.filter(guess, function(tool) { return tool.center != null; }); if (comps.length < 3) { return false; } _.each(comps, function(comp) { if(eDist(comp.center.coord, CENTER) < 0.5) { middle = comp; } }); if (middle == null) { return false; } var botInter = []; _.each(comps, function(comp) { botInter = [CENTER[0] + comp.radius, CENTER[1]]; if (eDist(comp.center.coord, botInter) < 0.5) { bottom = comp; } }); var topInter = []; _.each(comps, function(comp) { topInter = [CENTER[0] + applyRefFrame([comp.radius, 0], ROT)[0], CENTER[1] + applyRefFrame([comp.radius, 0], ROT)[1]]; if( eDist(comp.center.coord, topInter) < 0.5) { top = comp; } }); if (bottom == null || top == null) { return false; } return true;
showConstructionGuess(guess);
var farPoint = [ applyRefFrame([10, 0], ROT / 2)[0] + CENTER[0], applyRefFrame([10 , 0], ROT / 2)[1] + CENTER[1] ]; graph.bisect = line(CENTER, farPoint, { stroke: BLUE, strokeWidth: 1 }); graph.bisect.toBack();

We could just draw a line and try to make it bisect the angle, but that's difficult to do and there is no guarantee it's a perfect bisector.

graph.hintLines = raphael.set(); graph.intersect1 = [CENTER[0] + 2, CENTER[1]]; graph.intersect2 = [ applyRefFrame([2, 0], ROT)[0] + CENTER[0], applyRefFrame([2, 0], ROT)[1] + CENTER[1]]; circle(graph.intersect1, 0.08, { stroke: null, fill: BLUE }); circle(graph.intersect2, 0.08, { stroke: null, fill: BLUE }); graph.hintLines.push(drawHintLine(CENTER, graph.intersect1, 1)); graph.hintLines.push(drawHintLine(CENTER, graph.intersect2, 1)); graph.hintLines.push(drawHintLine(graph.intersect1, [ applyRefFrame([2.5, 0], ROT / 2)[0] + CENTER[0], applyRefFrame([2.5, 0], ROT / 2)[1] + CENTER[1]], 2)); graph.hintLines.push(drawHintLine(graph.intersect2, [ applyRefFrame([2.5, 0], ROT / 2)[0] + CENTER[0], applyRefFrame([2.5, 0], ROT / 2)[1] + CENTER[1]], 2)); graph.hintLines.push(drawHintLine(graph.intersect1, [ applyRefFrame([4, 0], ROT / 2)[0] + CENTER[0], applyRefFrame([4, 0], ROT / 2)[1] + CENTER[1]], 3)); graph.hintLines.push(drawHintLine(graph.intersect2, [ applyRefFrame([4, 0], ROT / 2)[0] + CENTER[0], applyRefFrame([4, 0], ROT / 2)[1] + CENTER[1]], 3)); graph.hintLines.toBack();

If we pick any two points on the given lines that are the same distance from the vertex of the angle, every point on the bisector line will be equidistant from those points.

circle(CENTER, 0.08, { stroke: null, fill: GRAY }); circle(CENTER, 2, { fill: null, stroke: GRAY, strokeWidth: 1, strokeDasharray: "- " });

We can use a compass centered at the vertex to find two points equidistant from the vertex.

graph.bisect.remove(); circle(graph.intersect1, 0.08, { stroke: null, fill: GRAY }); graph.compass1 = circle(graph.intersect1, 1.5, { fill: null, stroke: GRAY, strokeWidth: 1, strokeDasharray: "- " }); circle(graph.intersect2, 0.08, { stroke: null, fill: GRAY }); graph.compass2 = circle(graph.intersect2, 1.8, { fill: null, stroke: GRAY, strokeWidth: 1, strokeDasharray: "- " }); graph.hintLines.remove();

If we use two more compasses centered at each of the two points, we can see that they intersect, but not on the angle bisector! That's because the two circles are not the same size.

graph.compass1.animate({ rx: scaleVector(2)[0], ry: scaleVector(2)[1] }, 250); graph.compass2.animate({ rx: scaleVector(2)[0], ry: scaleVector(2)[1] }, 250);

A nice way to make the circles the same size is to set the edges of both circles so they pass through the vertex.

circle([CENTER[0] + 2 + 2 * cos(ROT), CENTER[1] + 2 * sin(ROT)], 0.08, { stroke: null, fill: GRAY }); line([ applyRefFrame([-10, 0], ROT / 2)[0] + CENTER[0], applyRefFrame([-10 , 0], ROT / 2)[1] + CENTER[1] ], [ applyRefFrame([10, 0], ROT / 2)[0] + CENTER[0], applyRefFrame([10 , 0], ROT / 2)[1] + CENTER[1] ], { stroke: GRAY, strokeWidth: 1, strokeDasharray: "- " });

Use a straightedge to connect the vertex to the point where the last two circles intersect. This line is the angle bisector.