block by nitaku 7023140

Gosper hex tiling

Full Screen

Create cells of an hexagonal tiling by following a Gosper space-filling curve.

This example uses the rendering approach introduced here, and the fractal generation technique introduced here.

Same as this example, but with a simpler animation.

index.js

(function() {
  var 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, _len, _ref;
    input = config.axiom;
    for (i = 0, _ref = config.steps; 0 <= _ref ? i < _ref : i > _ref; 0 <= _ref ? i++ : i--) {
      output = '';
      for (_i = 0, _len = input.length; _i < _len; _i++) {
        char = input[_i];
        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(490,30)');
    /* 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
  */

  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', '#76B0DA').transition().duration(100).each('end', (function() {
      if (size > data.length) return;
      return redraw(data, size + 1);
    }));
  };

}).call(this);

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Gosper Hexagon Tiling</title>
        <link type="text/css" href="index.css" rel="stylesheet"/>
        <script src="//d3js.org/d3.v3.min.js"></script>
        <script src="index.js"></script>
    </head>
    <body onload="main()"></body>
</html>

index.coffee

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(490,30)')
        
    ### 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: (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 ###
redraw = (data, size) ->
    global.vis.selectAll('.hex')
        .data(data[0...size])
      .enter().append('path')
        .attr('class', 'hex')
        .attr('d', global.path_generator)
        .attr('fill','#76B0DA')
      .transition().duration(100)
        .each('end', (() ->
            if size > data.length
                return
            redraw(data, size+1)
        ))
        

index.css

.hex {
  stroke: black;
  stroke-width: 1;
}

index.sass

.hex
    stroke: black
    stroke-width: 1