Skip to content

Instantly share code, notes, and snippets.

@mtaptich
Last active March 8, 2016 03:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mtaptich/4e2371a05ec1b633b544 to your computer and use it in GitHub Desktop.
Save mtaptich/4e2371a05ec1b633b544 to your computer and use it in GitHub Desktop.
Canvas + Interpolate

Particles flowing along a gradient.

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
<script src="./snake.js"></script>
<script src="//d3js.org/d3.v3.min.js"></script>
</head>
<body class="">
<div id="mapCanvas" style="height:100%;">
</div>
</body>
<script>
var width = 1100, height = 590, n_tiles = 18;
var canvas = d3.select("body").append("canvas")
.attr("id", 'mapCanvas_root')
.attr("width", width)
.attr("height", height);
params = {
canvas: canvas.node(),
data: {
headers: {
dx: width / n_tiles,
dy: height / n_tiles,
nx: n_tiles,
ny: n_tiles
},
u: d3.range(Math.pow(n_tiles,2)).map(function(){ return Math.random() < 0.5 ? -1 : 1}),
v: d3.range(Math.pow(n_tiles,2)).map(function(d){ return d/n_tiles < n_tiles/2 ? -1 : 1})
},
}
snake = new Snake(params);
snake.start(
[[0,0],[width, height]],
width,
height
)
</script>
</html>
var Snake = function( params ){
var PARTICLE_MULTIPLIER = 1/500,
MAX_INTENSITY =1,
MAX_PARTICLE_AGE = 400,
PARTICLE_LINE_WIDTH = 2;
var bilinearInterpolateVector = function(x, y, g00, g10, g01, g11) {
var rx = (1 - x);
var ry = (1 - y);
var a = rx * ry, b = x * ry, c = rx * y, d = x * y;
var u = g00[0] * a + g10[0] * b + g01[0] * c + g11[0] * d;
var v = g00[1] * a + g10[1] * b + g01[1] * c + g11[1] * d;
return [u, v, Math.sqrt(u * u + v * v)];
};
var createBuilder = function(data){
return {
header: data.headers,
get: function(i){
return [ data.u[i], data.v[i] ]
},
interpolate: bilinearInterpolateVector
}
}
var buildGrid = function(data, callback){
var builder = createBuilder(data)
var header = builder.header;
var Δx = header.dx,
Δy = header.dy
ni = header.nx,
nj = header.ny;
grid = [], p =0
for (var i = 0; i < ni; i++) {
row = []
for (var j = 0; j < nj; j++, p++) {
row[j] = builder.get(p)
};
grid[i] = row;
};
var interpolate = function (x, y) {
var fi = Math.floor(x / Δx),
fj = Math.floor(y/ Δy),
dx = 0.3,
dy = 0.3
try{
return builder.interpolate(dx, dy, grid[fi][fj], grid[fi+1][fj],grid[fi][fj+1],grid[fi+1][fj+1])
}catch(e){
return null
}
}
callback({
data: {
s: grid
},
interpolate: interpolate
})
}
var animate = function(grid, bounds){
console.log('Let us animate')
var isValue = function(x) {
return x !== null && x !== undefined;
}
// Colors
function asColorStyle(r, g, b, a) { return "rgba(" + 243 + ", " + 243 + ", " + 238 + ", " + a + ")" }
function hexToR(h) {return parseInt((cutHex(h)).substring(0,2),16)}
function hexToG(h) {return parseInt((cutHex(h)).substring(2,4),16)}
function hexToB(h) {return parseInt((cutHex(h)).substring(4,6),16)}
function cutHex(h) {return (h.charAt(0)=="#") ? h.substring(1,7):h}
function IntensityColorScale(maxWind) {
result = [
"rgba(" + hexToR('#00ffff') + ", " + hexToG('#00ffff') + ", " + hexToB('#00ffff') + ", " + 0.5 + ")",
"rgba(" + hexToR('#64f0ff') + ", " + hexToG('#64f0ff') + ", " + hexToB('#64f0ff') + ", " + 0.5 + ")",
"rgba(" + hexToR('#87e1ff') + ", " + hexToG('#87e1ff') + ", " + hexToB('#87e1ff') + ", " + 0.5 + ")",
"rgba(" + hexToR('#a0d0ff') + ", " + hexToG('#a0d0ff') + ", " + hexToB('#a0d0ff') + ", " + 0.5 + ")",
"rgba(" + hexToR('#b5c0ff') + ", " + hexToG('#b5c0ff') + ", " + hexToB('#b5c0ff') + ", " + 0.5 + ")",
"rgba(" + hexToR('#c6adff') + ", " + hexToG('#c6adff') + ", " + hexToB('#c6adff') + ", " + 0.2 + ")",
"rgba(" + hexToR('#d49bff') + ", " + hexToG('#d49bff') + ", " + hexToB('#d49bff') + ", " + 0.2 + ")",
"rgba(" + hexToR('#e185ff') + ", " + hexToG('#e185ff') + ", " + hexToB('#e185ff') + ", " + 0.2 + ")",
"rgba(" + hexToR('#ec6dff') + ", " + hexToG('#ec6dff') + ", " + hexToB('#ec6dff') + ", " + 0.2 + ")",
"rgba(" + hexToR('#ff1edb') + ", " + hexToG('#ff1edb') + ", " + hexToB('#ff1edb') + ", " + 0.2 + ")"
]
result.indexFor = function(m) { // map wind speed to a style
return Math.floor(Math.min(m, maxWind) / maxWind * (result.length - 1));
};
return result;
}
var colorStyles = IntensityColorScale(MAX_INTENSITY);
var fadeFillStyle = "rgba(0, 0, 0, 0.99)";
var particleCount = Math.round(bounds[1][0] * bounds[1][1] * PARTICLE_MULTIPLIER);
var buckets = colorStyles.map(function() { return []; });
var particles = [];
for (var i = 0; i < particleCount; i++) {
var _x = Math.random()*bounds[1][0],
_y = Math.random()*bounds[1][1],
__data__ = grid.interpolate(_x,_y);
if (isValue(__data__)){
particles.push({
x: __data__[0],
y: __data__[1],
m: __data__[2],
age: Math.floor(Math.random() * MAX_PARTICLE_AGE),
xt: _x - __data__[0],
xy: _y - __data__[1],
sx: _x,
sy: _y
});
}
}
function evolve() {
buckets.forEach(function(bucket) { bucket.length = 0; });
particles.forEach(function(particle) {
if (particle.age > MAX_PARTICLE_AGE) {
particle.x = particle.sx
particle.y = particle.sy
particle.age=0
}
var __data__ = grid.interpolate(particle.x,particle.y);
if (isValue(__data__)){
particle.xt = particle.x - __data__[0];
particle.yt = particle.y - __data__[1] ;
particle.m =__data__[2]
particle.x += __data__[0]
particle.y += __data__[1]
buckets[colorStyles.indexFor(Math.abs(particle.m))].push(particle);
}
particle.age += 1;
});
}
var g = params.canvas.getContext("2d");
g.lineWidth = PARTICLE_LINE_WIDTH;
g.fillStyle = fadeFillStyle;
function draw() {
// Fade existing particle trails.
var prev = g.globalCompositeOperation;
g.globalCompositeOperation = "destination-in";
g.fillRect(bounds[0][0], bounds[0][1], bounds[1][0], bounds[1][1]);
g.globalCompositeOperation = prev;
// Draw new particle trails.
buckets.forEach(function(bucket, i) {
if (bucket.length > 0) {
g.beginPath();
g.strokeStyle = colorStyles[i];
bucket.forEach(function(particle, i) {
var _m = particle.m
g.moveTo(particle.x, particle.y);
g.lineTo(particle.xt, particle.yt);
});
g.stroke();
}
});
}
d3.timer(function () {
evolve()
draw()
})
}
var start = function(bounds, width, height){
buildGrid(params.data, function(grid){
animate(grid, bounds)
})
}
var snake = {
start: start
};
return snake
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment