flowsLayer = ({
id: 'flows',
type: 'custom',
onAdd: function (map, gl) {
this.program = gl.createProgram();
gl.attachShader(this.program, compileShader(gl, gl.VERTEX_SHADER, `
#define WAVE_FACTOR 2000.0
#define WAVE_SPEED 0.005
precision highp float;
uniform mat4 u_matrix;
uniform int u_time;
attribute vec2 a_origin_loc;
attribute vec2 a_dest_loc;
attribute vec4 a_color;
attribute float a_thickness;
attribute float a_instance_index;
attribute vec2 a_square_vert;
varying vec2 v_square_pos;
varying vec4 v_color;
varying float v_source_to_target;
void main() {
v_square_pos = a_square_vert.xy; // position within [-1, 1] square
v_color = a_color;
float height = a_thickness * a_square_vert.y * mix(0.0, 1.0, v_square_pos.x);
gl_Position = u_matrix * vec4(mix(a_origin_loc.xy, a_dest_loc.xy, v_square_pos.x), 0.0, 1.0) +
vec4(a_square_vert.x, height, 0.0, 1.0);
// Adding a_instance_index to prevent the "pulsing" effect:
// when all waves depart from their origins at the same time
float t = (float(u_time) + a_instance_index) * WAVE_SPEED;
v_source_to_target = v_square_pos.x * length(a_origin_loc - a_dest_loc) * WAVE_FACTOR - t;
}
`));
gl.attachShader(this.program, compileShader(gl, gl.FRAGMENT_SHADER, `
precision highp float;
varying vec2 v_square_pos;
varying vec4 v_color;
varying float v_source_to_target;
void main() {
gl_FragColor = vec4(v_color.xyz, v_color.w * smoothstep(0.3, 1.0, fract(v_source_to_target)));
float soften = smoothstep(0.0, 1.0, v_square_pos.y);
gl_FragColor = vec4(gl_FragColor.rgb, gl_FragColor.a * soften);
}
`));
gl.linkProgram(this.program);
// Square vertices
this.aSquareVert = gl.getAttribLocation(this.program, "a_square_vert");
this.squareVerts = new Float32Array([
0, -1,
0, 1,
1, -1,
1, 1,
]);
this.squareVertsBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.squareVertsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, this.squareVerts, gl.STATIC_DRAW);
// Instanced attrs
this.aOrigin = gl.getAttribLocation(this.program, "a_origin_loc");
this.aDest = gl.getAttribLocation(this.program, "a_dest_loc");
this.aColor = gl.getAttribLocation(this.program, "a_color");
this.aThickness = gl.getAttribLocation(this.program, "a_thickness");
this.aInstanceIndex = gl.getAttribLocation(this.program, "a_instance_index");
const originCoords = new Float32Array(flows.length * 2);
const destCoords = new Float32Array(flows.length * 2);
const colors = new Float32Array(flows.length * 4);
const thicknesses = new Float32Array(flows.length);
const instanceIndices = new Float32Array(flows.length);
for (let i = 0; i < flows.length; i++) {
const { origin, dest, count } = flows[i];
originCoords[i * 2] = origin.x;
originCoords[i * 2 + 1] = origin.y;
destCoords[i * 2] = dest.x;
destCoords[i * 2 + 1] = dest.y;
const {r,g,b} = d3.rgb(flowColorScale(count));
colors[i * 4] = r/255;
colors[i * 4 + 1] = g/255;
colors[i * 4 + 2] = b/255;
colors[i * 4 + 3] = flowOpacityScale(count);
thicknesses[i] = flowThicknessScale(count);
instanceIndices[i] = i;
}
this.originCoordsBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.originCoordsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, originCoords, gl.STATIC_DRAW);
this.destCoordsBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.destCoordsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, destCoords, gl.STATIC_DRAW);
this.colorsBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.colorsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
this.thicknessBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.thicknessBuffer);
gl.bufferData(gl.ARRAY_BUFFER, thicknesses, gl.STATIC_DRAW);
this.instanceIndexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceIndexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, instanceIndices, gl.STATIC_DRAW);
},
render: function (gl, matrix) {
const ext = gl.getExtension('ANGLE_instanced_arrays');
gl.useProgram(this.program);
gl.uniformMatrix4fv(gl.getUniformLocation(this.program, "u_matrix"), false, matrix);
gl.uniform1i(gl.getUniformLocation(this.program, "u_time"), currentTime());
// Square vertices
gl.bindBuffer(gl.ARRAY_BUFFER, this.squareVertsBuffer);
gl.enableVertexAttribArray(this.squareVertsBuffer);
gl.vertexAttribPointer(this.aSquareVert, 2, gl.FLOAT, false, 0, 0);
ext.vertexAttribDivisorANGLE(this.aSquareVert, 0); // non-instanced
// Flows
gl.bindBuffer(gl.ARRAY_BUFFER, this.originCoordsBuffer);
gl.enableVertexAttribArray(this.aOrigin);
gl.vertexAttribPointer(this.aOrigin, 2, gl.FLOAT, false, 0, 0);
ext.vertexAttribDivisorANGLE(this.aOrigin, 1); // instanced
gl.bindBuffer(gl.ARRAY_BUFFER, this.destCoordsBuffer);
gl.enableVertexAttribArray(this.aDest);
gl.vertexAttribPointer(this.aDest, 2, gl.FLOAT, false, 0, 0);
ext.vertexAttribDivisorANGLE(this.aDest, 1); // instanced
gl.bindBuffer(gl.ARRAY_BUFFER, this.colorsBuffer);
gl.enableVertexAttribArray(this.aColor);
gl.vertexAttribPointer(this.aColor, 4, gl.FLOAT, false, 0, 0);
ext.vertexAttribDivisorANGLE(this.aColor, 1); // instanced
gl.bindBuffer(gl.ARRAY_BUFFER, this.thicknessBuffer);
gl.enableVertexAttribArray(this.aThickness);
gl.vertexAttribPointer(this.aThickness, 1, gl.FLOAT, false, 0, 0);
ext.vertexAttribDivisorANGLE(this.aThickness, 1); // instanced
gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceIndexBuffer);
gl.enableVertexAttribArray(this.aInstanceIndex);
gl.vertexAttribPointer(this.aInstanceIndex, 1, gl.FLOAT, false, 0, 0);
ext.vertexAttribDivisorANGLE(this.aInstanceIndex, 1); // instanced
gl.enable(gl.BLEND);
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
ext.drawArraysInstancedANGLE(gl.TRIANGLE_STRIP, 0, this.squareVerts.length / 2, flows.length);
}
})