Relevant live-javascript/jsbin: http://jsbin.com/ekuyam/2/edit
I basically just generate points in a hexagon relative to the center of the canvas and then draw circles at each point in the hexagon, but on close inspection the circles don't align properly, it's not actually visible though unless you use large circles.
When I use small circles they seem to be placed on the canvas pretty accurately, as seen here:
But on closer inspection by using larger circles (kind of like zooming in), it's revealed that there is some inaccuracy in the math, as can be seen by the unwanted spaces/areas here:
The lines should align perfectly but instead I get some unwanted space. Why is this and how can I amend it?
Code:
function hexagon(w, h, p) {
var points = 6;
var width = w;
var height = h;
var angle = ((2 * Math.PI) / points);
var hexagon = [];
for (i = 0; i < points; i++) {
hexagon.push({
x: width * Math.sin(angle * i) + p.x,
y: height * Math.cos(angle * i) + p.y
})
}
return hexagon
}
var stage = new Kinetic.Stage({
container: "container",
width: 600,
height: 600
});
var layer = new Kinetic.Layer();
var group = new Kinetic.Group({x: 600/2, y: 600/2, draggable: false});
var radius = 1000;
var s = new Kinetic.Circle({
radius: radius,
stroke: 'black',
strokeWidth: 1,
draggable: true
});
group.add(s);
var hex_points = hexagon(radius, radius, {x: 600/2, y: 600/2});
for (p in hex_points) {
var s = new Kinetic.Circle({
radius: radius,
stroke: 'black',
strokeWidth: 1,
draggable: true
});
s.setPosition({x: hex_points[p].x - 600/2, y: hex_points[p].y - 600/2})
group.add(s);
}
layer.add(group);
stage.add(layer);
This is because sin(2 * Math.PI) / 6 = sqrt(3)/2 which is an irrational number. However floats can only represent rational numbers (actually only with a denominator which is a power of 2). Thus you will always have some rounding error. If you magnify the circles enough then this rounding error becomes visible. In order to prevent this you need to limit the magnification.
If you absolutely have to get around it the simplest approach is to go for higher arithmetic precision.
Another approach would be to use the formula (x-x_0)^2 + (y-y_0)^2 = R^2 and solve it locally around the known intersection point WITHOUT using trigonometric functions. To be more specific, the function f(x,y):= (x-x_0)^2+(y-y_0)^2 satisfies the conditions for the inverse function theorem everywhere but x=x_0 and y=y_0. Notice that the circle example is contained in another wikipedia article on the implicit function theorem. The example at hand is a holomorphic function thus the local inverse will also be holomorphic. Especially it has a good local approximation by Taylor series expansion.. The point of this approach is that you do not draw the circle around the center put draw it "through the desired intersection point". Thus you can enforce that it will "intersect" this point. But keep in mind that he "intersection point" will be only an approximation as floats will never properly represent sqrt(3) or rational multiples thereof.
Now what to do? What I would do: once the magnification is such that there is only one intersection point on the screen I would cheat by substituting R such that R^2 = (x_i-x_0)^2+(y_i-y_0)^2 where (x_i, y_i) is the desired intersection point and "(x_0,y_0)" the center of the circle. Then I would proceed with the standard circle formulas.
A very much simpler approach would be to ensure that the circle lines get scaled as well. Thus for high magnification the imprecisions would be hidden under the "thick" perimeter of the circles.