As my previous approach doesn't seem to work and a solution would be rather complex, I have decided to try another approach which might be a little bit simpler.
This time, before the code draws any hexagon, it has to determine as how many rows and columns can fit in the pre-defined circle and based on this outcome it then starts drawing all the hexagons.
So far it kind of work, but as in my previous approach, there are times when the hexes are overlapping , or leaving a large gap in the lower part of the circle.
Another concern is , how do I format these hexagons into a grid?
Note, there is a small slider under the canvas, that lets you increase/decrease circle's radius and redraw the hexagons.
var c_el = document.getElementById("myCanvas");
var ctx = c_el.getContext("2d");
var canvas_width = c_el.clientWidth;
var canvas_height = c_el.clientHeight;
var circle = {
r: 120, /// radius
pos: {
x: (canvas_width / 2),
y: (canvas_height / 2)
}
}
var hexagon = {
r: 20,
pos:{
x: 0,
y: 0
}
}
var hex_w = hexagon.r * 2;
var hex_h = Math.floor( Math.sqrt(3) * hexagon.r );
var hex_s = (3/2) * hexagon.r;
fill_CircleWithHex( circle );
function fill_CircleWithHex(circle){
drawCircle( circle );
var c_h = circle.r * 2; /// circle height ////
var c_w = c_h; //// circle width /////
var max_hex_H = Math.round( c_h / hex_h );
var row_sizes = []
for(var row= 0; row< max_hex_H; row++){
var d = circle.r - ( row* hex_h); //// distance from circle's center to the row's chord ////
var c = 2 * (Math.sqrt((circle.r*circle.r) - (d * d))); /// length of the row's chord ////
var row_length = Math.floor(c / (hexagon.r * 3));
row_sizes.push( row_length )
}
console.log("circle_r : "+circle.r);
console.log("hex_r : "+hexagon.r);
console.log("max_hex_H : "+max_hex_H);
console.log("max_hex_W : ", row_sizes)
for(var row = 0; row < row_sizes.length; row++){
var max_hex_W = row_sizes[row];
var x_offset = Math.floor((c_w - (max_hex_W * hex_w)) / 2);
for(var col = 1; col < max_hex_W; col++){
hexagon.pos.x = (col * hex_w) + (circle.pos.x - circle.r) + x_offset ;
hexagon.pos.y = (row * hex_h) + (circle.pos.y - circle.r);
ctx.fillText(row+""+col, hexagon.pos.x - 6, hexagon.pos.y+4);
drawHexagon(hexagon)
}
}
}
function drawHexagon(hex){
var angle_deg, angle_rad, cor_x, cor_y;
ctx.beginPath();
for(var c=0; c <= 5; c++){
angle_deg = 60 * c;
angle_rad = (Math.PI / 180) * angle_deg;
cor_x = hex.r * Math.cos(angle_rad); //// corner_x ///
cor_y = hex.r* Math.sin(angle_rad); //// corner_y ///
if(c === 0){
ctx.moveTo(hex.pos.x+ cor_x, hex.pos.y+cor_y);
}else{
ctx.lineTo(hex.pos.x+cor_x, hex.pos.y+cor_y);
}
}
ctx.closePath();
ctx.stroke();
}
function drawCircle( circle ){
ctx.beginPath();
ctx.arc(circle.pos.x, circle.pos.y, circle.r, 0, 2 * Math.PI);
ctx.stroke();
}
$(function() {
$( "#slider" ).slider({
max: 200,
min:0,
value:100,
create: function( event, ui ) {
$("#value").html( $(this).slider('value') );
},
change: function( event, ui ) {
$("#value").html(ui.value);
},
slide: function( event, ui){
$("#value").html(ui.value);
circle.r = ui.value;
ctx.clearRect(0,0, canvas_width, canvas_height);
fill_CircleWithHex(circle);
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<canvas id="myCanvas" width="350" height="250" style="border:1px solid #d3d3d3;"> </canvas>
<div style="width: 200px; height: 40px;">
<div id="slider" style="position:relative; width: 150px; top: 4px;float: left;"></div> <div id="value" style="float: left;"> 0 </div>
</div>
Solution. Since a regular hexagon divides the circle into six equal parts and there are 360 degrees in the circle, each side of the regular hexagon should span a chord of 60 degrees on the circle.
The area of the hexagon can be found by splitting it into six equilateral triangles and the total area is 6\times (1/\sqrt{3}) \times 1 = 2\sqrt{3} square units. To get the proportion of the plane covered by the circles we must divide by pi by 2\sqrt{3} to get 0.90689\ldots or 90.7\% to 3 significant figures.
Use more boxes of the grid paper to draw a large hexagon; less boxes to draw a small hexagon. For this example, draw a square that is five boxes long by five boxes deep. Press down lightly on the pencil when drawing the square. Darken the lines of the three middle boxes on the top and the bottom of your square.
The following solves the packing problem for a regular honeycomb structure centered on the circle's midpoint. Regular means:
The coordinates of the individual hexagons represent the ordinal number of the hexagon shell countered from the center and the clockwise sequence number starting at high noon.
As the circle widens, new hexagon shells do not necessarily get filled as a whole. Though the degree of freedom to fill the outer shell partially produces an improved solution, it is still not optimal. Relaxing the regularity to rotational symmetries wrt other angles than 60 deg ( namely 120 and 180 deg ) will permit a higher coverage of the circle's interior.
I shall look into the math behind that for the next revision of this code (and possibly find a theorem to prove rotational symmetry around the circle'smidpoint is a necessary condition for optimality).
var c_el;
var ctx;
var canvas_width;
var canvas_height;
var circle;
var hexagon;
var hex_w;
var hex_h;
var hex_s;
var delta;
function drawHexagonAt ( po_ctr_hex, pn_circle, pn_sector ) {
var cur
;
cur = { x: po_ctr_hex.x - 0.5 * hexagon.r, y: po_ctr_hex.y - delta };
ctx.beginPath();
ctx.moveTo(cur.x, cur.y);
cur.x = cur.x + hexagon.r;
cur.y = cur.y;
ctx.lineTo(cur.x, cur.y);
cur.x = cur.x + hexagon.r / 2;
cur.y = cur.y + delta;
ctx.lineTo(cur.x, cur.y);
cur.x = cur.x - hexagon.r / 2;
cur.y = cur.y + delta;
ctx.lineTo(cur.x, cur.y);
cur.x = cur.x - hexagon.r;
cur.y = cur.y;
ctx.lineTo(cur.x, cur.y);
cur.x = cur.x - hexagon.r / 2;
cur.y = cur.y - delta;
ctx.lineTo(cur.x, cur.y);
cur.x = cur.x + hexagon.r / 2;
cur.y = cur.y - delta;
ctx.lineTo(cur.x, cur.y);
ctx.closePath();
ctx.stroke();
cur.x = cur.x + hexagon.r / 2;
cur.y = cur.y + delta;
ctx.fillText(pn_circle + "/" + pn_sector, cur.x-6, cur.y+4);
} // drawHexagonAt
function fill_CircleWithHex(circle){
drawCircle( circle );
var radacc2;
var iter = 0;
var sector = 0;
var i, j;
var ctr = { x: circle.pos.x , y: circle.pos.y };
var cur = { x: 0 , y: 0 };
delta = Math.floor(Math.sqrt(3) * 0.5 * hexagon.r);
radacc2 = hexagon.r * hexagon.r;
while ( (radacc2 < circle.r * circle.r) ) {
cur.x = ctr.x;
cur.y = ctr.y - iter * 2 * delta;
if (iter === 0) {
drawHexagonAt ( cur, 0, 0 );
}
else {
for ( var i=0; i < 6; i++ ) {
// j-loops -- next honeycomb
sector = 0;
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x + 1.5 * hexagon.r;
cur.y = cur.y + delta;
drawHexagonAt ( cur, iter, sector++ );
}
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x;
cur.y = cur.y + 2 * delta;
drawHexagonAt ( cur, iter, sector++ );
}
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x - 1.5 * hexagon.r;
cur.y = cur.y + delta;
drawHexagonAt ( cur, iter, sector++ );
}
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x - 1.5 * hexagon.r;
cur.y = cur.y - delta;
drawHexagonAt ( cur, iter, sector++ );
}
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x;
cur.y = cur.y - 2 * delta;
drawHexagonAt ( cur, iter, sector++ );
}
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x + 1.5 * hexagon.r;
cur.y = cur.y - delta;
drawHexagonAt ( cur, iter, sector++ );
}
} // i-loop -- meta-honeycomb
} // if -- Iteration 1 vs. n > 1
// radacc update
iter++;
radacc2 = ((2*iter + 1) * delta) * ((2*iter + 1) * delta) + hexagon.r * hexagon.r / 4;
} // while -- komplette Shells
//
// Partielle Shells
//
var proceed;
do {
cur.x = ctr.x;
cur.y = ctr.y - iter * 2 * delta;
proceed = false;
for ( var i=0; i < 6; i++ ) {
// j-loops -- next honeycomb
sector = 0;
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x + 1.5 * hexagon.r;
cur.y = cur.y + delta;
sector++
if ( Math.sqrt ( ( cur.x - ctr.x) * ( cur.x - ctr.x) + ( cur.y - ctr.y) * ( cur.y - ctr.y) ) + hexagon.r < circle.r ) {
drawHexagonAt ( cur, iter, sector );
proceed = true;
}
}
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x;
cur.y = cur.y + 2 * delta;
sector++
if ( Math.sqrt ( ( cur.x - ctr.x) * ( cur.x - ctr.x) + ( cur.y - ctr.y) * ( cur.y - ctr.y) ) + hexagon.r < circle.r ) {
drawHexagonAt ( cur, iter, sector );
proceed = true;
}
}
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x - 1.5 * hexagon.r;
cur.y = cur.y + delta;
sector++
if ( Math.sqrt ( ( cur.x - ctr.x) * ( cur.x - ctr.x) + ( cur.y - ctr.y) * ( cur.y - ctr.y) ) + hexagon.r < circle.r ) {
drawHexagonAt ( cur, iter, sector );
proceed = true;
}
}
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x - 1.5 * hexagon.r;
cur.y = cur.y - delta;
sector++
if ( Math.sqrt ( ( cur.x - ctr.x) * ( cur.x - ctr.x) + ( cur.y - ctr.y) * ( cur.y - ctr.y) ) + hexagon.r < circle.r ) {
drawHexagonAt ( cur, iter, sector );
proceed = true;
}
}
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x;
cur.y = cur.y - 2 * delta;
sector++
if ( Math.sqrt ( ( cur.x - ctr.x) * ( cur.x - ctr.x) + ( cur.y - ctr.y) * ( cur.y - ctr.y) ) + hexagon.r < circle.r ) {
drawHexagonAt ( cur, iter, sector );
proceed = true;
}
}
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x + 1.5 * hexagon.r;
cur.y = cur.y - delta;
sector++
if ( Math.sqrt ( ( cur.x - ctr.x) * ( cur.x - ctr.x) + ( cur.y - ctr.y) * ( cur.y - ctr.y) ) + hexagon.r < circle.r ) {
drawHexagonAt ( cur, iter, sector );
proceed = true;
}
}
} // i-loop -- meta-honeycomb
iter++;
} while (proceed && (iter < 15));
} // fill_CircleWithHex
function drawCircle( circle ){
ctx.beginPath();
ctx.arc(circle.pos.x, circle.pos.y, circle.r, 0, 2 * Math.PI);
ctx.stroke();
}
$(function() {
$( "#slider" ).slider({
max: 200,
min:0,
value:100,
create: function( event, ui ) {
$("#value").html( $(this).slider('value') );
},
change: function( event, ui ) {
$("#value").html(ui.value);
},
slide: function( event, ui){
$("#value").html(ui.value);
circle.r = ui.value;
ctx.clearRect(0,0, canvas_width, canvas_height);
fill_CircleWithHex(circle);
}
});
});
$(document).ready(function () {
c_el = document.getElementById("myCanvas");
ctx = c_el.getContext("2d");
canvas_width = c_el.clientWidth;
canvas_height = c_el.clientHeight;
circle = {
r: 120, /// radius
pos: {
x: (canvas_width / 2),
y: (canvas_height / 2)
}
};
hexagon = {
r: 20,
pos:{
x: 0,
y: 0
}
};
hex_w = hexagon.r * 2;
hex_h = Math.floor( Math.sqrt(3) * hexagon.r );
hex_s = (3/2) * hexagon.r;
fill_CircleWithHex( circle );
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<canvas id="myCanvas" width="350" height="250" style="border:1px solid #d3d3d3;"> </canvas>
<div style="width: 200px; height: 40px;">
<div id="slider" style="position:relative; width: 150px; top: 4px;float: left;"></div> <div id="value" style="float: left;"> 0 </div>
</div>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With