Create a gist now

Instantly share code, notes, and snippets.

Gosper Islands IV

This example concludes the Gosper Islands series (III, II, I) introducing colored regions. Each region constitutes a distinct area inside the overall Gosper Island. Differently from this previous example, this experiment does not use L-system in order to draw the hexagons composing the island. Instead, the method is totally based on a recursive function that calculates the frotation, rotation, scale and translation of the hexagons.

REFRESH the page in order to get a different result.

window.hept_tree_utils = {
### Returns an array with 7 object nodes
###
get_children: () ->
return [{name: '', type: '', children: []}, {name: '', type: '', children: []}, {name: '', type: '', children: []}, {name: '', type: '', children: []}, {name: '', type: '', children: []}, {name: '', type: '', children: []}, {name: '', type: '', children: []}]
### Given a base10 number, returns the base7 conversion
###
base10_to_base7: (n) ->
base7 = []
while n > 0
base7.unshift(n%7)
n = Math.floor(n/7)
return base7
_align_arrays: (a, n) ->
for i in [0...Math.abs(n)]
a = [0].concat(a)
return a
align_arrays: (a, b) ->
n = a.length - b.length
if n > 0
b = hept_tree_utils._align_arrays b, n
else if n < 0
a = hept_tree_utils._align_arrays a, n
return [a, b]
### Given a base7 number, returns the corresponding HEPT TREE structure
###
make_tree: (node, n1, n2, name1, name2, index) ->
node.children = hept_tree_utils.get_children()
for child,i in node.children
# The digit of the base7 number matches the loop index: possible partially full node
if i == n1[index]
child.name = name1
# Avoid to expand the tree for the last digit in the base7 number
if index < n1.length-1
child.type = 'partially full'
if n1[index] == n2[index]
hept_tree_utils.make_tree child, n1, n2, name1, name2, index+1
else
hept_tree_utils.make_tree child, n1, [], name1, name2, index+1
else
if name2 != 'empty'
child.type = 'full'
child.name = name2
# The digit of the base7 number matches the loop index: possible partially full node
else if i == n2[index]
child.name = name2
# Avoid to expand the tree for the last digit in the base7 number
if index < n2.length-1
child.type = 'partially full'
hept_tree_utils.make_tree child, n2, [], name2, 'empty', index+1
else
child.type = 'full'
else if i <= n1[index]
child.name = name1
child.type = 'full'
else if i <= n2[index] or (n2.length == 0 and name2 != 'empty')
child.name = name2
child.type = 'full'
else
child.name = 'empty'
return node
###
###
base7_to_hex: (n1, n2, index, data) ->
for d,i in data
if i < n1[index]
d.class = 'n1'
else if i < n2[index]
d.class = 'n2'
else
d.class = 'empty'
return data
rotate_point: (point, theta) ->
theta = theta*Math.PI/180
new_point = {
x: point.x * Math.cos(theta) - point.y * Math.sin(theta)
y: point.x * Math.sin(theta) + point.y * Math.cos(theta)
}
### HEXAGONS
###
hex_generate_svg_path: (a, theta) ->
r = a / Math.sin(Math.PI/3)
points = [
hept_tree_utils.rotate_point {x: a, y: r/2}, theta
hept_tree_utils.rotate_point {x: 0, y: r}, theta
hept_tree_utils.rotate_point {x: -a, y: r/2}, theta
hept_tree_utils.rotate_point {x: -a, y: -r/2}, theta
hept_tree_utils.rotate_point {x: 0, y: -r}, theta
hept_tree_utils.rotate_point {x: a, y: -r/2}, theta
]
# Vertices right and left
#"M#{a} 0 L#{a/2} #{r} L#{-a/2} #{r} L#{-a} 0 L#{-a/2} #{-r} L#{a/2} #{-r} Z"
# Vertices up and down
"M#{a} #{r/2} L0 #{r} L#{-a} #{r/2} L#{-a} #{-r/2} L0 #{-r} L#{a} #{-r/2} Z"
#"M#{points[0].x} #{points[0].y} L#{points[1].x} #{points[1].y} L#{points[2].x} #{points[2].y} L#{points[3].x} #{points[3].y} L#{points[4].x} #{points[4].y} L#{points[5].x} #{points[5].y} Z"
}
// Generated by CoffeeScript 1.10.0
(function() {
window.hept_tree_utils = {
/* Returns an array with 7 object nodes
*/
get_children: function() {
return [
{
name: '',
type: '',
children: []
}, {
name: '',
type: '',
children: []
}, {
name: '',
type: '',
children: []
}, {
name: '',
type: '',
children: []
}, {
name: '',
type: '',
children: []
}, {
name: '',
type: '',
children: []
}, {
name: '',
type: '',
children: []
}
];
},
/* Given a base10 number, returns the base7 conversion
*/
base10_to_base7: function(n) {
var base7;
base7 = [];
while (n > 0) {
base7.unshift(n % 7);
n = Math.floor(n / 7);
}
return base7;
},
_align_arrays: function(a, n) {
var i, j, ref;
for (i = j = 0, ref = Math.abs(n); 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
a = [0].concat(a);
}
return a;
},
align_arrays: function(a, b) {
var n;
n = a.length - b.length;
if (n > 0) {
b = hept_tree_utils._align_arrays(b, n);
} else if (n < 0) {
a = hept_tree_utils._align_arrays(a, n);
}
return [a, b];
},
/* Given a base7 number, returns the corresponding HEPT TREE structure
*/
make_tree: function(node, n1, n2, name1, name2, index) {
var child, i, j, len, ref;
node.children = hept_tree_utils.get_children();
ref = node.children;
for (i = j = 0, len = ref.length; j < len; i = ++j) {
child = ref[i];
if (i === n1[index]) {
child.name = name1;
if (index < n1.length - 1) {
child.type = 'partially full';
if (n1[index] === n2[index]) {
hept_tree_utils.make_tree(child, n1, n2, name1, name2, index + 1);
} else {
hept_tree_utils.make_tree(child, n1, [], name1, name2, index + 1);
}
} else {
if (name2 !== 'empty') {
child.type = 'full';
}
child.name = name2;
}
} else if (i === n2[index]) {
child.name = name2;
if (index < n2.length - 1) {
child.type = 'partially full';
hept_tree_utils.make_tree(child, n2, [], name2, 'empty', index + 1);
} else {
child.type = 'full';
}
} else if (i <= n1[index]) {
child.name = name1;
child.type = 'full';
} else if (i <= n2[index] || (n2.length === 0 && name2 !== 'empty')) {
child.name = name2;
child.type = 'full';
} else {
child.name = 'empty';
}
}
return node;
},
/*
*/
base7_to_hex: function(n1, n2, index, data) {
var d, i, j, len;
for (i = j = 0, len = data.length; j < len; i = ++j) {
d = data[i];
if (i < n1[index]) {
d["class"] = 'n1';
} else if (i < n2[index]) {
d["class"] = 'n2';
} else {
d["class"] = 'empty';
}
}
return data;
},
rotate_point: function(point, theta) {
var new_point;
theta = theta * Math.PI / 180;
return new_point = {
x: point.x * Math.cos(theta) - point.y * Math.sin(theta),
y: point.x * Math.sin(theta) + point.y * Math.cos(theta)
};
},
/* HEXAGONS
*/
hex_generate_svg_path: function(a, theta) {
var points, r;
r = a / Math.sin(Math.PI / 3);
points = [
hept_tree_utils.rotate_point({
x: a,
y: r / 2
}, theta), hept_tree_utils.rotate_point({
x: 0,
y: r
}, theta), hept_tree_utils.rotate_point({
x: -a,
y: r / 2
}, theta), hept_tree_utils.rotate_point({
x: -a,
y: -r / 2
}, theta), hept_tree_utils.rotate_point({
x: 0,
y: -r
}, theta), hept_tree_utils.rotate_point({
x: a,
y: -r / 2
}, theta)
];
return "M" + a + " " + (r / 2) + " L0 " + r + " L" + (-a) + " " + (r / 2) + " L" + (-a) + " " + (-r / 2) + " L0 " + (-r) + " L" + a + " " + (-r / 2) + " Z";
}
};
}).call(this);
width = 960
height = 500
data = []
INITIAL_SCALE = height/2.4
GOSPER_ANGLE = Math.acos(5 * Math.sqrt(7) / 14) / Math.PI * 180
ROTATION_INDEXES = [0, 120, 0, -120, 0, 0, -120]
F_ROTATION_INDEXES = [-120, 0, 0, -120, 0, 120, 0]
color = d3.scale.ordinal()
.domain ["class1", "class2", "class3", "class4", "class5", "class6", "class7", ""]
.range ["#fb8072","#80b1d3","#fdb462","#b3de69","#8dd3c7","#ffffb3","#bebada","#f2f2f2"]
# SVG setup
svg = d3.select 'svg'
.attr
width: width
height: height
viewBox: "#{-width/2} #{-height/2} #{width} #{height}"
vis = svg.append 'g'
# Returns the coordinates of a hexagon
get_hept_coords = (scale, indexes, index_i) ->
# Hexagon apothem is calculated as
a = scale / Math.sqrt(7)
# Hexagon radius
r = a / Math.sin(Math.PI/3)
# Coordinates calculation
coords = [
{x: -a, y: r+r/2},
{x: a, y: r+r/2},
{x: 0, y: 0},
{x: -2*a, y: 0},
{x: -a, y: -r-r/2},
{x: a, y: -r-r/2},
{x: 2*a, y: 0}
]
flip = false
if index_i > 0
for i in indexes.slice(0,index_i)
if not(flip) and i in [1,2,6]
flip = not flip
else if flip and i in [0,4,5]
flip = not flip
if flip
coords.reverse()
# Returns the coordinates of the ith hexagon
{coords: coords[indexes[index_i]], flip: flip}
### Returns the rotation, translation and scale of a certain hexagon described as a base 7 number
###
get_hex_data = (indexes) ->
# For each iteration hexagons rotate by GOSPER_ANGLE
rotation = GOSPER_ANGLE * indexes.length
#
f_rotation = 0
# Hexagon coordinates start from the origin
translation = {x: 0, y: 0}
# For each iteration hexagons are scaled by 1/√7
scale = 1 / Math.pow Math.sqrt(7), indexes.length
# For each index, denoting an iteration of the Gosper Curve, the translation coordinates are updated
for index, i in indexes
# The scale of a hexagon at a certain iteration i
scale_i = 1 / Math.pow Math.sqrt(7), i
# Get the hept coordinates
result = get_hept_coords scale_i, indexes, i
hept_coords = result.coords
# Rotate the hept coordinates by the GOSPER ANGLE multiplied by the iteration
hept_coords = hept_tree_utils.rotate_point(hept_coords, GOSPER_ANGLE*(i+1))
hept_coords = hept_tree_utils.rotate_point(hept_coords, f_rotation)
#
f_rotation += if result.flip then F_ROTATION_INDEXES[index] else ROTATION_INDEXES[index]
# Coordinates update
translation.x += hept_coords.x
translation.y += hept_coords.y
{rotation: rotation, frotation: f_rotation, translation: translation, scale: scale}
compare_base7_numbers = (a,b) ->
return parseInt(a.join('')) < parseInt(b.join(''))
get_class = (index) ->
for n,i in base7_numbers
if compare_base7_numbers index, n
return "class#{i+1}"
return ""
# Recursive Procedure
rec = (order, index) ->
if order == 0
data.push({index: index, data: get_hex_data(index), class: get_class(index)})
return
for i in [0..6]
rec order-1, index.concat([i])
### Initialization
###
order = Math.floor Math.random()*6
#order = Math.ceil(Math.log(n1+n2)/Math.log(7))
max_hex = Math.pow 7, order
class_number = Math.floor Math.random()*7
numbers = []
base7_numbers = []
for i in [0...class_number]
numbers.push Math.floor Math.random()*(max_hex-d3.sum(numbers))
base7_numbers.push hept_tree_utils.base10_to_base7 d3.sum(numbers)
console.log numbers
console.log base7_numbers
rec order, []
# Drawing
vis.append 'g'
.attr
transform: "translate(#{-width/2+50}, #{-height/2+50})"
.append 'text'
.text "Order #{order}"
hexes = vis.selectAll 'path'
.data data
hexes.enter().append 'path'
.attr
class: 'hex'
d: (d) ->
hept_tree_utils.hex_generate_svg_path d.data.scale*INITIAL_SCALE, 0
transform: (d) -> "translate(#{d.data.translation.x*INITIAL_SCALE}, #{d.data.translation.y*INITIAL_SCALE}) rotate(#{d.data.rotation})"
fill: (d) -> color d.class
'stroke-width': () -> if order < 5 then 2 else 1
html, body {
margin: 0;
padding: 0;
}
.hex {
stroke: #fff;
/*fill: #e5e5e5;*/
fill-opacity: 0.6;
}
text {
font-family: sans-serif;
font-size: 20px;
fill: steelblue;
}
.curve {
stroke-width: 1.5px;
opacity: 0.6
}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Gosper Islands IV</title>
<meta name="description" content="Gosper Islands III">
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="hept_tree_utils.js"></script>
<link rel="stylesheet" href="index.css">
</head>
<body>
<svg></svg>
<script src="index.js"></script>
</body>
</html>
// Generated by CoffeeScript 1.10.0
(function() {
var F_ROTATION_INDEXES, GOSPER_ANGLE, INITIAL_SCALE, ROTATION_INDEXES, base7_numbers, class_number, color, compare_base7_numbers, data, get_class, get_hept_coords, get_hex_data, height, hexes, i, j, max_hex, numbers, order, rec, ref, svg, vis, width;
width = 960;
height = 500;
data = [];
INITIAL_SCALE = height / 2.4;
GOSPER_ANGLE = Math.acos(5 * Math.sqrt(7) / 14) / Math.PI * 180;
ROTATION_INDEXES = [0, 120, 0, -120, 0, 0, -120];
F_ROTATION_INDEXES = [-120, 0, 0, -120, 0, 120, 0];
color = d3.scale.ordinal().domain(["class1", "class2", "class3", "class4", "class5", "class6", "class7", ""]).range(["#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#8dd3c7", "#ffffb3", "#bebada", "#f2f2f2"]);
svg = d3.select('svg').attr({
width: width,
height: height,
viewBox: (-width / 2) + " " + (-height / 2) + " " + width + " " + height
});
vis = svg.append('g');
get_hept_coords = function(scale, indexes, index_i) {
var a, coords, flip, i, j, len, r, ref;
a = scale / Math.sqrt(7);
r = a / Math.sin(Math.PI / 3);
coords = [
{
x: -a,
y: r + r / 2
}, {
x: a,
y: r + r / 2
}, {
x: 0,
y: 0
}, {
x: -2 * a,
y: 0
}, {
x: -a,
y: -r - r / 2
}, {
x: a,
y: -r - r / 2
}, {
x: 2 * a,
y: 0
}
];
flip = false;
if (index_i > 0) {
ref = indexes.slice(0, index_i);
for (j = 0, len = ref.length; j < len; j++) {
i = ref[j];
if (!flip && (i === 1 || i === 2 || i === 6)) {
flip = !flip;
} else if (flip && (i === 0 || i === 4 || i === 5)) {
flip = !flip;
}
}
}
if (flip) {
coords.reverse();
}
return {
coords: coords[indexes[index_i]],
flip: flip
};
};
/* Returns the rotation, translation and scale of a certain hexagon described as a base 7 number
*/
get_hex_data = function(indexes) {
var f_rotation, hept_coords, i, index, j, len, result, rotation, scale, scale_i, translation;
rotation = GOSPER_ANGLE * indexes.length;
f_rotation = 0;
translation = {
x: 0,
y: 0
};
scale = 1 / Math.pow(Math.sqrt(7), indexes.length);
for (i = j = 0, len = indexes.length; j < len; i = ++j) {
index = indexes[i];
scale_i = 1 / Math.pow(Math.sqrt(7), i);
result = get_hept_coords(scale_i, indexes, i);
hept_coords = result.coords;
hept_coords = hept_tree_utils.rotate_point(hept_coords, GOSPER_ANGLE * (i + 1));
hept_coords = hept_tree_utils.rotate_point(hept_coords, f_rotation);
f_rotation += result.flip ? F_ROTATION_INDEXES[index] : ROTATION_INDEXES[index];
translation.x += hept_coords.x;
translation.y += hept_coords.y;
}
return {
rotation: rotation,
frotation: f_rotation,
translation: translation,
scale: scale
};
};
compare_base7_numbers = function(a, b) {
return parseInt(a.join('')) < parseInt(b.join(''));
};
get_class = function(index) {
var i, j, len, n;
for (i = j = 0, len = base7_numbers.length; j < len; i = ++j) {
n = base7_numbers[i];
if (compare_base7_numbers(index, n)) {
return "class" + (i + 1);
}
}
return "";
};
rec = function(order, index) {
var i, j, results;
if (order === 0) {
data.push({
index: index,
data: get_hex_data(index),
"class": get_class(index)
});
return;
}
results = [];
for (i = j = 0; j <= 6; i = ++j) {
results.push(rec(order - 1, index.concat([i])));
}
return results;
};
/* Initialization
*/
order = Math.floor(Math.random() * 6);
max_hex = Math.pow(7, order);
class_number = Math.floor(Math.random() * 7);
numbers = [];
base7_numbers = [];
for (i = j = 0, ref = class_number; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
numbers.push(Math.floor(Math.random() * (max_hex - d3.sum(numbers))));
base7_numbers.push(hept_tree_utils.base10_to_base7(d3.sum(numbers)));
}
console.log(numbers);
console.log(base7_numbers);
rec(order, []);
vis.append('g').attr({
transform: "translate(" + (-width / 2 + 50) + ", " + (-height / 2 + 50) + ")"
}).append('text').text("Order " + order);
hexes = vis.selectAll('path').data(data);
hexes.enter().append('path').attr({
"class": 'hex',
d: function(d) {
return hept_tree_utils.hex_generate_svg_path(d.data.scale * INITIAL_SCALE, 0);
},
transform: function(d) {
return "translate(" + (d.data.translation.x * INITIAL_SCALE) + ", " + (d.data.translation.y * INITIAL_SCALE) + ") rotate(" + d.data.rotation + ")";
},
fill: function(d) {
return color(d["class"]);
},
'stroke-width': function() {
if (order < 5) {
return 2;
} else {
return 1;
}
}
});
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment