Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active August 29, 2015 14:23
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 nitaku/0c991d6ed0e994e1c7ed to your computer and use it in GitHub Desktop.
Save nitaku/0c991d6ed0e994e1c7ed to your computer and use it in GitHub Desktop.
Node Gosper hex tiling

This experiment creates cells of an hexagonal tiling by following a Node Gosper curve. The drawing highlights groups of 7 and 49 (7^2) cells.

Thanks to the properties of the curve, each fractal recursion gives a nice, almost hexagonal shape (not exactly an hexagon, since hexagons are not rep-tiles). The perimeter of the obtained "island" is also less jagged with respect to a tiling that follows the standard Gosper curve (in fact, the two cannot even be overlapped onto each other: compare).

The aforementioned properties can prove to be useful when using such tilings to produce GosperMaps like this one, or this older one.

global = {}
### compute a Lindenmayer system given an axiom, a number of steps and rules ###
fractalize = (config) ->
input = config.axiom
for i in [0...config.steps]
output = ''
for char in input
if char of config.rules
output += config.rules[char]
else
output += char
input = output
return output
### convert a Lindenmayer string into an array of hexagonal coordinates ###
hex_coords = (config) ->
directions = [
{x:+1, y:-1, z: 0},
{x:+1, y: 0, z:-1},
{x: 0, y:+1, z:-1},
{x:-1, y:+1, z: 0},
{x:-1, y: 0, z:+1},
{x: 0, y:-1, z:+1}
]
### start the walk from the origin cell, facing east ###
path = [{x:0,y:0,z:0}]
dir_i = 0
for char in config.fractal
if char == '+'
dir_i = (dir_i+1) % directions.length
else if char == '-'
dir_i = dir_i-1
if dir_i == -1
dir_i = 5
else if char == 'F'
dir = directions[dir_i]
current = path[path.length-1]
path.push {x:current.x+dir.x, y:current.y+dir.y, z:current.z+dir.z}
return path
window.main = () ->
width = 960
height = 500
svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
global.vis = svg.append('g')
.attr('transform', 'translate(660,360)')
### create the Gosper curve ###
gosper = fractalize
axiom: 'A'
steps: 3
rules:
# A: 'A+BF++BF-FA--FAFA-BF+'
# B: '-FA+BFBF++BF+FA--FA-B'
A: 'A-FB--FB-F++AF++A-F+AF+B-'
B: '+A-FB-F+B--FB--F+AF++AF+B'
### convert the curve into coordinates of hex cells ###
data = hex_coords
fractal: gosper
### create the GeoJSON hexes ###
hexes = {
type: 'FeatureCollection',
features: (new_hex(d) for d in data)
}
### custom projection to make hexagons appear regular (y axis is also flipped) ###
radius = 12
dx = radius * 2 * Math.sin(Math.PI / 3)
dy = radius * 1.5
global.path_generator = d3.geo.path()
.projection d3.geo.transform({
point: (x,y) -> this.stream.point(x * dx / 2, -(y - (2 - (y & 1)) / 3) * dy / 2)
})
### start the animation ###
redraw(hexes.features, 1)
### draw the origin ###
global.vis.append('circle')
.attr('cx', 0)
.attr('cy', 0)
.attr('r', 3)
### create a new hexagon ###
new_hex = (d) ->
### conversion from hex coordinates to rect ###
x = 2*(d.x + d.z/2.0)
y = 2*d.z
return {
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [[
[x, y+2],
[x+1, y+1],
[x+1, y],
[x, y-1],
[x-1, y],
[x-1, y+1],
[x, y+2]
]]
}
}
### update the drawing, then call again this function till data ends ###
color = (i) ->
i_7 = Math.floor(i/7)
i_49 = Math.floor(i/49)
return d3.hcl((i_7%7)*360/7, 30, switch i_49
when 0 then 80
when 1 then 55
when 2 then 30
when 3 then 55
when 4 then 80
when 5 then 55
when 6 then 80)
redraw = (data, size) ->
global.vis.selectAll('.hex')
.data(data[0...size])
.enter().append('path')
.attr('class', 'hex')
.attr('d', global.path_generator)
.attr('fill', (d, i) ->
return color(i)
)
.transition().duration(60)
.each('end', (() ->
if size > data.length
return
redraw(data, size+1)
))
.hex {
stroke: black;
stroke-width: 1;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Node Gosper Hexagon Tiling</title>
<link type="text/css" href="index.css" rel="stylesheet"/>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="index.js"></script>
</head>
<body onload="main()"></body>
</html>
// Generated by CoffeeScript 1.4.0
(function() {
var color, fractalize, global, hex_coords, new_hex, redraw;
global = {};
/* compute a Lindenmayer system given an axiom, a number of steps and rules
*/
fractalize = function(config) {
var char, i, input, output, _i, _j, _len, _ref;
input = config.axiom;
for (i = _i = 0, _ref = config.steps; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
output = '';
for (_j = 0, _len = input.length; _j < _len; _j++) {
char = input[_j];
if (char in config.rules) {
output += config.rules[char];
} else {
output += char;
}
}
input = output;
}
return output;
};
/* convert a Lindenmayer string into an array of hexagonal coordinates
*/
hex_coords = function(config) {
var char, current, dir, dir_i, directions, path, _i, _len, _ref;
directions = [
{
x: +1,
y: -1,
z: 0
}, {
x: +1,
y: 0,
z: -1
}, {
x: 0,
y: +1,
z: -1
}, {
x: -1,
y: +1,
z: 0
}, {
x: -1,
y: 0,
z: +1
}, {
x: 0,
y: -1,
z: +1
}
];
/* start the walk from the origin cell, facing east
*/
path = [
{
x: 0,
y: 0,
z: 0
}
];
dir_i = 0;
_ref = config.fractal;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
char = _ref[_i];
if (char === '+') {
dir_i = (dir_i + 1) % directions.length;
} else if (char === '-') {
dir_i = dir_i - 1;
if (dir_i === -1) {
dir_i = 5;
}
} else if (char === 'F') {
dir = directions[dir_i];
current = path[path.length - 1];
path.push({
x: current.x + dir.x,
y: current.y + dir.y,
z: current.z + dir.z
});
}
}
return path;
};
window.main = function() {
var d, data, dx, dy, gosper, height, hexes, radius, svg, width;
width = 960;
height = 500;
svg = d3.select('body').append('svg').attr('width', width).attr('height', height);
global.vis = svg.append('g').attr('transform', 'translate(660,360)');
/* create the Gosper curve
*/
gosper = fractalize({
axiom: 'A',
steps: 3,
rules: {
A: 'A-FB--FB-F++AF++A-F+AF+B-',
B: '+A-FB-F+B--FB--F+AF++AF+B'
}
});
/* convert the curve into coordinates of hex cells
*/
data = hex_coords({
fractal: gosper
});
/* create the GeoJSON hexes
*/
hexes = {
type: 'FeatureCollection',
features: (function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = data.length; _i < _len; _i++) {
d = data[_i];
_results.push(new_hex(d));
}
return _results;
})()
};
/* custom projection to make hexagons appear regular (y axis is also flipped)
*/
radius = 12;
dx = radius * 2 * Math.sin(Math.PI / 3);
dy = radius * 1.5;
global.path_generator = d3.geo.path().projection(d3.geo.transform({
point: function(x, y) {
return this.stream.point(x * dx / 2, -(y - (2 - (y & 1)) / 3) * dy / 2);
}
}));
/* start the animation
*/
redraw(hexes.features, 1);
/* draw the origin
*/
return global.vis.append('circle').attr('cx', 0).attr('cy', 0).attr('r', 3);
};
/* create a new hexagon
*/
new_hex = function(d) {
/* conversion from hex coordinates to rect
*/
var x, y;
x = 2 * (d.x + d.z / 2.0);
y = 2 * d.z;
return {
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [[[x, y + 2], [x + 1, y + 1], [x + 1, y], [x, y - 1], [x - 1, y], [x - 1, y + 1], [x, y + 2]]]
}
};
};
/* update the drawing, then call again this function till data ends
*/
color = function(i) {
var i_49, i_7;
i_7 = Math.floor(i / 7);
i_49 = Math.floor(i / 49);
return d3.hcl((i_7 % 7) * 360 / 7, 30, (function() {
switch (i_49) {
case 0:
return 80;
case 1:
return 55;
case 2:
return 30;
case 3:
return 55;
case 4:
return 80;
case 5:
return 55;
case 6:
return 80;
}
})());
};
redraw = function(data, size) {
return global.vis.selectAll('.hex').data(data.slice(0, size)).enter().append('path').attr('class', 'hex').attr('d', global.path_generator).attr('fill', function(d, i) {
return color(i);
}).transition().duration(60).each('end', (function() {
if (size > data.length) {
return;
}
return redraw(data, size + 1);
}));
};
}).call(this);
.hex
stroke: black
stroke-width: 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment