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

Calculating particle size and distance between them in Javascript

我正在尝试显示带有粒子的图像。它有效,但粒子的数量取决于一个变量 (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);
        }
    }
}

稍后在代码中,粒子以给定的大小渲染。

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

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

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

要找到 x (nx) 的点数,我们可以使用公式:

那么y的点数(ny):

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>

最适合保养方面。

这类包装问题在 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 就适合屏幕。

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

质数永远放不下

凡是质数的计数都没有解,x行y列不可能填入质数。这是因为结果计数是 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()