From 53721d91a4c037731ad4cbd9b9378c6c231c1c71 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Fri, 10 Feb 2017 17:11:43 +0200 Subject: [PATCH 01/10] switch to mercator projection for display --- demo/index.js | 9 ++++++++- dist/wind-gl.js | 2 +- src/shaders/draw.vert.glsl | 7 ++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/demo/index.js b/demo/index.js index 21e39c59..ab0b1cc9 100644 --- a/demo/index.js +++ b/demo/index.js @@ -75,7 +75,7 @@ getJSON('https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_110m_coastl for (let j = 0; j < line.length; j++) { ctx[j ? 'lineTo' : 'moveTo']( (line[j][0] + 180) * canvas.width / 360, - (-line[j][1] + 90) * canvas.height / 180); + latY(line[j][1]) * canvas.height); } } ctx.stroke(); @@ -105,3 +105,10 @@ function getJSON(url, callback) { }; xhr.send(); } + +function latY(lat) { + const sin = Math.sin(lat * Math.PI / 180), + y = (0.5 - 0.25 * Math.log((1 + sin) / (1 - sin)) / Math.PI); + return y < 0 ? 0 : + y > 1 ? 1 : y; +} diff --git a/dist/wind-gl.js b/dist/wind-gl.js index c1f4b79e..f149fc89 100644 --- a/dist/wind-gl.js +++ b/dist/wind-gl.js @@ -87,7 +87,7 @@ function bindFramebuffer(gl, framebuffer, texture) { } } -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"; +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 // project the position with mercator projection\n float s = sin(radians(90.0 - v_particle_pos.y * 180.0));\n float y = degrees(log((1.0 + s) / (1.0 - s))) / 360.0;\n float x = 2.0 * v_particle_pos.x - 1.0;\n\n gl_PointSize = 1.0;\n gl_Position = vec4(x, y, 0, 1);\n}\n"; 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"; diff --git a/src/shaders/draw.vert.glsl b/src/shaders/draw.vert.glsl index 14884c45..717c27be 100644 --- a/src/shaders/draw.vert.glsl +++ b/src/shaders/draw.vert.glsl @@ -17,6 +17,11 @@ void main() { color.r / 255.0 + color.b, color.g / 255.0 + color.a); + // project the position with mercator projection + float s = sin(radians(90.0 - v_particle_pos.y * 180.0)); + float y = degrees(log((1.0 + s) / (1.0 - s))) / 360.0; + float x = 2.0 * v_particle_pos.x - 1.0; + gl_PointSize = 1.0; - gl_Position = vec4(2.0 * v_particle_pos.x - 1.0, 1.0 - 2.0 * v_particle_pos.y, 0, 1); + gl_Position = vec4(x, y, 0, 1); } From dc318e76cb113c04d4434efe50e6d60df8ac2a85 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Tue, 28 Feb 2017 16:48:32 -0800 Subject: [PATCH 02/10] add ability to zoom in --- demo/index.js | 40 ++++++++++++++++++++++++++++++++------ dist/wind-gl.js | 5 ++++- src/index.js | 3 +++ src/shaders/draw.vert.glsl | 14 ++++++++++--- 4 files changed, 52 insertions(+), 10 deletions(-) diff --git a/demo/index.js b/demo/index.js index ab0b1cc9..122116d3 100644 --- a/demo/index.js +++ b/demo/index.js @@ -20,7 +20,7 @@ frame(); const gui = new dat.GUI(); gui.add(wind, 'numParticles', 1024, 589824); -gui.add(wind, 'fadeOpacity', 0.96, 0.999).step(0.001).updateDisplay(); +gui.add(wind, 'fadeOpacity', 0.96, 0.999, 0.001).updateDisplay(); gui.add(wind, 'speedFactor', 0.05, 1.0); gui.add(wind, 'dropRate', 0, 0.1); gui.add(wind, 'dropRateBump', 0, 0.2); @@ -38,20 +38,38 @@ const windFiles = { }; const meta = { + 'zoom': 0, '2016-11-20+h': 0, 'retina resolution': true, 'github.com/mapbox/webgl-wind': function () { window.location = 'https://github.com/mapbox/webgl-wind'; } }; + +gui.add(meta, 'zoom', 0, 2, 0.01).onChange(updateZoom); gui.add(meta, '2016-11-20+h', 0, 48, 6).onFinishChange(updateWind); + if (pxRatio !== 1) { gui.add(meta, 'retina resolution').onFinishChange(updateRetina); } + gui.add(meta, 'github.com/mapbox/webgl-wind'); + updateWind(0); updateRetina(); +function updateZoom() { + const halfSize = 0.5 / Math.pow(2, meta.zoom); + wind.bbox = [ + 0.5 - halfSize, + 0.5 - halfSize, + 0.5 + halfSize, + 0.5 + halfSize + ]; + drawCoastline(); + wind.resize(); +} + function updateRetina() { const ratio = meta['retina resolution'] ? pxRatio : 1; canvas.width = canvas.clientWidth * ratio; @@ -59,7 +77,14 @@ function updateRetina() { wind.resize(); } +let coastlineFeatures; + getJSON('https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_110m_coastline.geojson', function (data) { + coastlineFeatures = data.features; + drawCoastline(); +}); + +function drawCoastline() { const canvas = document.getElementById('coastline'); canvas.width = canvas.clientWidth * pxRatio; canvas.height = canvas.clientHeight * pxRatio; @@ -70,16 +95,19 @@ getJSON('https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_110m_coastl ctx.strokeStyle = 'white'; ctx.beginPath(); - for (let i = 0; i < data.features.length; i++) { - const line = data.features[i].geometry.coordinates; + for (let i = 0; i < coastlineFeatures.length; i++) { + const line = coastlineFeatures[i].geometry.coordinates; for (let j = 0; j < line.length; j++) { + const x = (line[j][0] + 180) / 360; + const y = latY(line[j][1]); ctx[j ? 'lineTo' : 'moveTo']( - (line[j][0] + 180) * canvas.width / 360, - latY(line[j][1]) * canvas.height); + (x - wind.bbox[0]) / (wind.bbox[2] - wind.bbox[0]) * canvas.width, + (y - (1 - wind.bbox[3])) / (wind.bbox[3] - wind.bbox[1]) * canvas.height); } } ctx.stroke(); -}); + +} function updateWind(name) { getJSON('wind/' + windFiles[name] + '.json', function (windData) { diff --git a/dist/wind-gl.js b/dist/wind-gl.js index f149fc89..5f48be5d 100644 --- a/dist/wind-gl.js +++ b/dist/wind-gl.js @@ -87,7 +87,7 @@ function bindFramebuffer(gl, framebuffer, texture) { } } -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 // project the position with mercator projection\n float s = sin(radians(90.0 - v_particle_pos.y * 180.0));\n float y = degrees(log((1.0 + s) / (1.0 - s))) / 360.0;\n float x = 2.0 * v_particle_pos.x - 1.0;\n\n gl_PointSize = 1.0;\n gl_Position = vec4(x, y, 0, 1);\n}\n"; +var drawVert = "precision mediump float;\n\nattribute float a_index;\n\nuniform sampler2D u_particles;\nuniform float u_particles_res;\n\nuniform vec4 u_bbox;\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 // project the position with mercator projection\n float s = sin(radians(90.0 - v_particle_pos.y * 180.0));\n float y = (degrees(log((1.0 + s) / (1.0 - s))) / 360.0 + 1.0) / 2.0;\n float x = v_particle_pos.x;\n\n vec2 min = u_bbox.xy;\n vec2 max = u_bbox.zw;\n\n gl_PointSize = 1.0;\n gl_Position = vec4(\n 2.0 * (x - min.x) / (max.x - min.x) - 1.0,\n 2.0 * (y - min.y) / (max.y - min.y) - 1.0,\n 0, 1);\n}\n"; 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"; @@ -115,6 +115,7 @@ var WindGL = function WindGL(gl) { this.speedFactor = 0.25; // how fast the particles move this.dropRate = 0.003; // how often the particles move to a random place this.dropRateBump = 0.01; // drop rate increase relative to individual particle speed + this.bbox = [0, 0, 1, 1]; // mercator bbox of the wind view this.drawProgram = createProgram(gl, drawVert, drawFrag); this.screenProgram = createProgram(gl, quadVert, screenFrag); @@ -233,6 +234,8 @@ WindGL.prototype.drawParticles = function drawParticles () { gl.uniform2f(program.u_wind_min, this.windData.uMin, this.windData.vMin); gl.uniform2f(program.u_wind_max, this.windData.uMax, this.windData.vMax); + gl.uniform4fv(program.u_bbox, this.bbox); + gl.drawArrays(gl.POINTS, 0, this._numParticles); }; diff --git a/src/index.js b/src/index.js index fefbe1b9..0334a23f 100644 --- a/src/index.js +++ b/src/index.js @@ -28,6 +28,7 @@ export default class WindGL { this.speedFactor = 0.25; // how fast the particles move this.dropRate = 0.003; // how often the particles move to a random place this.dropRateBump = 0.01; // drop rate increase relative to individual particle speed + this.bbox = [0, 0, 1, 1]; // mercator bbox of the wind view this.drawProgram = util.createProgram(gl, drawVert, drawFrag); this.screenProgram = util.createProgram(gl, quadVert, screenFrag); @@ -144,6 +145,8 @@ export default class WindGL { gl.uniform2f(program.u_wind_min, this.windData.uMin, this.windData.vMin); gl.uniform2f(program.u_wind_max, this.windData.uMax, this.windData.vMax); + gl.uniform4fv(program.u_bbox, this.bbox); + gl.drawArrays(gl.POINTS, 0, this._numParticles); } diff --git a/src/shaders/draw.vert.glsl b/src/shaders/draw.vert.glsl index 717c27be..448f034e 100644 --- a/src/shaders/draw.vert.glsl +++ b/src/shaders/draw.vert.glsl @@ -5,6 +5,8 @@ attribute float a_index; uniform sampler2D u_particles; uniform float u_particles_res; +uniform vec4 u_bbox; + varying vec2 v_particle_pos; void main() { @@ -19,9 +21,15 @@ void main() { // project the position with mercator projection float s = sin(radians(90.0 - v_particle_pos.y * 180.0)); - float y = degrees(log((1.0 + s) / (1.0 - s))) / 360.0; - float x = 2.0 * v_particle_pos.x - 1.0; + float y = (degrees(log((1.0 + s) / (1.0 - s))) / 360.0 + 1.0) / 2.0; + float x = v_particle_pos.x; + + vec2 min = u_bbox.xy; + vec2 max = u_bbox.zw; gl_PointSize = 1.0; - gl_Position = vec4(x, y, 0, 1); + gl_Position = vec4( + 2.0 * (x - min.x) / (max.x - min.x) - 1.0, + 2.0 * (y - min.y) / (max.y - min.y) - 1.0, + 0, 1); } From 95b9c384dbda70d40ce45934fa9f7949c13b70de Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Tue, 28 Feb 2017 17:53:29 -0800 Subject: [PATCH 03/10] retain the correct number of particles when zooming --- demo/index.js | 11 ++++++----- dist/wind-gl.js | 22 +++++++++++++++++----- src/index.js | 18 +++++++++++++++--- src/shaders/draw.vert.glsl | 6 +++--- src/shaders/update.frag.glsl | 10 ++++++++-- 5 files changed, 49 insertions(+), 18 deletions(-) diff --git a/demo/index.js b/demo/index.js index 122116d3..563a80c7 100644 --- a/demo/index.js +++ b/demo/index.js @@ -60,14 +60,15 @@ updateRetina(); function updateZoom() { const halfSize = 0.5 / Math.pow(2, meta.zoom); - wind.bbox = [ + wind.setBBox([ 0.5 - halfSize, 0.5 - halfSize, 0.5 + halfSize, 0.5 + halfSize - ]; + ]); drawCoastline(); wind.resize(); + wind.numParticles = wind.numParticles; } function updateRetina() { @@ -100,13 +101,13 @@ function drawCoastline() { for (let j = 0; j < line.length; j++) { const x = (line[j][0] + 180) / 360; const y = latY(line[j][1]); + const bbox = wind.mercBBox; ctx[j ? 'lineTo' : 'moveTo']( - (x - wind.bbox[0]) / (wind.bbox[2] - wind.bbox[0]) * canvas.width, - (y - (1 - wind.bbox[3])) / (wind.bbox[3] - wind.bbox[1]) * canvas.height); + (x - bbox[0]) / (bbox[2] - bbox[0]) * canvas.width, + (y - (1 - bbox[3])) / (bbox[3] - bbox[1]) * canvas.height); } } ctx.stroke(); - } function updateWind(name) { diff --git a/dist/wind-gl.js b/dist/wind-gl.js index 5f48be5d..3ed6a290 100644 --- a/dist/wind-gl.js +++ b/dist/wind-gl.js @@ -87,7 +87,7 @@ function bindFramebuffer(gl, framebuffer, texture) { } } -var drawVert = "precision mediump float;\n\nattribute float a_index;\n\nuniform sampler2D u_particles;\nuniform float u_particles_res;\n\nuniform vec4 u_bbox;\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 // project the position with mercator projection\n float s = sin(radians(90.0 - v_particle_pos.y * 180.0));\n float y = (degrees(log((1.0 + s) / (1.0 - s))) / 360.0 + 1.0) / 2.0;\n float x = v_particle_pos.x;\n\n vec2 min = u_bbox.xy;\n vec2 max = u_bbox.zw;\n\n gl_PointSize = 1.0;\n gl_Position = vec4(\n 2.0 * (x - min.x) / (max.x - min.x) - 1.0,\n 2.0 * (y - min.y) / (max.y - min.y) - 1.0,\n 0, 1);\n}\n"; +var drawVert = "precision mediump float;\n\nattribute float a_index;\n\nuniform sampler2D u_particles;\nuniform float u_particles_res;\n\nuniform vec4 u_mercator_bbox;\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 // project the position with mercator projection\n float s = sin(radians(90.0 - v_particle_pos.y * 180.0));\n float y = (degrees(log((1.0 + s) / (1.0 - s))) / 360.0 + 1.0) / 2.0;\n float x = v_particle_pos.x;\n\n vec2 min = u_mercator_bbox.xy;\n vec2 max = u_mercator_bbox.zw;\n\n gl_PointSize = 1.0;\n gl_Position = vec4(\n 2.0 * (x - min.x) / (max.x - min.x) - 1.0,\n 2.0 * (y - min.y) / (max.y - min.y) - 1.0,\n 0, 1);\n}\n"; 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"; @@ -95,7 +95,7 @@ var quadVert = "precision mediump float;\n\nattribute vec2 a_pos;\n\nvarying vec 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"; -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 = cos(radians(pos.y * 180.0 - 90.0));\n vec2 offset = vec2(velocity.x / distortion, -velocity.y) * 0.0001 * u_speed_factor;\n\n // update particle position, wrapping around the date line\n pos = fract(1.0 + pos + offset);\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 drop_rate = u_drop_rate + speed_t * u_drop_rate_bump;\n float drop = step(1.0 - drop_rate, rand(seed));\n\n vec2 random_pos = vec2(\n rand(seed + 1.3),\n rand(seed + 2.1));\n pos = mix(pos, random_pos, 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"; +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;\nuniform vec4 u_bbox;\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 = cos(radians(pos.y * 180.0 - 90.0));\n vec2 offset = vec2(velocity.x / distortion, -velocity.y) * 0.0001 * u_speed_factor;\n\n // update particle position, wrapping around the date line\n pos = fract(1.0 + pos + offset);\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 drop_rate = u_drop_rate + speed_t * u_drop_rate_bump;\n\n float retain = step(drop_rate, rand(seed)) *\n step(u_bbox.x, pos.x) *\n step(pos.x, u_bbox.z) *\n step(u_bbox.y, pos.y) *\n step(pos.y, u_bbox.w); // also drop the particle if it went off the current bbox\n\n vec2 random_pos = vec2(\n rand(seed + 1.3),\n rand(seed + 2.1));\n pos = mix(pos, random_pos, 1.0 - retain);\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"; var defaultRampColors = { 0.0: '#3288bd', @@ -115,7 +115,6 @@ var WindGL = function WindGL(gl) { this.speedFactor = 0.25; // how fast the particles move this.dropRate = 0.003; // how often the particles move to a random place this.dropRateBump = 0.01; // drop rate increase relative to individual particle speed - this.bbox = [0, 0, 1, 1]; // mercator bbox of the wind view this.drawProgram = createProgram(gl, drawVert, drawFrag); this.screenProgram = createProgram(gl, quadVert, screenFrag); @@ -125,6 +124,7 @@ var WindGL = function WindGL(gl) { this.framebuffer = gl.createFramebuffer(); this.setColorRamp(defaultRampColors); + this.setBBox([0, 0, 1, 1]); this.resize(); }; @@ -171,6 +171,11 @@ WindGL.prototype.setWind = function setWind (windData) { this.windTexture = createTexture(this.gl, this.gl.LINEAR, windData.image); }; +WindGL.prototype.setBBox = function setBBox (bbox) { + this.bbox = bbox; + this.mercBBox = [bbox[0], mercY(bbox[1]), bbox[2], mercY(bbox[3])]; +}; + WindGL.prototype.draw = function draw () { var gl = this.gl; gl.disable(gl.DEPTH_TEST); @@ -233,8 +238,7 @@ WindGL.prototype.drawParticles = function drawParticles () { gl.uniform1f(program.u_particles_res, this.particleStateResolution); gl.uniform2f(program.u_wind_min, this.windData.uMin, this.windData.vMin); gl.uniform2f(program.u_wind_max, this.windData.uMax, this.windData.vMax); - - gl.uniform4fv(program.u_bbox, this.bbox); + gl.uniform4fv(program.u_mercator_bbox, this.mercBBox); gl.drawArrays(gl.POINTS, 0, this._numParticles); }; @@ -259,6 +263,7 @@ WindGL.prototype.updateParticles = function updateParticles () { gl.uniform1f(program.u_speed_factor, this.speedFactor); gl.uniform1f(program.u_drop_rate, this.dropRate); gl.uniform1f(program.u_drop_rate_bump, this.dropRateBump); + gl.uniform4fv(program.u_bbox, this.bbox); gl.drawArrays(gl.TRIANGLES, 0, 6); @@ -288,6 +293,13 @@ function getColorRamp(colors) { return new Uint8Array(ctx.getImageData(0, 0, 256, 1).data); } +function mercY(y) { + var s = Math.sin(Math.PI / 2 - y * Math.PI); + var y2 = 1.0 - (Math.log((1.0 + s) / (1.0 - s)) / (2 * Math.PI) + 1.0) / 2.0; + return y2 < 0 ? 0 : + y2 > 1 ? 1 : y2; +} + return WindGL; }))); diff --git a/src/index.js b/src/index.js index 0334a23f..425f13e6 100644 --- a/src/index.js +++ b/src/index.js @@ -28,7 +28,6 @@ export default class WindGL { this.speedFactor = 0.25; // how fast the particles move this.dropRate = 0.003; // how often the particles move to a random place this.dropRateBump = 0.01; // drop rate increase relative to individual particle speed - this.bbox = [0, 0, 1, 1]; // mercator bbox of the wind view this.drawProgram = util.createProgram(gl, drawVert, drawFrag); this.screenProgram = util.createProgram(gl, quadVert, screenFrag); @@ -38,6 +37,7 @@ export default class WindGL { this.framebuffer = gl.createFramebuffer(); this.setColorRamp(defaultRampColors); + this.setBBox([0, 0, 1, 1]); this.resize(); } @@ -82,6 +82,11 @@ export default class WindGL { this.windTexture = util.createTexture(this.gl, this.gl.LINEAR, windData.image); } + setBBox(bbox) { + this.bbox = bbox; + this.mercBBox = [bbox[0], mercY(bbox[1]), bbox[2], mercY(bbox[3])]; + } + draw() { const gl = this.gl; gl.disable(gl.DEPTH_TEST); @@ -144,8 +149,7 @@ export default class WindGL { gl.uniform1f(program.u_particles_res, this.particleStateResolution); gl.uniform2f(program.u_wind_min, this.windData.uMin, this.windData.vMin); gl.uniform2f(program.u_wind_max, this.windData.uMax, this.windData.vMax); - - gl.uniform4fv(program.u_bbox, this.bbox); + gl.uniform4fv(program.u_mercator_bbox, this.mercBBox); gl.drawArrays(gl.POINTS, 0, this._numParticles); } @@ -170,6 +174,7 @@ export default class WindGL { gl.uniform1f(program.u_speed_factor, this.speedFactor); gl.uniform1f(program.u_drop_rate, this.dropRate); gl.uniform1f(program.u_drop_rate_bump, this.dropRateBump); + gl.uniform4fv(program.u_bbox, this.bbox); gl.drawArrays(gl.TRIANGLES, 0, 6); @@ -197,3 +202,10 @@ function getColorRamp(colors) { return new Uint8Array(ctx.getImageData(0, 0, 256, 1).data); } + +function mercY(y) { + const s = Math.sin(Math.PI / 2 - y * Math.PI); + const y2 = 1.0 - (Math.log((1.0 + s) / (1.0 - s)) / (2 * Math.PI) + 1.0) / 2.0; + return y2 < 0 ? 0 : + y2 > 1 ? 1 : y2; +} diff --git a/src/shaders/draw.vert.glsl b/src/shaders/draw.vert.glsl index 448f034e..9913ddc9 100644 --- a/src/shaders/draw.vert.glsl +++ b/src/shaders/draw.vert.glsl @@ -5,7 +5,7 @@ attribute float a_index; uniform sampler2D u_particles; uniform float u_particles_res; -uniform vec4 u_bbox; +uniform vec4 u_mercator_bbox; varying vec2 v_particle_pos; @@ -24,8 +24,8 @@ void main() { float y = (degrees(log((1.0 + s) / (1.0 - s))) / 360.0 + 1.0) / 2.0; float x = v_particle_pos.x; - vec2 min = u_bbox.xy; - vec2 max = u_bbox.zw; + vec2 min = u_mercator_bbox.xy; + vec2 max = u_mercator_bbox.zw; gl_PointSize = 1.0; gl_Position = vec4( diff --git a/src/shaders/update.frag.glsl b/src/shaders/update.frag.glsl index 82b92169..af3dc4b1 100644 --- a/src/shaders/update.frag.glsl +++ b/src/shaders/update.frag.glsl @@ -9,6 +9,7 @@ uniform float u_rand_seed; uniform float u_speed_factor; uniform float u_drop_rate; uniform float u_drop_rate_bump; +uniform vec4 u_bbox; varying vec2 v_tex_pos; @@ -53,12 +54,17 @@ void main() { // drop rate is a chance a particle will restart at random position, to avoid degeneration float drop_rate = u_drop_rate + speed_t * u_drop_rate_bump; - float drop = step(1.0 - drop_rate, rand(seed)); + + float retain = step(drop_rate, rand(seed)) * + step(u_bbox.x, pos.x) * + step(pos.x, u_bbox.z) * + step(u_bbox.y, pos.y) * + step(pos.y, u_bbox.w); // also drop the particle if it went off the current bbox vec2 random_pos = vec2( rand(seed + 1.3), rand(seed + 2.1)); - pos = mix(pos, random_pos, drop); + pos = mix(pos, random_pos, 1.0 - retain); // encode the new particle position back into RGBA gl_FragColor = vec4( From 4ccac6f9fe330810598c97e41a8c174a40794d64 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Tue, 28 Feb 2017 18:04:22 -0800 Subject: [PATCH 04/10] reset particle position within bbox --- demo/index.js | 3 ++- dist/wind-gl.js | 2 +- src/shaders/update.frag.glsl | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/demo/index.js b/demo/index.js index 563a80c7..02d99785 100644 --- a/demo/index.js +++ b/demo/index.js @@ -46,7 +46,7 @@ const meta = { } }; -gui.add(meta, 'zoom', 0, 2, 0.01).onChange(updateZoom); +gui.add(meta, 'zoom', 0, 8, 0.01).onChange(updateZoom); gui.add(meta, '2016-11-20+h', 0, 48, 6).onFinishChange(updateWind); if (pxRatio !== 1) { @@ -117,6 +117,7 @@ function updateWind(name) { windImage.src = 'wind/' + windFiles[name] + '.png'; windImage.onload = function () { wind.setWind(windData); + wind.resize(); }; }); } diff --git a/dist/wind-gl.js b/dist/wind-gl.js index 3ed6a290..157461d8 100644 --- a/dist/wind-gl.js +++ b/dist/wind-gl.js @@ -95,7 +95,7 @@ var quadVert = "precision mediump float;\n\nattribute vec2 a_pos;\n\nvarying vec 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"; -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;\nuniform vec4 u_bbox;\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 = cos(radians(pos.y * 180.0 - 90.0));\n vec2 offset = vec2(velocity.x / distortion, -velocity.y) * 0.0001 * u_speed_factor;\n\n // update particle position, wrapping around the date line\n pos = fract(1.0 + pos + offset);\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 drop_rate = u_drop_rate + speed_t * u_drop_rate_bump;\n\n float retain = step(drop_rate, rand(seed)) *\n step(u_bbox.x, pos.x) *\n step(pos.x, u_bbox.z) *\n step(u_bbox.y, pos.y) *\n step(pos.y, u_bbox.w); // also drop the particle if it went off the current bbox\n\n vec2 random_pos = vec2(\n rand(seed + 1.3),\n rand(seed + 2.1));\n pos = mix(pos, random_pos, 1.0 - retain);\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"; +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;\nuniform vec4 u_bbox;\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 = cos(radians(pos.y * 180.0 - 90.0));\n vec2 offset = vec2(velocity.x / distortion, -velocity.y) * 0.0001 * u_speed_factor;\n\n // update particle position, wrapping around the date line\n pos = fract(1.0 + pos + offset);\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 drop_rate = u_drop_rate + speed_t * u_drop_rate_bump;\n\n float retain = step(drop_rate, rand(seed)) *\n step(u_bbox.x, pos.x) *\n step(pos.x, u_bbox.z) *\n step(u_bbox.y, pos.y) *\n step(pos.y, u_bbox.w); // also drop the particle if it went off the current bbox\n\n vec2 random_pos = vec2(\n u_bbox.x + rand(seed + 1.3) * (u_bbox.z - u_bbox.x),\n u_bbox.y + rand(seed + 2.1) * (u_bbox.w - u_bbox.y));\n pos = mix(pos, random_pos, 1.0 - retain);\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"; var defaultRampColors = { 0.0: '#3288bd', diff --git a/src/shaders/update.frag.glsl b/src/shaders/update.frag.glsl index af3dc4b1..661f22a1 100644 --- a/src/shaders/update.frag.glsl +++ b/src/shaders/update.frag.glsl @@ -62,8 +62,8 @@ void main() { step(pos.y, u_bbox.w); // also drop the particle if it went off the current bbox vec2 random_pos = vec2( - rand(seed + 1.3), - rand(seed + 2.1)); + u_bbox.x + rand(seed + 1.3) * (u_bbox.z - u_bbox.x), + u_bbox.y + rand(seed + 2.1) * (u_bbox.w - u_bbox.y)); pos = mix(pos, random_pos, 1.0 - retain); // encode the new particle position back into RGBA From bbedf948fae05eed7cdb83ca8fbbb4f4569e2246 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Wed, 1 Mar 2017 06:33:36 -0800 Subject: [PATCH 05/10] various zoom to bbox fixes --- dist/wind-gl.js | 2 +- src/shaders/update.frag.glsl | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dist/wind-gl.js b/dist/wind-gl.js index 157461d8..052222aa 100644 --- a/dist/wind-gl.js +++ b/dist/wind-gl.js @@ -95,7 +95,7 @@ var quadVert = "precision mediump float;\n\nattribute vec2 a_pos;\n\nvarying vec 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"; -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;\nuniform vec4 u_bbox;\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 = cos(radians(pos.y * 180.0 - 90.0));\n vec2 offset = vec2(velocity.x / distortion, -velocity.y) * 0.0001 * u_speed_factor;\n\n // update particle position, wrapping around the date line\n pos = fract(1.0 + pos + offset);\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 drop_rate = u_drop_rate + speed_t * u_drop_rate_bump;\n\n float retain = step(drop_rate, rand(seed)) *\n step(u_bbox.x, pos.x) *\n step(pos.x, u_bbox.z) *\n step(u_bbox.y, pos.y) *\n step(pos.y, u_bbox.w); // also drop the particle if it went off the current bbox\n\n vec2 random_pos = vec2(\n u_bbox.x + rand(seed + 1.3) * (u_bbox.z - u_bbox.x),\n u_bbox.y + rand(seed + 2.1) * (u_bbox.w - u_bbox.y));\n pos = mix(pos, random_pos, 1.0 - retain);\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"; +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;\nuniform vec4 u_bbox;\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 = cos(radians(pos.y * 180.0 - 90.0));\n vec2 offset = vec2(velocity.x / distortion, -velocity.y) * 0.0001 * u_speed_factor * (u_bbox.z - u_bbox.x);\n\n // update particle position, wrapping around the date line\n pos = fract(1.0 + pos + offset);\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 drop_rate = u_drop_rate + speed_t * u_drop_rate_bump;\n\n float retain = step(drop_rate, rand(seed)) *\n step(u_bbox.x, pos.x) *\n step(pos.x, u_bbox.z) *\n step(u_bbox.y, 1.0 - pos.y) *\n step(1.0 - pos.y, u_bbox.w); // also drop the particle if it went off the current bbox\n\n vec2 random_pos = vec2(\n u_bbox.x + rand(seed + 1.3) * (u_bbox.z - u_bbox.x),\n 1.0 - (u_bbox.y + rand(seed + 2.1) * (u_bbox.w - u_bbox.y)));\n pos = mix(pos, random_pos, 1.0 - retain);\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"; var defaultRampColors = { 0.0: '#3288bd', diff --git a/src/shaders/update.frag.glsl b/src/shaders/update.frag.glsl index 661f22a1..3dfce79d 100644 --- a/src/shaders/update.frag.glsl +++ b/src/shaders/update.frag.glsl @@ -44,7 +44,7 @@ void main() { // take EPSG:4236 distortion into account for calculating where the particle moved float distortion = cos(radians(pos.y * 180.0 - 90.0)); - vec2 offset = vec2(velocity.x / distortion, -velocity.y) * 0.0001 * u_speed_factor; + vec2 offset = vec2(velocity.x / distortion, -velocity.y) * 0.0001 * u_speed_factor * (u_bbox.z - u_bbox.x); // update particle position, wrapping around the date line pos = fract(1.0 + pos + offset); @@ -58,12 +58,12 @@ void main() { float retain = step(drop_rate, rand(seed)) * step(u_bbox.x, pos.x) * step(pos.x, u_bbox.z) * - step(u_bbox.y, pos.y) * - step(pos.y, u_bbox.w); // also drop the particle if it went off the current bbox + step(u_bbox.y, 1.0 - pos.y) * + step(1.0 - pos.y, u_bbox.w); // also drop the particle if it went off the current bbox vec2 random_pos = vec2( u_bbox.x + rand(seed + 1.3) * (u_bbox.z - u_bbox.x), - u_bbox.y + rand(seed + 2.1) * (u_bbox.w - u_bbox.y)); + 1.0 - (u_bbox.y + rand(seed + 2.1) * (u_bbox.w - u_bbox.y))); pos = mix(pos, random_pos, 1.0 - retain); // encode the new particle position back into RGBA From ed2b5a865405c2d78898dd8c07e053bf13238b87 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Tue, 17 Jul 2018 14:52:43 +0300 Subject: [PATCH 06/10] fix artifacts when zooming in --- dist/wind-gl.js | 5 +++-- src/index.js | 1 + src/shaders/draw.vert.glsl | 6 +++++- src/shaders/update.frag.glsl | 21 +++++++++------------ 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/dist/wind-gl.js b/dist/wind-gl.js index 052222aa..6685b872 100644 --- a/dist/wind-gl.js +++ b/dist/wind-gl.js @@ -87,7 +87,7 @@ function bindFramebuffer(gl, framebuffer, texture) { } } -var drawVert = "precision mediump float;\n\nattribute float a_index;\n\nuniform sampler2D u_particles;\nuniform float u_particles_res;\n\nuniform vec4 u_mercator_bbox;\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 // project the position with mercator projection\n float s = sin(radians(90.0 - v_particle_pos.y * 180.0));\n float y = (degrees(log((1.0 + s) / (1.0 - s))) / 360.0 + 1.0) / 2.0;\n float x = v_particle_pos.x;\n\n vec2 min = u_mercator_bbox.xy;\n vec2 max = u_mercator_bbox.zw;\n\n gl_PointSize = 1.0;\n gl_Position = vec4(\n 2.0 * (x - min.x) / (max.x - min.x) - 1.0,\n 2.0 * (y - min.y) / (max.y - min.y) - 1.0,\n 0, 1);\n}\n"; +var drawVert = "precision mediump float;\n\nattribute float a_index;\n\nuniform sampler2D u_particles;\nuniform float u_particles_res;\n\nuniform vec4 u_mercator_bbox;\nuniform vec4 u_bbox;\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 vec2 pos = vec2(\n color.r / 255.0 + color.b,\n color.g / 255.0 + color.a);\n\n // convert to global geographic position\n v_particle_pos = u_bbox.xy + pos * (u_bbox.zw - u_bbox.xy);\n\n // project the position with mercator projection\n float s = sin(radians(90.0 - v_particle_pos.y * 180.0));\n float y = (degrees(log((1.0 + s) / (1.0 - s))) / 360.0 + 1.0) / 2.0;\n float x = v_particle_pos.x;\n\n vec2 min = u_mercator_bbox.xy;\n vec2 max = u_mercator_bbox.zw;\n\n gl_PointSize = 1.0;\n gl_Position = vec4(\n 2.0 * (x - min.x) / (max.x - min.x) - 1.0,\n 2.0 * (y - min.y) / (max.y - min.y) - 1.0,\n 0, 1);\n}\n"; 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"; @@ -95,7 +95,7 @@ var quadVert = "precision mediump float;\n\nattribute vec2 a_pos;\n\nvarying vec 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"; -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;\nuniform vec4 u_bbox;\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 = cos(radians(pos.y * 180.0 - 90.0));\n vec2 offset = vec2(velocity.x / distortion, -velocity.y) * 0.0001 * u_speed_factor * (u_bbox.z - u_bbox.x);\n\n // update particle position, wrapping around the date line\n pos = fract(1.0 + pos + offset);\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 drop_rate = u_drop_rate + speed_t * u_drop_rate_bump;\n\n float retain = step(drop_rate, rand(seed)) *\n step(u_bbox.x, pos.x) *\n step(pos.x, u_bbox.z) *\n step(u_bbox.y, 1.0 - pos.y) *\n step(1.0 - pos.y, u_bbox.w); // also drop the particle if it went off the current bbox\n\n vec2 random_pos = vec2(\n u_bbox.x + rand(seed + 1.3) * (u_bbox.z - u_bbox.x),\n 1.0 - (u_bbox.y + rand(seed + 2.1) * (u_bbox.w - u_bbox.y)));\n pos = mix(pos, random_pos, 1.0 - retain);\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"; +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;\nuniform vec4 u_bbox;\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 // convert to global geographic position\n vec2 global_pos = u_bbox.xy + pos * (u_bbox.zw - u_bbox.xy);\n\n vec2 velocity = mix(u_wind_min, u_wind_max, lookup_wind(global_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 = cos(radians(global_pos.y * 180.0 - 90.0));\n vec2 offset = vec2(velocity.x / distortion, -velocity.y) * 0.0001 * u_speed_factor;\n\n // update particle position, wrapping around the boundaries\n pos = fract(1.0 + pos + offset);\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 drop_rate = u_drop_rate + speed_t * u_drop_rate_bump;\n\n float retain = step(drop_rate, rand(seed));\n\n vec2 random_pos = vec2(rand(seed + 1.3), 1.0 - rand(seed + 2.1));\n pos = mix(pos, random_pos, 1.0 - retain);\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"; var defaultRampColors = { 0.0: '#3288bd', @@ -239,6 +239,7 @@ WindGL.prototype.drawParticles = function drawParticles () { gl.uniform2f(program.u_wind_min, this.windData.uMin, this.windData.vMin); gl.uniform2f(program.u_wind_max, this.windData.uMax, this.windData.vMax); gl.uniform4fv(program.u_mercator_bbox, this.mercBBox); + gl.uniform4fv(program.u_bbox, this.bbox); gl.drawArrays(gl.POINTS, 0, this._numParticles); }; diff --git a/src/index.js b/src/index.js index 425f13e6..9775f842 100644 --- a/src/index.js +++ b/src/index.js @@ -150,6 +150,7 @@ export default class WindGL { gl.uniform2f(program.u_wind_min, this.windData.uMin, this.windData.vMin); gl.uniform2f(program.u_wind_max, this.windData.uMax, this.windData.vMax); gl.uniform4fv(program.u_mercator_bbox, this.mercBBox); + gl.uniform4fv(program.u_bbox, this.bbox); gl.drawArrays(gl.POINTS, 0, this._numParticles); } diff --git a/src/shaders/draw.vert.glsl b/src/shaders/draw.vert.glsl index 9913ddc9..5c46c3e8 100644 --- a/src/shaders/draw.vert.glsl +++ b/src/shaders/draw.vert.glsl @@ -6,6 +6,7 @@ uniform sampler2D u_particles; uniform float u_particles_res; uniform vec4 u_mercator_bbox; +uniform vec4 u_bbox; varying vec2 v_particle_pos; @@ -15,10 +16,13 @@ void main() { floor(a_index / u_particles_res) / u_particles_res)); // decode current particle position from the pixel's RGBA value - v_particle_pos = vec2( + vec2 pos = vec2( color.r / 255.0 + color.b, color.g / 255.0 + color.a); + // convert to global geographic position + v_particle_pos = u_bbox.xy + pos * (u_bbox.zw - u_bbox.xy); + // project the position with mercator projection float s = sin(radians(90.0 - v_particle_pos.y * 180.0)); float y = (degrees(log((1.0 + s) / (1.0 - s))) / 360.0 + 1.0) / 2.0; diff --git a/src/shaders/update.frag.glsl b/src/shaders/update.frag.glsl index 3dfce79d..79d45313 100644 --- a/src/shaders/update.frag.glsl +++ b/src/shaders/update.frag.glsl @@ -39,14 +39,17 @@ void main() { color.r / 255.0 + color.b, color.g / 255.0 + color.a); // decode particle position from pixel RGBA - vec2 velocity = mix(u_wind_min, u_wind_max, lookup_wind(pos)); + // convert to global geographic position + vec2 global_pos = u_bbox.xy + pos * (u_bbox.zw - u_bbox.xy); + + vec2 velocity = mix(u_wind_min, u_wind_max, lookup_wind(global_pos)); float speed_t = length(velocity) / length(u_wind_max); // take EPSG:4236 distortion into account for calculating where the particle moved - float distortion = cos(radians(pos.y * 180.0 - 90.0)); - vec2 offset = vec2(velocity.x / distortion, -velocity.y) * 0.0001 * u_speed_factor * (u_bbox.z - u_bbox.x); + float distortion = cos(radians(global_pos.y * 180.0 - 90.0)); + vec2 offset = vec2(velocity.x / distortion, -velocity.y) * 0.0001 * u_speed_factor; - // update particle position, wrapping around the date line + // update particle position, wrapping around the boundaries pos = fract(1.0 + pos + offset); // a random seed to use for the particle drop @@ -55,15 +58,9 @@ void main() { // drop rate is a chance a particle will restart at random position, to avoid degeneration float drop_rate = u_drop_rate + speed_t * u_drop_rate_bump; - float retain = step(drop_rate, rand(seed)) * - step(u_bbox.x, pos.x) * - step(pos.x, u_bbox.z) * - step(u_bbox.y, 1.0 - pos.y) * - step(1.0 - pos.y, u_bbox.w); // also drop the particle if it went off the current bbox + float retain = step(drop_rate, rand(seed)); - vec2 random_pos = vec2( - u_bbox.x + rand(seed + 1.3) * (u_bbox.z - u_bbox.x), - 1.0 - (u_bbox.y + rand(seed + 2.1) * (u_bbox.w - u_bbox.y))); + vec2 random_pos = vec2(rand(seed + 1.3), 1.0 - rand(seed + 2.1)); pos = mix(pos, random_pos, 1.0 - retain); // encode the new particle position back into RGBA From ec6b6dc41d7585e33be6a8240ed0d0f4a6f0658b Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Wed, 18 Jul 2018 15:37:53 +0300 Subject: [PATCH 07/10] switch to matrix-based view calculation --- demo/index.js | 9 ++++++--- dist/wind-gl.js | 17 +++++++++++++---- src/index.js | 15 ++++++++++++--- src/shaders/draw.vert.glsl | 11 ++--------- 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/demo/index.js b/demo/index.js index 02d99785..310c964b 100644 --- a/demo/index.js +++ b/demo/index.js @@ -101,10 +101,13 @@ function drawCoastline() { for (let j = 0; j < line.length; j++) { const x = (line[j][0] + 180) / 360; const y = latY(line[j][1]); - const bbox = wind.mercBBox; + const minX = wind.bbox[0]; + const minY = latY(90 - 180 * wind.bbox[1]); + const maxX = wind.bbox[2]; + const maxY = latY(90 - 180 * wind.bbox[3]); ctx[j ? 'lineTo' : 'moveTo']( - (x - bbox[0]) / (bbox[2] - bbox[0]) * canvas.width, - (y - (1 - bbox[3])) / (bbox[3] - bbox[1]) * canvas.height); + (x - minX) / (maxX - minX) * canvas.width, + (y - (1 - maxY)) / (maxY - minY) * canvas.height); } } ctx.stroke(); diff --git a/dist/wind-gl.js b/dist/wind-gl.js index 6685b872..8a38473c 100644 --- a/dist/wind-gl.js +++ b/dist/wind-gl.js @@ -87,7 +87,7 @@ function bindFramebuffer(gl, framebuffer, texture) { } } -var drawVert = "precision mediump float;\n\nattribute float a_index;\n\nuniform sampler2D u_particles;\nuniform float u_particles_res;\n\nuniform vec4 u_mercator_bbox;\nuniform vec4 u_bbox;\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 vec2 pos = vec2(\n color.r / 255.0 + color.b,\n color.g / 255.0 + color.a);\n\n // convert to global geographic position\n v_particle_pos = u_bbox.xy + pos * (u_bbox.zw - u_bbox.xy);\n\n // project the position with mercator projection\n float s = sin(radians(90.0 - v_particle_pos.y * 180.0));\n float y = (degrees(log((1.0 + s) / (1.0 - s))) / 360.0 + 1.0) / 2.0;\n float x = v_particle_pos.x;\n\n vec2 min = u_mercator_bbox.xy;\n vec2 max = u_mercator_bbox.zw;\n\n gl_PointSize = 1.0;\n gl_Position = vec4(\n 2.0 * (x - min.x) / (max.x - min.x) - 1.0,\n 2.0 * (y - min.y) / (max.y - min.y) - 1.0,\n 0, 1);\n}\n"; +var drawVert = "precision mediump float;\n\nattribute float a_index;\n\nuniform sampler2D u_particles;\nuniform float u_particles_res;\n\nuniform mat4 u_matrix;\nuniform vec4 u_bbox;\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 vec2 pos = vec2(\n color.r / 255.0 + color.b,\n color.g / 255.0 + color.a);\n\n // convert to global geographic position\n v_particle_pos = u_bbox.xy + pos * (u_bbox.zw - u_bbox.xy);\n\n // project the position with mercator projection\n float s = sin(radians(90.0 - v_particle_pos.y * 180.0));\n float y = (degrees(log((1.0 + s) / (1.0 - s))) / 360.0 + 1.0) / 2.0;\n\n gl_PointSize = 1.0;\n gl_Position = u_matrix * vec4(v_particle_pos.x, y, 0, 1);\n}\n"; 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"; @@ -173,7 +173,16 @@ WindGL.prototype.setWind = function setWind (windData) { WindGL.prototype.setBBox = function setBBox (bbox) { this.bbox = bbox; - this.mercBBox = [bbox[0], mercY(bbox[1]), bbox[2], mercY(bbox[3])]; + + var minX = bbox[0]; + var minY = mercY(bbox[1]); + var maxX = bbox[2]; + var maxY = mercY(bbox[3]); + + var kx = 2 / (maxX - minX); + var ky = 2 / (maxY - minY); + + this.matrix = new Float32Array([kx, 0, 0, 0, 0, ky, 0, 0, 0, 0, 1, 0, -1 - minX * kx, -1 - minY * ky, 0, 1]); }; WindGL.prototype.draw = function draw () { @@ -238,7 +247,7 @@ WindGL.prototype.drawParticles = function drawParticles () { gl.uniform1f(program.u_particles_res, this.particleStateResolution); gl.uniform2f(program.u_wind_min, this.windData.uMin, this.windData.vMin); gl.uniform2f(program.u_wind_max, this.windData.uMax, this.windData.vMax); - gl.uniform4fv(program.u_mercator_bbox, this.mercBBox); + gl.uniformMatrix4fv(program.u_matrix, false, this.matrix); gl.uniform4fv(program.u_bbox, this.bbox); gl.drawArrays(gl.POINTS, 0, this._numParticles); @@ -295,7 +304,7 @@ function getColorRamp(colors) { } function mercY(y) { - var s = Math.sin(Math.PI / 2 - y * Math.PI); + var s = Math.sin(Math.PI * (0.5 - y)); var y2 = 1.0 - (Math.log((1.0 + s) / (1.0 - s)) / (2 * Math.PI) + 1.0) / 2.0; return y2 < 0 ? 0 : y2 > 1 ? 1 : y2; diff --git a/src/index.js b/src/index.js index 9775f842..6f484666 100644 --- a/src/index.js +++ b/src/index.js @@ -84,7 +84,16 @@ export default class WindGL { setBBox(bbox) { this.bbox = bbox; - this.mercBBox = [bbox[0], mercY(bbox[1]), bbox[2], mercY(bbox[3])]; + + const minX = bbox[0]; + const minY = mercY(bbox[1]); + const maxX = bbox[2]; + const maxY = mercY(bbox[3]); + + const kx = 2 / (maxX - minX); + const ky = 2 / (maxY - minY); + + this.matrix = new Float32Array([kx, 0, 0, 0, 0, ky, 0, 0, 0, 0, 1, 0, -1 - minX * kx, -1 - minY * ky, 0, 1]); } draw() { @@ -149,7 +158,7 @@ export default class WindGL { gl.uniform1f(program.u_particles_res, this.particleStateResolution); gl.uniform2f(program.u_wind_min, this.windData.uMin, this.windData.vMin); gl.uniform2f(program.u_wind_max, this.windData.uMax, this.windData.vMax); - gl.uniform4fv(program.u_mercator_bbox, this.mercBBox); + gl.uniformMatrix4fv(program.u_matrix, false, this.matrix); gl.uniform4fv(program.u_bbox, this.bbox); gl.drawArrays(gl.POINTS, 0, this._numParticles); @@ -205,7 +214,7 @@ function getColorRamp(colors) { } function mercY(y) { - const s = Math.sin(Math.PI / 2 - y * Math.PI); + const s = Math.sin(Math.PI * (0.5 - y)); const y2 = 1.0 - (Math.log((1.0 + s) / (1.0 - s)) / (2 * Math.PI) + 1.0) / 2.0; return y2 < 0 ? 0 : y2 > 1 ? 1 : y2; diff --git a/src/shaders/draw.vert.glsl b/src/shaders/draw.vert.glsl index 5c46c3e8..92b92dac 100644 --- a/src/shaders/draw.vert.glsl +++ b/src/shaders/draw.vert.glsl @@ -5,7 +5,7 @@ attribute float a_index; uniform sampler2D u_particles; uniform float u_particles_res; -uniform vec4 u_mercator_bbox; +uniform mat4 u_matrix; uniform vec4 u_bbox; varying vec2 v_particle_pos; @@ -26,14 +26,7 @@ void main() { // project the position with mercator projection float s = sin(radians(90.0 - v_particle_pos.y * 180.0)); float y = (degrees(log((1.0 + s) / (1.0 - s))) / 360.0 + 1.0) / 2.0; - float x = v_particle_pos.x; - - vec2 min = u_mercator_bbox.xy; - vec2 max = u_mercator_bbox.zw; gl_PointSize = 1.0; - gl_Position = vec4( - 2.0 * (x - min.x) / (max.x - min.x) - 1.0, - 2.0 * (y - min.y) / (max.y - min.y) - 1.0, - 0, 1); + gl_Position = u_matrix * vec4(v_particle_pos.x, y, 0, 1); } From 157e8572ca0b859e72639767c64e1ef1adc1a550 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Wed, 18 Jul 2018 18:47:27 +0300 Subject: [PATCH 08/10] add ability to pass projection matrix --- demo/index.js | 2 +- dist/wind-gl.js | 23 ++++++++++++++--------- src/index.js | 23 ++++++++++++++--------- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/demo/index.js b/demo/index.js index 310c964b..a9a5a161 100644 --- a/demo/index.js +++ b/demo/index.js @@ -60,7 +60,7 @@ updateRetina(); function updateZoom() { const halfSize = 0.5 / Math.pow(2, meta.zoom); - wind.setBBox([ + wind.setView([ 0.5 - halfSize, 0.5 - halfSize, 0.5 + halfSize, diff --git a/dist/wind-gl.js b/dist/wind-gl.js index 8a38473c..9da1fcb4 100644 --- a/dist/wind-gl.js +++ b/dist/wind-gl.js @@ -124,7 +124,7 @@ var WindGL = function WindGL(gl) { this.framebuffer = gl.createFramebuffer(); this.setColorRamp(defaultRampColors); - this.setBBox([0, 0, 1, 1]); + this.setView([0, 0, 1, 1]); this.resize(); }; @@ -171,18 +171,23 @@ WindGL.prototype.setWind = function setWind (windData) { this.windTexture = createTexture(this.gl, this.gl.LINEAR, windData.image); }; -WindGL.prototype.setBBox = function setBBox (bbox) { +WindGL.prototype.setView = function setView (bbox, matrix) { this.bbox = bbox; - var minX = bbox[0]; - var minY = mercY(bbox[1]); - var maxX = bbox[2]; - var maxY = mercY(bbox[3]); + if (matrix) { + this.matrix = matrix; - var kx = 2 / (maxX - minX); - var ky = 2 / (maxY - minY); + } else { + var minX = bbox[0]; + var minY = mercY(bbox[1]); + var maxX = bbox[2]; + var maxY = mercY(bbox[3]); + + var kx = 2 / (maxX - minX); + var ky = 2 / (maxY - minY); - this.matrix = new Float32Array([kx, 0, 0, 0, 0, ky, 0, 0, 0, 0, 1, 0, -1 - minX * kx, -1 - minY * ky, 0, 1]); + this.matrix = new Float32Array([kx, 0, 0, 0, 0, ky, 0, 0, 0, 0, 1, 0, -1 - minX * kx, -1 - minY * ky, 0, 1]); + } }; WindGL.prototype.draw = function draw () { diff --git a/src/index.js b/src/index.js index 6f484666..fc80eac1 100644 --- a/src/index.js +++ b/src/index.js @@ -37,7 +37,7 @@ export default class WindGL { this.framebuffer = gl.createFramebuffer(); this.setColorRamp(defaultRampColors); - this.setBBox([0, 0, 1, 1]); + this.setView([0, 0, 1, 1]); this.resize(); } @@ -82,18 +82,23 @@ export default class WindGL { this.windTexture = util.createTexture(this.gl, this.gl.LINEAR, windData.image); } - setBBox(bbox) { + setView(bbox, matrix) { this.bbox = bbox; - const minX = bbox[0]; - const minY = mercY(bbox[1]); - const maxX = bbox[2]; - const maxY = mercY(bbox[3]); + if (matrix) { + this.matrix = matrix; - const kx = 2 / (maxX - minX); - const ky = 2 / (maxY - minY); + } else { + const minX = bbox[0]; + const minY = mercY(bbox[1]); + const maxX = bbox[2]; + const maxY = mercY(bbox[3]); - this.matrix = new Float32Array([kx, 0, 0, 0, 0, ky, 0, 0, 0, 0, 1, 0, -1 - minX * kx, -1 - minY * ky, 0, 1]); + const kx = 2 / (maxX - minX); + const ky = 2 / (maxY - minY); + + this.matrix = new Float32Array([kx, 0, 0, 0, 0, ky, 0, 0, 0, 0, 1, 0, -1 - minX * kx, -1 - minY * ky, 0, 1]); + } } draw() { From 52ef895d84796ee967eeb14a24cc79b02f3e595b Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Mon, 30 Jul 2018 19:00:35 +0300 Subject: [PATCH 09/10] various fixes --- demo/index.js | 4 +--- dist/wind-gl.js | 15 ++++++++------- src/index.js | 13 +++++++------ src/shaders/draw.vert.glsl | 4 ++-- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/demo/index.js b/demo/index.js index a9a5a161..ba97abc5 100644 --- a/demo/index.js +++ b/demo/index.js @@ -8,7 +8,6 @@ canvas.height = canvas.clientHeight; const gl = canvas.getContext('webgl', {antialiasing: false}); const wind = window.wind = new WindGL(gl); -wind.numParticles = 65536; function frame() { if (wind.windData) { @@ -116,10 +115,9 @@ function drawCoastline() { function updateWind(name) { getJSON('wind/' + windFiles[name] + '.json', function (windData) { const windImage = new Image(); - windData.image = windImage; windImage.src = 'wind/' + windFiles[name] + '.png'; windImage.onload = function () { - wind.setWind(windData); + wind.setWind(windData, windImage); wind.resize(); }; }); diff --git a/dist/wind-gl.js b/dist/wind-gl.js index 9da1fcb4..56ef6ccb 100644 --- a/dist/wind-gl.js +++ b/dist/wind-gl.js @@ -87,7 +87,7 @@ function bindFramebuffer(gl, framebuffer, texture) { } } -var drawVert = "precision mediump float;\n\nattribute float a_index;\n\nuniform sampler2D u_particles;\nuniform float u_particles_res;\n\nuniform mat4 u_matrix;\nuniform vec4 u_bbox;\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 vec2 pos = vec2(\n color.r / 255.0 + color.b,\n color.g / 255.0 + color.a);\n\n // convert to global geographic position\n v_particle_pos = u_bbox.xy + pos * (u_bbox.zw - u_bbox.xy);\n\n // project the position with mercator projection\n float s = sin(radians(90.0 - v_particle_pos.y * 180.0));\n float y = (degrees(log((1.0 + s) / (1.0 - s))) / 360.0 + 1.0) / 2.0;\n\n gl_PointSize = 1.0;\n gl_Position = u_matrix * vec4(v_particle_pos.x, y, 0, 1);\n}\n"; +var drawVert = "precision mediump float;\n\nattribute float a_index;\n\nuniform sampler2D u_particles;\nuniform float u_particles_res;\n\nuniform mat4 u_matrix;\nuniform vec4 u_bbox;\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 vec2 pos = vec2(\n color.r / 255.0 + color.b,\n color.g / 255.0 + color.a);\n\n // convert to global geographic position\n v_particle_pos = u_bbox.xy + pos * (u_bbox.zw - u_bbox.xy);\n\n // project the position with mercator projection\n float s = sin(radians(v_particle_pos.y * 180.0 - 90.0));\n float y = 1.0 - (degrees(log((1.0 + s) / (1.0 - s))) / 360.0 + 1.0) / 2.0;\n\n gl_PointSize = 1.0;\n gl_Position = u_matrix * vec4(v_particle_pos.x, y, 0, 1);\n}\n"; 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"; @@ -115,6 +115,7 @@ var WindGL = function WindGL(gl) { this.speedFactor = 0.25; // how fast the particles move this.dropRate = 0.003; // how often the particles move to a random place this.dropRateBump = 0.01; // drop rate increase relative to individual particle speed + this.numParticles = 65536; this.drawProgram = createProgram(gl, drawVert, drawFrag); this.screenProgram = createProgram(gl, quadVert, screenFrag); @@ -166,9 +167,9 @@ prototypeAccessors.numParticles.get = function () { return this._numParticles; }; -WindGL.prototype.setWind = function setWind (windData) { - this.windData = windData; - this.windTexture = createTexture(this.gl, this.gl.LINEAR, windData.image); +WindGL.prototype.setWind = function setWind (data, image) { + this.windData = data; + this.windTexture = createTexture(this.gl, this.gl.LINEAR, image); }; WindGL.prototype.setView = function setView (bbox, matrix) { @@ -179,9 +180,9 @@ WindGL.prototype.setView = function setView (bbox, matrix) { } else { var minX = bbox[0]; - var minY = mercY(bbox[1]); + var minY = mercY(bbox[3]); var maxX = bbox[2]; - var maxY = mercY(bbox[3]); + var maxY = mercY(bbox[1]); var kx = 2 / (maxX - minX); var ky = 2 / (maxY - minY); @@ -309,7 +310,7 @@ function getColorRamp(colors) { } function mercY(y) { - var s = Math.sin(Math.PI * (0.5 - y)); + var s = Math.sin(Math.PI * (y - 0.5)); var y2 = 1.0 - (Math.log((1.0 + s) / (1.0 - s)) / (2 * Math.PI) + 1.0) / 2.0; return y2 < 0 ? 0 : y2 > 1 ? 1 : y2; diff --git a/src/index.js b/src/index.js index fc80eac1..91b886ea 100644 --- a/src/index.js +++ b/src/index.js @@ -28,6 +28,7 @@ export default class WindGL { this.speedFactor = 0.25; // how fast the particles move this.dropRate = 0.003; // how often the particles move to a random place this.dropRateBump = 0.01; // drop rate increase relative to individual particle speed + this.numParticles = 65536; this.drawProgram = util.createProgram(gl, drawVert, drawFrag); this.screenProgram = util.createProgram(gl, quadVert, screenFrag); @@ -77,9 +78,9 @@ export default class WindGL { return this._numParticles; } - setWind(windData) { - this.windData = windData; - this.windTexture = util.createTexture(this.gl, this.gl.LINEAR, windData.image); + setWind(data, image) { + this.windData = data; + this.windTexture = util.createTexture(this.gl, this.gl.LINEAR, image); } setView(bbox, matrix) { @@ -90,9 +91,9 @@ export default class WindGL { } else { const minX = bbox[0]; - const minY = mercY(bbox[1]); + const minY = mercY(bbox[3]); const maxX = bbox[2]; - const maxY = mercY(bbox[3]); + const maxY = mercY(bbox[1]); const kx = 2 / (maxX - minX); const ky = 2 / (maxY - minY); @@ -219,7 +220,7 @@ function getColorRamp(colors) { } function mercY(y) { - const s = Math.sin(Math.PI * (0.5 - y)); + const s = Math.sin(Math.PI * (y - 0.5)); const y2 = 1.0 - (Math.log((1.0 + s) / (1.0 - s)) / (2 * Math.PI) + 1.0) / 2.0; return y2 < 0 ? 0 : y2 > 1 ? 1 : y2; diff --git a/src/shaders/draw.vert.glsl b/src/shaders/draw.vert.glsl index 92b92dac..41e56f1e 100644 --- a/src/shaders/draw.vert.glsl +++ b/src/shaders/draw.vert.glsl @@ -24,8 +24,8 @@ void main() { v_particle_pos = u_bbox.xy + pos * (u_bbox.zw - u_bbox.xy); // project the position with mercator projection - float s = sin(radians(90.0 - v_particle_pos.y * 180.0)); - float y = (degrees(log((1.0 + s) / (1.0 - s))) / 360.0 + 1.0) / 2.0; + float s = sin(radians(v_particle_pos.y * 180.0 - 90.0)); + float y = 1.0 - (degrees(log((1.0 + s) / (1.0 - s))) / 360.0 + 1.0) / 2.0; gl_PointSize = 1.0; gl_Position = u_matrix * vec4(v_particle_pos.x, y, 0, 1); From c1fa1316f7c4ba09e90b57c0cb4e785c5eaf62a0 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Mon, 30 Jul 2018 19:09:38 +0300 Subject: [PATCH 10/10] fix projection in demo --- demo/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demo/index.js b/demo/index.js index ba97abc5..216d112d 100644 --- a/demo/index.js +++ b/demo/index.js @@ -101,12 +101,12 @@ function drawCoastline() { const x = (line[j][0] + 180) / 360; const y = latY(line[j][1]); const minX = wind.bbox[0]; - const minY = latY(90 - 180 * wind.bbox[1]); + const minY = latY(180 * wind.bbox[3] - 90); const maxX = wind.bbox[2]; - const maxY = latY(90 - 180 * wind.bbox[3]); + const maxY = latY(180 * wind.bbox[1] - 90); ctx[j ? 'lineTo' : 'moveTo']( (x - minX) / (maxX - minX) * canvas.width, - (y - (1 - maxY)) / (maxY - minY) * canvas.height); + (y - minY) / (maxY - minY) * canvas.height); } } ctx.stroke();