Skip to content

Instantly share code, notes, and snippets.

@plmrry
Last active March 28, 2016 19:39
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 plmrry/0b2b3e5c494189067d23 to your computer and use it in GitHub Desktop.
Save plmrry/0b2b3e5c494189067d23 to your computer and use it in GitHub Desktop.
Mondrian

An experiment in translating from SVG into WebGL (via Three.js).

From a class project that involved a creative re-creation of the work of Piet Mondrian.

(Originally written in CoffeeScript, so the code is a little weird.)

<!doctype html>
<html>
<script id="vertexShader" type="x-shader/x-vertex">
precision mediump int;
uniform vec4 my_color;
varying vec3 vPosition;
varying vec4 vColor;
varying vec4 vProjected;
void main() {
vec3 pos = position;
vPosition = pos;
vColor = my_color;
vec4 projected = projectionMatrix * modelViewMatrix * vec4( pos, 1.0 );
vProjected = projected;
gl_Position = projected;
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
precision mediump int;
varying vec3 vPosition;
varying vec4 vColor;
varying vec4 vProjected;
void main() {
vec4 color;
// color = vec4( vColor );
// color = vec4( vPosition, 0.5 );
color = vProjected;
// gl_FragColor = color;
gl_FragColor = vec4( vColor );
}
</script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/r72/three.min.js'></script>
<script src='http://d3js.org/d3.v3.min.js'></script>
<script>
var size = {
width: 300,
height: 500,
depth: 100
};
var cellSize = 100;
var rows = Math.floor(size.height / cellSize);
var columns = Math.floor(size.width / cellSize);
var n = rows * columns;
var depthLevels = 5;
var colors = d3.range(4);
var color = d3.scale
.ordinal()
.domain(colors)
.range(['#a0a0a0', '#f0d050', '#e03040', '#4080c0']);
var data = [];
function addData() {
var newData;
newData = d3.range(n).map(function(d, i) {
return {
column: i % columns,
row: Math.floor(i / columns),
depth: d3.shuffle(d3.range(depthLevels))[0],
color: d3.shuffle(colors)[0]
};
});
data = data.concat(newData);
};
var body = d3.select('html')
.selectAll('body')
.data([1])
.enter()
.append('body');
translate = function(x, y) {
return "translate(" + x + "," + y + ")";
};
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(void 0, size.width / size.height);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(size.width, size.height);
renderer.setClearColor('#ffffff', 1);
renderer.sortObjects = true;
body.append(function() {
return renderer.domElement;
});
var fragment = window.fragment = document.createDocumentFragment();
var svg = body.append('svg').attr(size);
var frame = (function() {
var g, m, mesh;
g = new THREE.PlaneGeometry(size.width, size.height);
m = new THREE.MeshBasicMaterial({
color: 0x0
});
m.transparent = true;
m.opacity = 0.05;
mesh = new THREE.Mesh(g, m);
scene.add(mesh);
return mesh;
})();
var bordersObj = new THREE.Object3D();
bordersObj.position.z = 100;
scene.add(bordersObj);
var frameScale = (function() {
var a, arr;
a = [[size.width, 1], [size.height, -1]];
arr = a.map(function(a) {
return d3.scale.linear()
.domain([0, a[0]])
.range([-a[0] / 2 * a[1], a[0] / 2 * a[1]]);
});
return {
x: arr[0],
y: arr[1]
};
})();
var random = Math.random;
var normal = d3.random.normal(0, cellSize * .2);
var vs = document.getElementById('vertexShader').textContent;
var fs = document.getElementById('fragmentShader').textContent;
var grid = (function() {
var arr;
arr = [[columns, size.width], [rows, size.height]].map(function(_) {
return d3.scale.ordinal()
.domain(d3.range(_[0]))
.rangeBands([0, _[1]], 0.5, 0.5);
});
return {
x: arr[0],
y: arr[1]
};
})();
grid.z = d3.scale.ordinal()
.domain(d3.range(depthLevels))
.rangePoints([10, size.depth]);
function addRect(cells) {
return cells.append('rect').classed('mainRect', true).attr({
'width': grid.x.rangeBand(),
'height': grid.y.rangeBand()
}).style({
'fill-opacity': 0.9,
'fill': '#888888'
}).each(function(d, i) {
var g, h, material, mesh, r, ref, w;
r = d3.select(this);
ref = ['width', 'height'].map(function(_) {
return r.attr(_);
}), w = ref[0], h = ref[1];
g = new THREE.PlaneGeometry(w, h);
material = new THREE.ShaderMaterial({
polygonOffset: true,
polygonOffsetFactor: i * 10,
uniforms: {
time: {
type: "f",
value: 1.0
},
my_color: {
type: "v4",
value: new THREE.Vector4(1.0, 0.4, 1.0, 1.0)
}
},
vertexShader: vs,
fragmentShader: fs
});
mesh = new THREE.Mesh(g, material);
mesh.position.x += w / 2;
mesh.position.y -= h / 2;
mesh.name = 'rect';
return d.object3D.add(mesh);
});
};
function updateCells() {
var cells, i, transition;
cells = svg.selectAll('.cell').data(data);
transition = cells.enter()
.append('g')
.classed('cell', true)
.each(function(d) {
d.object3D = new THREE.Object3D();
return frame.add(d.object3D);
})
.call(addRect).attr({
transform: translate(size.width / 2, size.height / 2),
'data-depth': grid.z(0)
})
.transition()
.duration(500)
.transition()
.duration(300)
.attr('transform', function(d) {
return translate(grid.x(d.column), size.height / 2);
})
.transition()
.attr('transform', function(d) {
return translate(grid.x(d.column), grid.y(d.row));
})
.transition()
.call(function(t) {
var rect;
rect = t.selectAll('rect');
return rect.style('fill', function(d) {
return color(d.color);
});
})
.transition()
.attr('data-depth', function(d) {
return grid.z(d.depth) + normal() * 2;
})
.transition()
.call(function(t) {
t.select('rect')
.each(function(d) {
d._width = parseInt(d3.select(this).attr('width'));
d._height = parseInt(d3.select(this).attr('height'));
})
.attr({
width: function(d) {
return Math.max(0, d._width + normal());
},
height: function(d) {
return Math.max(0, d._height + normal());
}
});
});
i = 0;
return new Promise(function(resolve) {
return transition
.each("end", function(d) {
var m;
m = this.transform.baseVal[0].matrix;
d3.select(this)
.transition()
.duration(300)
.attr('transform', function(d) {
return translate(m.e + normal(), m.f + normal());
})
.each("end", function() {
if (++i === transition.size()) {
return resolve();
}
});
});
});
};
var maxLightness = 1;
var normalizePosition = {
x: d3.scale.linear()
.domain([0, size.width / 2, size.width])
.range([maxLightness, 0, maxLightness]),
y: d3.scale.linear()
.domain([0, size.height / 2, size.height])
.range([maxLightness, 0, maxLightness])
};
var borderRectWidth = 4;
function addBorders(d) {
var g = d3.select(this);
var rect = g.select('rect');
var material = new THREE.ShaderMaterial({
polygonOffset: true,
polygonOffsetFactor: -500,
uniforms: {
time: {
type: "f",
value: 1.0
},
my_color: {
type: "v4",
value: new THREE.Vector4(0, 0, 0, 1.0)
}
},
vertexShader: vs,
fragmentShader: fs
});
var rectObj = d.object3D.getObjectByName('rect');
var ref = ["width", "height"]
.map(function(dim) {
return rect.attr(dim);
}), w = ref[0], h = ref[1];
var bord = borderRectWidth;
var arr = [
[w, bord, 0, +h / 2, 'top'],
[w, bord, 0, -h / 2, 'bottom'],
[bord, h, -w / 2, 0, 'left'],
[bord, h, +w / 2, 0, 'right']
];
return arr.forEach(function(args) {
var geometry, height, mesh, width, x, y;
if (Math.random() < 0.9) {
return;
}
width = args[0], height = args[1], x = args[2], y = args[3];
geometry = new THREE.PlaneGeometry(width, height);
mesh = new THREE.Mesh(geometry, material);
mesh.name = 'border';
mesh.position.x = x === 0 ? normal() : x;
mesh.position.y = y === 0 ? normal() : y;
mesh.position.z = 5;
return rectObj.add(mesh);
});
};
function repeat() {
addData();
return updateCells()
.then(function() {
svg.selectAll('.cell').transition().each(function(d) {
var m, ref, rgb, x, y;
m = this.transform.baseVal.getItem(0).matrix;
ref = [m.e, m.f], x = ref[0], y = ref[1];
rgb = d3.rgb(color(d.color));
return d3.select(this).select('rect')
.style('fill', rgb.toString());
});
return svg.selectAll('.cell').each(addBorders);
});
};
var repetitions = 10;
d3.range(repetitions).reduce(function(current, next) {
return current.then(repeat);
}, Promise.resolve());
camera.position.z = 700;
camera.position.x = -700;
camera.lookAt(new THREE.Vector3(0, 0, 0));
function render() {
requestAnimationFrame(render);
svg.selectAll('.cell').each(function(d, i) {
var cell, colorArray, colorVec, h, m, rect, rectObj, ref, w;
cell = d3.select(this);
rect = cell.select('.mainRect');
rectObj = d.object3D.getObjectByName('rect');
colorArray = new THREE.Color(rect.node().style.fill).toArray();
colorVec = new THREE.Vector4().fromArray(colorArray.concat(1.0));
rectObj.material.uniforms.my_color.value = colorVec;
rectObj.geometry.dispose();
ref = ['width', 'height'].map(function(dim) {
return rect.attr(dim);
}), w = ref[0], h = ref[1];
rectObj.geometry = new THREE.PlaneGeometry(w, h);
m = this.transform.baseVal.getItem(0).matrix;
d.object3D.position.x = frameScale.x(m.e);
d.object3D.position.y = frameScale.y(m.f);
return d.object3D.position.z = cell.attr('data-depth');
});
return renderer.render(scene, camera);
};
render();
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment