在Javascript中计算粒子大小和它们之间的距离

时间:2016-05-15 14:40:53

标签: javascript math canvas particles

我正在尝试显示带有粒子的图像。它可以工作,但粒子的数量取决于一个变量(numberOfParticles),它可以在0到3000之间。在任何值上,图像应该以给定数量的粒子以尽可能最好的方式渲染。有一个嵌套的for循环遍历图像数据(高度和宽度)并创建像这样的粒子。

for (var y = 0; y < data.height; y+=averageDistance) {
    for (var x = 0; x < data.width; x+=averageDistance) {
        if (particles.length < numberOfParticles){
            var particle = {
                x0: x,
                y0: y,
                color: "rgb("+data.data[(y * 4 * data.width)+ (x * 4)]+","+data.data[(y * 4 * data.width)+ (x * 4) +1]+","+data.data[(y * 4 * data.width)+ (x * 4) +2]+")"
            };
            particles.push(particle);
        }
    }
}

稍后在代码中,粒子会以给定的大小呈现。

我的问题是,如何计算粒子应具有的大小以及它们之间的距离?

我已经尝试计算'平均距离',计算未被粒子覆盖的像素数量并将其除以粒子数量,但我无法使其正常工作。在变量numberOfParticles的某个值上总有剩余的空间(因此底部没有被填充)或剩余的粒子(因此只显示了40个粒子,而不是50个)。

2 个答案:

答案 0 :(得分:2)

数学部分的解决方案可以在this answer中找到。

要查找x的点数( n x ),我们可以使用该公式:

nx

然后y的点数( n y ):

ny = n / nx

在JavaScript代码中:

nx = Math.sqrt((w / h) * n + Math.pow(w - h, 2) / (4 * Math.pow(h, 2))) - (w - h) / (2 * h);
ny = n / nx;

使用数字nx和ny,我们可以计算x和y的增量:

dx = w / nx;
dy = h / ny;

实施例

var ctx = c.getContext("2d"), n, w, h, nx, ny, dx, dy, x, y;

// define values
n = 1600;
w = c.width - 1;   // make inclusive
h = c.height - 1;

// plug values into formula
nx = Math.sqrt((w / h) * n + Math.pow(w - h, 2) / (4 * Math.pow(h, 2))) - (w - h) / (2 * h);
ny = n / nx;

// calculate deltas
dx = w / nx;
dy = h / ny;

// render proof-of-concept
for(y = 0; y < h; y += dy) {
  for(x = 0; x < w; x += dx) {
    ctx.fillStyle = "hsl(" + (360*Math.random()) + ",50%,50%";
    ctx.fillRect(x, y, dx-1, dy-1);
  }
}

o.innerHTML = "Points to place: " + n + "<br>" + 
  "<strong>n<sub>x</sub></strong>: " + nx.toFixed(2) + "<br>" + 
  "<strong>n<sub>y</sub></strong>: " + ny.toFixed(2) + "<br>" +
  "ΔX: " + dy.toFixed(2) + "<br>" +
  "ΔY: " + dy.toFixed(2) + "<br>" +
  "Total (nx × ny): " + (nx * ny).toFixed(0);
<canvas id=c width=600 height=400></canvas>
<br><output id=o></output>

答案 1 :(得分:2)

最适合维护方面。

这些类型的包装问题在CG中出现了很多。

要使用较小的方框填充方框,您只需找到所需计数的平方根。

var c = 100; // number of boxes
var w = 10; // box width
var h = 10; // box height
var sW = 1000; // screen width
var sH = 1000; // screen height

拟合为sqrt(c) = sqrt(100) = 10。这是10横跨,将屏幕宽度除以计数sW / 10 = 100以获得框的宽度,以便100适合屏幕。

这适用于整数平方的计数,如果没有平方根,我们可以分解以找到更好的解。但即便如此,并不总是有适合的解决方案。

素数永远不适合

作为素数的任何计数都没有解决方案,不可能将素数拟合到y行的x行中。这是因为结果计数是x * y,这意味着它不是素数。

<强>妥协

最后你需要妥协。您可以控制计数只允许使用解决方案计数(不是素数),或者您接受会出现一些错误并允许解决方案超出范围。

此解决方案适用但允许计数更改,屏幕外的区域保持最小,同时仍保持框长宽比。

宽度计数是sqrt((c / sA)* bA)其中sA是屏幕方面,bA是方框。

var c = 100; // number of boxes
var w = 10; // box width
var h = 10; // box height
var sW = 1000; // screen width
var sH = 1000; // screen height
var sA = sH / sW; // screen aspect
var bA = h / w; // box aspect
var wCount = Math.sqrt((c / sA) * bA); // get nearest fit for width
wCount = Math.round(wCount); // round to an integers. This forces the width to fit

现在你有了wCount,只需将屏幕宽度除以该值即可获得框渲染宽度,并将框渲染宽度乘以框方面以获得框渲染高度。

var rW = w / wCount; // the size of the box to render
var rH = rW * bA; // the height is the width time aspect

有时你会得到一个完美的解决方案,但大多数时候你都不会。由于四舍五入,实际计数将高于请求的计数。但是,对于所需的盒子和屏幕方面,适合性将是最佳的。

<强>演示

使用随机框大小,随机屏幕大小每3秒更新一次(用绿色框覆盖以显示顶部和底部多余部分)文本显示列数和行数,请求的数量和实际数量。

var canvas,ctx;
function createCanvas(){
    canvas = document.createElement("canvas"); 
    canvas.style.position = "absolute";
    canvas.style.left     = "0px";
    canvas.style.top      = "0px";
    canvas.style.zIndex   = 1000;
    document.body.appendChild(canvas);
}

function resize(){
    if(canvas === undefined){
        createCanvas();
    }
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight; 
    ctx = canvas.getContext("2d"); 
}
window.addEventListener("resize",resize);
resize();


var w = 10;
var h = 20;
var c = 100;
var sW = canvas.width*0.7;
var sH = canvas.height*0.5;
var sA = sH /sW;
var bA = h / w;
function getParticleWidth(particleImageX,particleImageY,w,h,particleCount){
    var a = particleImageY / particleImageX; // get particle aspect
    var c = particleCount; // same with this
    var b = h/w    
    
    return Math.sqrt((c/b)*a)
}

function drawTheParticles(){
    var x,y;
    pCount = Math.round(Math.sqrt((c/sA)*bA));
    var pWidth = sW / pCount;
    var pHeight = pWidth * bA;
    ctx.lineWidth = 1;
    ctx.strokeStyle = "black";
    ctx.fillStyle = "white";
    ctx.clearRect(0,0,canvas.width,canvas.height); // clear last result
    var cc = 0;
    var sx = (canvas.width-sW)/2;
    var sy = (canvas.height-sH)/2;
    var hc = ((Math.ceil(sH / pHeight)*pHeight)-sH)/2;
    var wc =0;
    for(y = 0; y < sH; y += pHeight){
        for(x = 0; x < sW-(pWidth/2); x += pWidth){
            ctx.fillRect(x + 1+sx-wc, y + 1+sy-hc, pWidth - 2, pHeight - 2);
            ctx.strokeRect(x + 1+sx-wc, y + 1+sy-hc, pWidth - 2, pHeight - 2);
            cc ++;
        }
    }

    ctx.strokeStyle = "black";
    ctx.fillStyle = "rgba(50,200,70,0.25)";
    ctx.fillRect(sx, sy, sW, sH);
    ctx.strokeRect(sx, sy, sW, sH);
    
    // show the details
    ctx.font = "20px arial";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    var str = ""+pCount+" by "+Math.ceil(sH / pHeight)+" need " + c + " got "+cc;
    var width = ctx.measureText(str).width;
    ctx.lineWidth = 2;
    // clear an area for text
    // with a shadow and not the stupid built in shadow
    ctx.fillStyle = "rgba(0,0,0,0.4)";
    ctx.fillRect((canvas.width / 2) - (width + 8) / 2+6, (canvas.height / 2) - 14+6, width + 8, 28  );
    
    ctx.fillStyle = "#CCC";
    ctx.fillRect((canvas.width / 2) - (width + 8) / 2, (canvas.height / 2) - 14, width + 8, 28  );
    ctx.fillRect((canvas.width / 2) - (width + 8) / 2, (canvas.height / 2) - 14, width + 8, 28  );
    // now draw the text with a bit of an outline
    ctx.fillStyle = "blue"
    ctx.strokeStyle = "white";
    ctx.lineJoin = "round";
    ctx.strokeText(str, canvas.width/2, canvas.height / 2);
    ctx.fillText(str, canvas.width/2, canvas.height / 2);
    
    // And set up to do it all again in 3 seconds
    // get random particle image size
    w = Math.floor(Math.random() * 100 + 10);
    h = Math.floor(Math.random() * 100 + 10);
    // get random particle count
    c = Math.floor(Math.random() * 500 + 10);
    // get random screen width height
    sW = canvas.height*(Math.random()*0.4 + 0.6);
    sH = canvas.height*(Math.random()*0.6 + 0.4);
    // recaculate aspects
    sA = sH /sW;
    bA = h / w;    
    
    // redo it in 3 seconds

    setTimeout(drawTheParticles,3000)
}


drawTheParticles()