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/1036b853c3f52b8994a4 to your computer and use it in GitHub Desktop.
Save nitaku/1036b853c3f52b8994a4 to your computer and use it in GitHub Desktop.
Gosper hex tiling irregularity

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

It can be noticed that a standard Gosper curve does not produce nice-looking shapes at each fractal recursion, whereas someone would expect a (quasi-)hexagonal shape. Compare this drawing to the previous example, in which a Node Gosper curve is used to solve this problem.

It seems easy to alter the L-system parameters of the standard Gosper curve to obtain the intended result: adding a cell or two at the beginning of the sequence seems to be enough, but, to the best of our knowledge, it is not sufficient (try changing the axiom and see what happens!). The Node Gosper curve use instead a different set of rules (commented out in the code).

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) rotate(120)')
### 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>Gosper Hexagon Tiling irregularity</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) rotate(120)');
/* 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'
}
});
/* 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