2d metaballs w javascript
W artykule stworzymy w html5 canvas efekt metaballs. Podstawową koncepcją jest wykorzystanie gradientu radialnego i ustawienie progu alfa, który będzie filtrowany. Na przykład każdy piksel dla alfa niższej niż 150 zostanie ustawiony na alfa 0. Daje to efekt metaballi, ponieważ podczas gdy gradienty pokrywają obszary, które są normalnie ukryte mają obecnie ich wartości alfa dodane co pozwala im znaleźć się powyżej progu.
<html> <head> </head> <body style="background:black;"> <canvas id="canvas"></canvas> </body> </html>Na początku stórzmy tablicę punktów zawierającą ich położenie, prędkości i wielkości.
for(var i = 0; i < 50; i++){ var x = Math.random()*width, y = Math.random()*height, vx = (Math.random()*8)-4, vy = (Math.random()*8)-4, size = Math.floor(Math.random()*60)+60; points.push({x:x,y:y,vx:vx,vy:vy, size:size}); };Następnie rysujemy punkty na tymczasowym canvasie.
var grad = tempCtx.createRadialGradient(point.x, point.y, 1, point.x, point.y, point.size); tempCtx.beginPath(); grad.addColorStop(0, 'rgba(' + colors.r +',' + colors.g + ',' + colors.b + ',1)'); grad.addColorStop(1, 'rgba(' + colors.r +',' + colors.g + ',' + colors.b + ',0)'); tempCtx.fillStyle = grad; tempCtx.arc(point.x, point.y, point.size, 0, Math.PI*2); tempCtx.fill();Na koniec czytamy dane pikseli, filtrujemy alfa w oparciu o próg i umieszczamy dane z powrotem w canvasie.
function metaballs(){ var imageData = tempCtx.getImageData(0,0,width,height), pix = imageData.data; for (var i = 0, n = pix.length; i <n; i += 4) { // Checks threshold if(pix[i+3]<threshold){ pix[i+3]=0; } } ctx.putImageData(imageData, 0, 0); }Pełny kod javascript:
var canvas = document.getElementById("canvas"), ctx = canvas.getContext("2d"), tempCanvas = document.createElement("canvas"), tempCtx = tempCanvas.getContext("2d"), width = 512, height = 512, threshold = 210, colors = {r:255,g:0,b:0}, cycle = 0, points = []; canvas.width = tempCanvas.width = width; canvas.height= tempCanvas.height= height; for(var i = 0; i < 50; i++){ var x = Math.random()*width, y = Math.random()*height, vx = (Math.random()*8)-4, vy = (Math.random()*8)-4, size = Math.floor(Math.random()*60)+60; points.push({x:x,y:y,vx:vx,vy:vy, size:size}); }; function update(){ var len = points.length; tempCtx.clearRect(0,0,width,height); while(len--){ var point = points[len]; point.x+=point.vx; point.y+=point.vy; if(point.x > width+point.size){ point.x = 0-point.size; } if(point.x < 0-point.size){ point.x = width+point.size; } if(point.y > height+point.size){ point.y = 0-point.size; } if(point.y < 0-point.size){ point.y = height+point.size; } tempCtx.beginPath(); var grad = tempCtx.createRadialGradient(point.x, point.y, 1, point.x, point.y, point.size); grad.addColorStop(0, 'rgba(' + colors.r +',' + colors.g + ',' + colors.b + ',1)'); grad.addColorStop(1, 'rgba(' + colors.r +',' + colors.g + ',' + colors.b + ',0)'); tempCtx.fillStyle = grad; tempCtx.arc(point.x, point.y, point.size, 0, Math.PI*2); tempCtx.fill(); } metaballs(); colorCycle(); setTimeout(update,10); } function colorCycle(){ cycle+=0.1; if(cycle>100){ cycle = 0; } colors.r = ~~(Math.sin(.3*cycle + 0) * 127+ 128); colors.g = ~~(Math.sin(.3*cycle + 2) * 127+ 128); colors.b = ~~( Math.sin(.3*cycle + 4) * 127+ 128); } function metaballs(){ var imageData = tempCtx.getImageData(0,0,width,height), pix = imageData.data; for (var i = 0, n = pix.length; i <n; i += 4) { if(pix[i+3]<threshold){ pix[i+3]/=6; if(pix[i+3]>threshold/4){ pix[i+3]=0; } } } ctx.putImageData(imageData, 0, 0); } update();