Last active
August 29, 2015 14:19
-
-
Save Rantanen/ad9e0f501967ee7141a7 to your computer and use it in GitHub Desktop.
Fast packing hexagon with circles
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype HTML> | |
<html> | |
<head> | |
</head> | |
<body> | |
<h1>Packing hexagon with circles</h1> | |
<canvas id="canvas1" width="1000" height="800"></canvas> | |
<h1>Intermediate (u,v) coordinates</h1> | |
<canvas id="canvas2" width="1000" height="1000"></canvas> | |
<script> | |
// Our parameters | |
var radius_m = 400; | |
var diameter_circle = 10; | |
// A magic number we'll calculate here just to save some performance later. | |
// Ignore for now. | |
var magic = Math.sqrt( 3.0 ) / 6.0 - 0.5; | |
// Calculate how many circles there will be one one of the hexagon "spokes": | |
// _____________ | |
// /o /\ | |
// / o / \ | |
// / o / \ | |
// / o / \ | |
// / o / \ | |
// / o / \ | |
// /____________o____________\ | |
// \ / \ / | |
// | |
var radius = Math.floor( radius_m / diameter_circle ); | |
// Let's assume vectors u and v that draw the hexagon: | |
// | |
// In (x,y) space the hexagon looks like: | |
// ______ | |
// /\ \ | |
// / \ \ | |
// /____\ ____\ | |
// ^ \ / | |
// u\ \ / | |
// \ ___>\/ | |
// v | |
// (Fig 1) | |
// | |
// Two of the triangle edges are omitted in the figure above, because... | |
// In (u,v) vector space the hexagon is tilted: | |
// ____ | |
// /| | | |
// / | | | |
// /____|____| | |
// ^ | / | |
// u | | / | |
// |___>|/ | |
// v | |
// (Fig 2) | |
// | |
// Assuming the centerpoint is (0,0) this means the "hexagon" is contained in a | |
// square going from: | |
// | |
// | |
// / u = v + 1 | |
// 1 /___ | |
// /| | / u = v - 1 | |
// / | | / | |
// /____|____| / | |
// / ^ | / | |
// / | | / | |
// -1 |___>|/ | |
// / | |
// -1 1 | |
// (Fig 3) | |
// | |
// So essentially the whole hexagon in (u,v) space is defined as: | |
// | |
// { (u,v) : -1 < u < 1 && -1 < v < 1 && u < v + 1 && u > v - 1 } | |
// | |
// To scale the hexagon we'll just substitute 1 and -1 with radius and -radius | |
// defined above. | |
var uv_circles = []; | |
var circles = []; | |
var start = Date.now(); | |
// This gives us the following for loop which spans the full square (ignoring | |
// clipped corners). | |
for( var u = -radius; u <= radius; u++ ) { | |
for( var v = -radius; v <= radius; v++ ) { | |
// To remove the clipped corners we need to ignore the cases where: | |
// u > v + 1 or u < v - 1 | |
if( u > v + radius || u < v - radius ) | |
continue; | |
// Now (u,v) will span the Fig 2 shape. | |
uv_circles.push([ u, v ]); | |
// Now we could use some fancy vector maths to turn the (u,v) | |
// coordinates into (x,y) coordinates suitable for the mercerator, but | |
// if we use one more trick we can optimize that transformation. | |
// | |
// Instead of having the vectors spanning (x,y) shape in Fig 1, we can | |
// rotate the diamonds a bit: | |
// | |
// ______ \''---,, | |
// \ \ \ \ | |
// \ \ => \ \ | |
// \_____\ ''---,,\ | |
// (Fig 4) | |
// | |
// Now instead of being sheared like the original shape the new shape | |
// is only scaled along the diagonal. This makes it easy to transform | |
// the square-(u,v) shape into the diamond shape required for the | |
// hexagon by offsetting the points by their distance. | |
// | |
// Although the scaling changes the magnitude of the coordinates so we | |
// need to further scale the x/y coordiantes with ( 1 - magic ) | |
// | |
// 'magic' here is a number I won't explain in detail (as I can't | |
// remember the formulas I used to calculate it in the first place. | |
// :p). Essentially it's the answer to: | |
// | |
// How much a point u or v must be offset to turn square from Fig 2 | |
// into the diamond in Fig 4. | |
// | |
var offset = ( u + v ) * magic; | |
x = ( u + offset ) * ( 1 - magic ); | |
y = ( v + offset ) * ( 1 - magic ); | |
// This gives us coordinates in our ideal coordinate system. For the | |
// real world coordinates these need to be further scaled by the circle | |
// diameter. | |
x *= diameter_circle; | |
y *= diameter_circle; | |
circles.push([ x, y ]); | |
} | |
} | |
// Report performance | |
console.log( circles.length + ' circles generated in ' + ( Date.now() - start ) + ' ms' ); | |
// Let's draw the circles for fun. | |
// | |
// Our canvas. | |
var canvas = document.getElementById( 'canvas1' ); | |
var context = canvas.getContext( '2d' ); | |
var centerX = canvas.width / 2; | |
var centerY = canvas.height / 2; | |
// Let's first draw the ideal circle: | |
context.beginPath(); | |
context.arc( | |
centerX, | |
centerY, | |
radius_m, | |
0, 2 * Math.PI, false ); | |
context.strokeStyle = '#ff0000'; | |
context.stroke(); | |
start = Date.now(); | |
for( var c in circles ) { | |
var x = circles[c][0]; | |
var y = circles[c][1]; | |
// Now (u,v) will span the Fig 2 shape. | |
context.beginPath(); | |
context.arc( | |
x + centerX, | |
y + centerY, | |
diameter_circle / 2, | |
0, 2 * Math.PI, false ); | |
context.strokeStyle = '#00ff00'; | |
context.stroke(); | |
} | |
// Report performance | |
console.log( circles.length + ' circles drawn in ' + ( Date.now() - start ) + ' ms' ); | |
// For visualization let's draw the (u,v) circles as well. | |
var canvas = document.getElementById( 'canvas2' ); | |
var context = canvas.getContext( '2d' ); | |
var centerX = canvas.width / 2; | |
var centerY = canvas.height / 2; | |
start = Date.now(); | |
for( var c in uv_circles ) { | |
var x = uv_circles[c][0]; | |
var y = uv_circles[c][1]; | |
// Now (u,v) will span the Fig 2 shape. | |
context.beginPath(); | |
context.arc( | |
x * diameter_circle + centerX, | |
y * diameter_circle + centerY, | |
diameter_circle / 2, | |
0, 2 * Math.PI, false ); | |
context.strokeStyle = '#00ff00'; | |
context.stroke(); | |
} | |
</script> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The essential function without comments: | |
var radius_m = 400; | |
var diameter_circle = 10; | |
var radius = Math.floor( radius_m / diameter_circle ); | |
var magic = Math.sqrt( 3.0 ) / 6.0 - 0.5; | |
var circles = []; | |
for( var u = -radius; u <= radius; u++ ) { | |
for( var v = -radius; v <= radius; v++ ) { | |
if( u > v + radius || u < v - radius ) | |
continue; | |
var offset = ( u + v ) * magic; | |
circles.push([ | |
( u + offset ) * ( 1 - magic ) * diameter_circle, // x | |
( v + offset ) * ( 1 - magic ) * diameter_circle, // y | |
}); | |
} | |
} | |
console.log( circles ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment