Skip to content

Commit b450656

Browse files
committed
commit bundle to master
1 parent 99ce119 commit b450656

File tree

2 files changed

+392
-1
lines changed

2 files changed

+392
-1
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
demo/bundle.js
21
yarn.lock

demo/bundle.js

Lines changed: 392 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,392 @@
1+
(function (global, factory) {
2+
typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
3+
typeof define === 'function' && define.amd ? define(factory) :
4+
(factory());
5+
}(this, (function () { 'use strict';
6+
7+
function createShader(gl, type, source) {
8+
var shader = gl.createShader(type);
9+
gl.shaderSource(shader, source);
10+
11+
gl.compileShader(shader);
12+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
13+
throw new Error(gl.getShaderInfoLog(shader));
14+
}
15+
16+
return shader;
17+
}
18+
19+
function createProgram(gl, vertexSource, fragmentSource) {
20+
var program = gl.createProgram();
21+
22+
var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);
23+
var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
24+
25+
gl.attachShader(program, vertexShader);
26+
gl.attachShader(program, fragmentShader);
27+
28+
gl.linkProgram(program);
29+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
30+
throw new Error(gl.getProgramInfoLog(program));
31+
}
32+
33+
var wrapper = {program: program};
34+
35+
var numAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
36+
for (var i = 0; i < numAttributes; i++) {
37+
var attribute = gl.getActiveAttrib(program, i);
38+
wrapper[attribute.name] = gl.getAttribLocation(program, attribute.name);
39+
}
40+
var numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
41+
for (i = 0; i < numUniforms; i++) {
42+
var uniform = gl.getActiveUniform(program, i);
43+
wrapper[uniform.name] = gl.getUniformLocation(program, uniform.name);
44+
}
45+
46+
return wrapper;
47+
}
48+
49+
function createTexture(gl, filter, data, width, height) {
50+
var texture = gl.createTexture();
51+
gl.bindTexture(gl.TEXTURE_2D, texture);
52+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
53+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
54+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
55+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
56+
if (data instanceof Uint8Array) {
57+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
58+
} else {
59+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, data);
60+
}
61+
gl.bindTexture(gl.TEXTURE_2D, null);
62+
return texture;
63+
}
64+
65+
function bindTexture(gl, texture, unit) {
66+
gl.activeTexture(gl.TEXTURE0 + unit);
67+
gl.bindTexture(gl.TEXTURE_2D, texture);
68+
}
69+
70+
function createBuffer(gl, data) {
71+
var buffer = gl.createBuffer();
72+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
73+
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
74+
return buffer;
75+
}
76+
77+
function bindAttribute(gl, buffer, attribute, numComponents) {
78+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
79+
gl.enableVertexAttribArray(attribute);
80+
gl.vertexAttribPointer(attribute, numComponents, gl.FLOAT, false, 0, 0);
81+
}
82+
83+
function bindFramebuffer(gl, framebuffer, texture) {
84+
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
85+
if (texture) {
86+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
87+
}
88+
}
89+
90+
var drawVert = "precision mediump float;\n\nattribute float a_index;\n\nuniform sampler2D u_particles;\nuniform float u_particles_res;\n\nvarying vec2 v_particle_pos;\n\nvoid main() {\n vec4 color = texture2D(u_particles, vec2(\n fract(a_index / u_particles_res),\n floor(a_index / u_particles_res) / u_particles_res));\n\n // decode current particle position from the pixel's RGBA value\n v_particle_pos = vec2(\n color.r / 255.0 + color.b,\n color.g / 255.0 + color.a);\n\n gl_PointSize = 1.0;\n gl_Position = vec4(2.0 * v_particle_pos.x - 1.0, 1.0 - 2.0 * v_particle_pos.y, 0, 1);\n}\n";
91+
92+
var drawFrag = "precision mediump float;\n\nuniform sampler2D u_wind;\nuniform vec2 u_wind_min;\nuniform vec2 u_wind_max;\nuniform sampler2D u_color_ramp;\n\nvarying vec2 v_particle_pos;\n\nvoid main() {\n vec2 velocity = mix(u_wind_min, u_wind_max, texture2D(u_wind, v_particle_pos).rg);\n float speed_t = length(velocity) / length(u_wind_max);\n\n // color ramp is encoded in a 16x16 texture\n vec2 ramp_pos = vec2(\n fract(16.0 * speed_t),\n floor(16.0 * speed_t) / 16.0);\n\n gl_FragColor = texture2D(u_color_ramp, ramp_pos);\n}\n";
93+
94+
var quadVert = "precision mediump float;\n\nattribute vec2 a_pos;\n\nvarying vec2 v_tex_pos;\n\nvoid main() {\n v_tex_pos = a_pos;\n gl_Position = vec4(1.0 - 2.0 * a_pos, 0, 1);\n}\n";
95+
96+
var screenFrag = "precision mediump float;\n\nuniform sampler2D u_screen;\nuniform float u_opacity;\n\nvarying vec2 v_tex_pos;\n\nvoid main() {\n vec4 color = texture2D(u_screen, 1.0 - v_tex_pos);\n // a hack to guarantee opacity fade out even with a value close to 1.0\n gl_FragColor = vec4(floor(255.0 * color * u_opacity) / 255.0);\n}\n";
97+
98+
var updateFrag = "precision highp float;\n\nuniform sampler2D u_particles;\nuniform sampler2D u_wind;\nuniform vec2 u_wind_res;\nuniform vec2 u_wind_min;\nuniform vec2 u_wind_max;\nuniform float u_rand_seed;\nuniform float u_speed_factor;\nuniform float u_drop_rate;\nuniform float u_drop_rate_bump;\n\nvarying vec2 v_tex_pos;\n\n// pseudo-random generator\nconst vec3 rand_constants = vec3(12.9898, 78.233, 4375.85453);\nfloat rand(const vec2 co) {\n float t = dot(rand_constants.xy, co);\n return fract(sin(t) * (rand_constants.z + t));\n}\n\n// wind speed lookup; use manual bilinear filtering based on 4 adjacent pixels for smooth interpolation\nvec2 lookup_wind(const vec2 uv) {\n // return texture2D(u_wind, uv).rg; // lower-res hardware filtering\n vec2 px = 1.0 / u_wind_res;\n vec2 vc = (floor(uv * u_wind_res)) * px;\n vec2 f = fract(uv * u_wind_res);\n vec2 tl = texture2D(u_wind, vc).rg;\n vec2 tr = texture2D(u_wind, vc + vec2(px.x, 0)).rg;\n vec2 bl = texture2D(u_wind, vc + vec2(0, px.y)).rg;\n vec2 br = texture2D(u_wind, vc + px).rg;\n return mix(mix(tl, tr, f.x), mix(bl, br, f.x), f.y);\n}\n\nvoid main() {\n vec4 color = texture2D(u_particles, v_tex_pos);\n vec2 pos = vec2(\n color.r / 255.0 + color.b,\n color.g / 255.0 + color.a); // decode particle position from pixel RGBA\n\n vec2 velocity = mix(u_wind_min, u_wind_max, lookup_wind(pos));\n float speed_t = length(velocity) / length(u_wind_max);\n\n // take EPSG:4236 distortion into account for calculating where the particle moved\n float distortion = max(0.1, cos(radians(pos.y * 180.0 - 90.0)));\n vec2 offset = vec2(velocity.x / distortion, -velocity.y) * 0.0001 * u_speed_factor;\n\n // a random seed to use for the particle drop\n vec2 seed = (pos + v_tex_pos) * u_rand_seed;\n\n // drop rate is a chance a particle will restart at random position, to avoid degeneration\n float dropRate = u_drop_rate + speed_t * u_drop_rate_bump;\n float drop = step(1.0 - dropRate, rand(seed));\n pos = mix(fract(1.0 + pos + offset), vec2(rand(seed + 1.3), rand(seed + 2.1)), drop);\n\n // encode the new particle position back into RGBA\n gl_FragColor = vec4(\n fract(pos * 255.0),\n floor(pos * 255.0) / 255.0);\n}\n";
99+
100+
var defaultRampColors = {
101+
0.0: '#3288bd',
102+
0.1: '#66c2a5',
103+
0.2: '#abdda4',
104+
0.3: '#e6f598',
105+
0.4: '#fee08b',
106+
0.5: '#fdae61',
107+
0.6: '#f46d43',
108+
1.0: '#d53e4f'
109+
};
110+
111+
class WindGL {
112+
constructor(gl) {
113+
this.gl = gl;
114+
115+
this.fadeOpacity = 0.996; // how fast the particle trails fade on each frame
116+
this.speedFactor = 0.25; // how fast the particles move
117+
this.dropRate = 0.003; // how often the particles move to a random place
118+
this.dropRateBump = 0.01; // drop rate increase relative to individual particle speed
119+
120+
this.drawProgram = createProgram(gl, drawVert, drawFrag);
121+
this.screenProgram = createProgram(gl, quadVert, screenFrag);
122+
this.updateProgram = createProgram(gl, quadVert, updateFrag);
123+
124+
this.quadBuffer = createBuffer(gl, new Float32Array([0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1]));
125+
this.framebuffer = gl.createFramebuffer();
126+
127+
this.setColorRamp(defaultRampColors);
128+
this.resize();
129+
}
130+
131+
resize() {
132+
var gl = this.gl;
133+
var emptyPixels = new Uint8Array(gl.canvas.width * gl.canvas.height * 4);
134+
// screen textures to hold the drawn screen for the previous and the current frame
135+
this.backgroundTexture = createTexture(gl, gl.NEAREST, emptyPixels, gl.canvas.width, gl.canvas.height);
136+
this.screenTexture = createTexture(gl, gl.NEAREST, emptyPixels, gl.canvas.width, gl.canvas.height);
137+
}
138+
139+
setColorRamp(colors) {
140+
// lookup texture for colorizing the particles according to their speed
141+
this.colorRampTexture = createTexture(this.gl, this.gl.LINEAR, getColorRamp(colors), 16, 16);
142+
}
143+
144+
set numParticles(numParticles) {
145+
var gl = this.gl;
146+
147+
// we create a square texture where each pixel will hold a particle position encoded as RGBA
148+
var particleRes = this.particleStateResolution = Math.ceil(Math.sqrt(numParticles));
149+
this._numParticles = particleRes * particleRes;
150+
151+
var particleState = new Uint8Array(this._numParticles * 4);
152+
for (var i = 0; i < particleState.length; i++) {
153+
particleState[i] = Math.floor(Math.random() * 256); // randomize the initial particle positions
154+
}
155+
// textures to hold the particle state for the current and the next frame
156+
this.particleStateTexture0 = createTexture(gl, gl.NEAREST, particleState, particleRes, particleRes);
157+
this.particleStateTexture1 = createTexture(gl, gl.NEAREST, particleState, particleRes, particleRes);
158+
159+
var particleIndices = new Float32Array(this._numParticles);
160+
for (i = 0; i < this._numParticles; i++) particleIndices[i] = i;
161+
this.particleIndexBuffer = createBuffer(gl, particleIndices);
162+
}
163+
get numParticles() {
164+
return this._numParticles;
165+
}
166+
167+
setWind(windData) {
168+
this.windData = windData;
169+
this.windTexture = createTexture(this.gl, this.gl.LINEAR, windData.image);
170+
}
171+
172+
draw() {
173+
var gl = this.gl;
174+
gl.disable(gl.DEPTH_TEST);
175+
gl.disable(gl.STENCIL_TEST);
176+
177+
bindTexture(gl, this.windTexture, 0);
178+
bindTexture(gl, this.particleStateTexture0, 1);
179+
180+
this.drawScreen();
181+
this.updateParticles();
182+
}
183+
184+
drawScreen() {
185+
var gl = this.gl;
186+
// draw the screen into a temporary framebuffer to retain it as the background on the next frame
187+
bindFramebuffer(gl, this.framebuffer, this.screenTexture);
188+
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
189+
190+
this.drawTexture(this.backgroundTexture, this.fadeOpacity);
191+
this.drawParticles();
192+
193+
bindFramebuffer(gl, null);
194+
// enable blending to support drawing on top of an existing background (e.g. a map)
195+
gl.enable(gl.BLEND);
196+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
197+
this.drawTexture(this.screenTexture, 1.0);
198+
gl.disable(gl.BLEND);
199+
200+
// save the current screen as the background for the next frame
201+
var temp = this.backgroundTexture;
202+
this.backgroundTexture = this.screenTexture;
203+
this.screenTexture = temp;
204+
}
205+
206+
drawTexture(texture, opacity) {
207+
var gl = this.gl;
208+
var program = this.screenProgram;
209+
gl.useProgram(program.program);
210+
211+
bindAttribute(gl, this.quadBuffer, program.a_pos, 2);
212+
bindTexture(gl, texture, 2);
213+
gl.uniform1i(program.u_screen, 2);
214+
gl.uniform1f(program.u_opacity, opacity);
215+
216+
gl.drawArrays(gl.TRIANGLES, 0, 6);
217+
}
218+
219+
drawParticles() {
220+
var gl = this.gl;
221+
var program = this.drawProgram;
222+
gl.useProgram(program.program);
223+
224+
bindAttribute(gl, this.particleIndexBuffer, program.a_index, 1);
225+
bindTexture(gl, this.colorRampTexture, 2);
226+
227+
gl.uniform1i(program.u_wind, 0);
228+
gl.uniform1i(program.u_particles, 1);
229+
gl.uniform1i(program.u_color_ramp, 2);
230+
231+
gl.uniform1f(program.u_particles_res, this.particleStateResolution);
232+
gl.uniform2f(program.u_wind_min, this.windData.uMin, this.windData.vMin);
233+
gl.uniform2f(program.u_wind_max, this.windData.uMax, this.windData.vMax);
234+
235+
gl.drawArrays(gl.POINTS, 0, this._numParticles);
236+
}
237+
238+
updateParticles() {
239+
var gl = this.gl;
240+
bindFramebuffer(gl, this.framebuffer, this.particleStateTexture1);
241+
gl.viewport(0, 0, this.particleStateResolution, this.particleStateResolution);
242+
243+
var program = this.updateProgram;
244+
gl.useProgram(program.program);
245+
246+
bindAttribute(gl, this.quadBuffer, program.a_pos, 2);
247+
248+
gl.uniform1i(program.u_wind, 0);
249+
gl.uniform1i(program.u_particles, 1);
250+
251+
gl.uniform1f(program.u_rand_seed, Math.random());
252+
gl.uniform2f(program.u_wind_res, this.windData.width, this.windData.height);
253+
gl.uniform2f(program.u_wind_min, this.windData.uMin, this.windData.vMin);
254+
gl.uniform2f(program.u_wind_max, this.windData.uMax, this.windData.vMax);
255+
gl.uniform1f(program.u_speed_factor, this.speedFactor);
256+
gl.uniform1f(program.u_drop_rate, this.dropRate);
257+
gl.uniform1f(program.u_drop_rate_bump, this.dropRateBump);
258+
259+
gl.drawArrays(gl.TRIANGLES, 0, 6);
260+
261+
// swap the particle state textures so the new one becomes the current one
262+
var temp = this.particleStateTexture0;
263+
this.particleStateTexture0 = this.particleStateTexture1;
264+
this.particleStateTexture1 = temp;
265+
}
266+
}
267+
268+
function getColorRamp(colors) {
269+
var canvas = document.createElement('canvas');
270+
var ctx = canvas.getContext('2d');
271+
272+
canvas.width = 256;
273+
canvas.height = 1;
274+
275+
var gradient = ctx.createLinearGradient(0, 0, 256, 0);
276+
for (var stop in colors) {
277+
gradient.addColorStop(+stop, colors[stop]);
278+
}
279+
280+
ctx.fillStyle = gradient;
281+
ctx.fillRect(0, 0, 256, 1);
282+
283+
return new Uint8Array(ctx.getImageData(0, 0, 256, 1).data);
284+
}
285+
286+
var canvas = document.getElementById('canvas');
287+
288+
var pxRatio = Math.max(Math.floor(window.devicePixelRatio) || 1, 2);
289+
canvas.width = canvas.clientWidth;
290+
canvas.height = canvas.clientHeight;
291+
292+
var gl = canvas.getContext('webgl', {antialiasing: false});
293+
294+
var wind = window.wind = new WindGL(gl);
295+
wind.numParticles = 65536;
296+
297+
function frame() {
298+
if (wind.windData) {
299+
wind.draw();
300+
}
301+
requestAnimationFrame(frame);
302+
}
303+
frame();
304+
305+
var gui = new dat.GUI();
306+
gui.add(wind, 'numParticles', 1024, 589824);
307+
gui.add(wind, 'fadeOpacity', 0.96, 0.999).step(0.001).updateDisplay();
308+
gui.add(wind, 'speedFactor', 0.05, 1.0);
309+
gui.add(wind, 'dropRate', 0, 0.1);
310+
gui.add(wind, 'dropRateBump', 0, 0.2);
311+
312+
var windFiles = {
313+
0: '2016112000',
314+
6: '2016112006',
315+
12: '2016112012',
316+
18: '2016112018',
317+
24: '2016112100',
318+
30: '2016112106',
319+
36: '2016112112',
320+
42: '2016112118',
321+
48: '2016112200'
322+
};
323+
324+
var meta = {
325+
'2016-11-20+h': 0,
326+
'retina resolution': false,
327+
'github.com/mapbox/webgl-wind': function () {
328+
window.location = 'https://github.com/mapbox/webgl-wind';
329+
}
330+
};
331+
gui.add(meta, '2016-11-20+h', 0, 48, 6).onFinishChange(updateWind);
332+
if (pxRatio !== 1) {
333+
gui.add(meta, 'retina resolution').onFinishChange(updateRetina);
334+
}
335+
gui.add(meta, 'github.com/mapbox/webgl-wind');
336+
updateWind(0);
337+
338+
function updateRetina() {
339+
var ratio = meta['retina resolution'] ? pxRatio : 1;
340+
canvas.width = canvas.clientWidth * ratio;
341+
canvas.height = canvas.clientHeight * ratio;
342+
wind.resize();
343+
}
344+
345+
getJSON('https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_110m_coastline.geojson', function (data) {
346+
var canvas = document.getElementById('coastline');
347+
canvas.width = canvas.clientWidth * pxRatio;
348+
canvas.height = canvas.clientHeight * pxRatio;
349+
350+
var ctx = canvas.getContext('2d');
351+
ctx.lineWidth = pxRatio;
352+
ctx.lineJoin = ctx.lineCap = 'round';
353+
ctx.strokeStyle = 'white';
354+
ctx.beginPath();
355+
356+
for (var i = 0; i < data.features.length; i++) {
357+
var line = data.features[i].geometry.coordinates;
358+
for (var j = 0; j < line.length; j++) {
359+
ctx[j ? 'lineTo' : 'moveTo'](
360+
(line[j][0] + 180) * canvas.width / 360,
361+
(-line[j][1] + 90) * canvas.height / 180);
362+
}
363+
}
364+
ctx.stroke();
365+
});
366+
367+
function updateWind(name) {
368+
getJSON('wind/' + windFiles[name] + '.json', function (windData) {
369+
var windImage = new Image();
370+
windData.image = windImage;
371+
windImage.src = 'wind/' + windFiles[name] + '.png';
372+
windImage.onload = function () {
373+
wind.setWind(windData);
374+
};
375+
});
376+
}
377+
378+
function getJSON(url, callback) {
379+
var xhr = new XMLHttpRequest();
380+
xhr.responseType = 'json';
381+
xhr.open('get', url, true);
382+
xhr.onload = function () {
383+
if (xhr.status >= 200 && xhr.status < 300) {
384+
callback(xhr.response);
385+
} else {
386+
throw new Error(xhr.statusText);
387+
}
388+
};
389+
xhr.send();
390+
}
391+
392+
})));

0 commit comments

Comments
 (0)