Skip to content

Instantly share code, notes, and snippets.

@mbostock
Last active March 9, 2019 17:16
Show Gist options
  • Save mbostock/2b116e0a95b4c981d50a to your computer and use it in GitHub Desktop.
Save mbostock/2b116e0a95b4c981d50a to your computer and use it in GitHub Desktop.
Isometric III
license: gpl-3.0
redirect: https://observablehq.com/@mbostock/isometric-iii
<!DOCTYPE html>
<meta charset="utf-8">
<canvas width="960" height="500"></canvas>
<script src="//d3js.org/d3-timer.v0.3.min.js"></script>
<script src="//d3js.org/d3-ease.v0.7.min.js"></script>
<script src="isometric.js"></script>
<script>
var canvas = document.querySelector("canvas"),
context = canvas.getContext("2d"),
width = canvas.width,
height = canvas.height;
var isocontext = isometric(context);
isocontext.scale3d(30, 30, 30);
d3_timer.timer(function(elapsed) {
context.save();
context.clearRect(0, 0, width, height);
context.fillStyle = "#fff";
context.strokeStyle = "#000";
context.translate(width / 2, height * 0.6);
for (var x = 14, d, t = (elapsed / 5000) % 1; x >= -14; --x) {
for (var y = 14; y >= -14; --y) {
if ((d = distanceManhattan(x, y)) > 15) continue;
var te = d3_ease.easeCubic(Math.max(0, Math.min(1, t * 4.8 - distanceCartesian(x, y) / 5)));
drawCube((d & 1 ? -1 : +1) * (Math.PI / 4 - te * Math.PI / 2), x * 1.42, y * 1.42 + te * (x & 1 ? 1 : -1) * 1.42, te * 1.42 * 2);
}
}
context.restore();
});
function distanceCartesian(x, y) {
return Math.sqrt(x * x + y * y);
}
function distanceManhattan(x, y) {
return Math.abs(x) + Math.abs(y);
}
function drawCube(angle, x, y, z) {
if ((angle %= Math.PI / 2) < 0) angle += Math.PI / 2;
isocontext.save();
isocontext.translate3d(x, y, z);
isocontext.rotateZ(angle - Math.PI / 4);
context.beginPath();
isocontext.moveTo(+0.5, -0.5, +0.5);
isocontext.lineTo(+0.5, +0.5, +0.5);
isocontext.lineTo(-0.5, +0.5, +0.5);
isocontext.lineTo(-0.5, +0.5, -0.5);
isocontext.lineTo(-0.5, -0.5, -0.5);
isocontext.lineTo(+0.5, -0.5, -0.5);
isocontext.closePath();
context.fill();
context.lineWidth = 1.5;
context.stroke();
context.beginPath();
isocontext.moveTo(-0.5, -0.5, +0.5);
isocontext.lineTo(+0.5, -0.5, +0.5);
isocontext.moveTo(-0.5, -0.5, +0.5);
isocontext.lineTo(-0.5, +0.5, +0.5);
isocontext.moveTo(-0.5, -0.5, +0.5);
isocontext.lineTo(-0.5, -0.5, -0.5);
context.lineWidth = 0.75;
context.stroke();
isocontext.restore();
}
</script>
(function(exports) {
var angle = Math.PI / 6;
var identity = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0
];
function Isometric(context) {
this._moveTo = context.moveTo.bind(context);
this._lineTo = context.lineTo.bind(context);
this.closePath = context.closePath.bind(context);
this._matrix = identity.slice();
this._matrixes = [];
this._projection = [
Math.cos(angle), Math.cos(Math.PI - angle),
-Math.sin(angle), -Math.sin(Math.PI - angle)
];
}
Isometric.prototype = {
_project: function(point, x, y, z) {
point(
x * this._projection[0] + y * this._projection[1],
x * this._projection[2] + y * this._projection[3] - z
);
},
_transform: function(point, x, y, z) {
this._project(point,
x * this._matrix[0] + y * this._matrix[1] + z * this._matrix[2] + this._matrix[3],
x * this._matrix[4] + y * this._matrix[5] + z * this._matrix[6] + this._matrix[7],
x * this._matrix[8] + y * this._matrix[9] + z * this._matrix[10] + this._matrix[11]
);
},
save: function() {
this._matrixes.push(this._matrix.slice());
},
restore: function() {
if (this._matrixes.length) this._matrix = this._matrixes.pop();
},
// | a b c d | | kx 0 0 0 | | a * kx b * ky c * kz d |
// | e f g h | * | 0 ky 0 0 | = | e * kx f * ky g * kz h |
// | i j k l | | 0 0 kz 0 | | i * kx j * ky k * kz l |
// | 0 0 0 1 | | 0 0 0 1 | | 0 0 0 1 |
scale3d: function(kx, ky, kz) {
this._matrix[0] *= kx;
this._matrix[1] *= ky;
this._matrix[2] *= kz;
this._matrix[4] *= kx;
this._matrix[5] *= ky;
this._matrix[6] *= kz;
this._matrix[8] *= kx;
this._matrix[9] *= ky;
this._matrix[10] *= kz;
},
// | a b c d | | cos -sin 0 0 | | a * cos + b * sin a * -sin + b * cos c d |
// | e f g h | * | sin cos 0 0 | = | e * cos + f * sin e * -sin + f * cos g h |
// | i j k l | | 0 0 1 0 | | i * cos + j * sin i * -sin + j * cos k l |
// | 0 0 0 1 | | 0 0 0 1 | | 0 0 0 1 |
rotateZ: function(angle) {
var cos = Math.cos(angle),
sin = Math.sin(angle),
a = this._matrix[0],
b = this._matrix[1],
c = this._matrix[2],
d = this._matrix[3],
e = this._matrix[4],
f = this._matrix[5],
g = this._matrix[6],
h = this._matrix[7],
i = this._matrix[8],
j = this._matrix[9],
k = this._matrix[10],
l = this._matrix[11];
this._matrix[0] = a * cos + b * sin;
this._matrix[1] = a * -sin + b * cos;
this._matrix[4] = e * cos + f * sin;
this._matrix[5] = e * -sin + f * cos;
this._matrix[8] = i * cos + j * sin;
this._matrix[9] = i * -sin + j * cos;
},
// | a b c d | | 1 0 0 tx | | a b c a * tx + b * ty + c * tz + d |
// | e f g h | * | 0 1 0 ty | = | e f g e * tx + f * ty + g * tz + h |
// | i j k l | | 0 0 1 tz | | i j k i * tx + j * ty + k * tz + l |
// | 0 0 0 1 | | 0 0 0 1 | | 0 0 0 1 |
translate3d: function(tx, ty, tz) {
this._matrix[3] += this._matrix[0] * tx + this._matrix[1] * ty + this._matrix[2] * tz;
this._matrix[7] += this._matrix[4] * tx + this._matrix[5] * ty + this._matrix[6] * tz;
this._matrix[11] += this._matrix[8] * tx + this._matrix[9] * ty + this._matrix[10] * tz;
},
moveTo: function(x, y, z) {
this._transform(this._moveTo, x, y, z);
},
lineTo: function(x, y, z) {
this._transform(this._lineTo, x, y, z);
}
};
exports.isometric = function(context) {
return new Isometric(context);
};
})(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment