Skip to content

Instantly share code, notes, and snippets.

@mbostock
Forked from mbostock/.block
Last active May 25, 2017 05:19
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save mbostock/3058685 to your computer and use it in GitHub Desktop.
Save mbostock/3058685 to your computer and use it in GitHub Desktop.
Geodesic Grid
license: gpl-3.0
(function() {
var φ = 1.618033988749895,
ρ = 180 / Math.PI;
var vertices = [
[1,φ,0], [-1,φ,0], [1,-φ,0], [-1,-φ,0],
[0,1,φ], [0,-1,φ], [0,1,-φ], [0,-1,-φ],
[φ,0,1], [-φ,0,1], [φ,0,-1], [-φ,0,-1]
];
var faces = [
[0,1,4], [1,9,4], [4,9,5], [5,9,3], [2,3,7],
[3,2,5], [7,10,2], [0,8,10], [0,4,8], [8,2,10],
[8,4,5], [8,5,2], [1,0,6], [11,1,6], [3,9,11],
[6,10,7], [3,11,7], [11,6,7], [6,0,10], [9,1,11]
].map(function(face) {
return face.map(function(i) {
return vertices[i];
});
});
d3.geodesic = {
multipolygon: function(n) {
return {
type: "MultiPolygon",
coordinates: subdivideFaces(~~n).map(function(face) {
face = face.map(project);
face.push(face[0]);
return [face];
})
};
},
polygons: function(n) {
return d3.geodesic.multipolygon(~~n).coordinates.map(function(face) {
return {type: "Polygon", coordinates: face};
});
},
multilinestring: function(n) {
return {
type: "MultiLineString",
coordinates: subdivideEdges(~~n).map(function(edge) {
return edge.map(project);
})
};
}
};
function subdivideFaces(n) {
return d3.merge(faces.map(function(face) {
var i01 = interpolate(face[0], face[1]),
i02 = interpolate(face[0], face[2]),
faces = [];
faces.push([
face[0],
i01(1 / n),
i02(1 / n)
]);
for (var i = 1; i < n; ++i) {
var i1 = interpolate(i01(i / n), i02(i / n)),
i2 = interpolate(i01((i + 1) / n), i02((i + 1) / n));
for (var j = 0; j <= i; ++j) {
faces.push([
i1(j / i),
i2(j / (i + 1)),
i2((j + 1) / (i + 1))
]);
}
for (var j = 0; j < i; ++j) {
faces.push([
i1(j / i),
i1((j + 1) / i),
i2((j + 1) / (i + 1))
]);
}
}
return faces;
}));
}
function subdivideEdges(n) {
var edges = {};
subdivideFaces(n).forEach(function(face) {
add(face[0], face[1]);
add(face[1], face[2]);
add(face[2], face[0]);
});
function add(p0, p1) {
var t;
if (p0[0] < p1[0] || (p0[0] == p1[0] && (p0[1] < p1[1] || (p0[1] == p1[1] && p0[2] < p1[2])))) t = p0, p0 = p1, p1 = t;
edges[p0.map(round) + " " + p1.map(round)] = [p0, p1];
}
function round(d) {
return d3.round(d, 4);
}
return d3.values(edges);
}
function interpolate(p0, p1) {
var x0 = p0[0],
y0 = p0[1],
z0 = p0[2],
x1 = p1[0] - x0,
y1 = p1[1] - y0,
z1 = p1[2] - z0;
return function(t) {
return [
x0 + t * x1,
y0 + t * y1,
z0 + t * z1
];
};
}
function project(p) {
var x = p[0],
y = p[1],
z = p[2];
return [
Math.atan2(y, x) * ρ,
Math.acos(z / Math.sqrt(x * x + y * y + z * z)) * ρ - 90
];
}
})();
<!DOCTYPE html>
<meta charset="utf-8">
<style>
path {
fill: none;
stroke: #000;
}
circle {
fill: none;
stroke: #000;
stroke-width: 3px;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="geodesic.js" charset="utf-8"></script>
<script>
var width = 960,
height = 500;
var velocity = [-.003, .003];
var projection = d3.geo.orthographic()
.scale(240);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var feature = svg.append("path")
.datum(d3.geodesic.multilinestring(8));
svg.append("circle")
.attr("r", 240)
.attr("cx", width / 2)
.attr("cy", height / 2);
d3.timer(function(elapsed) {
projection.rotate([elapsed * velocity[0], elapsed * velocity[1]]);
feature.attr("d", path);
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment