【问题标题】:Calculating particle size and distance between them in Javascript在Javascript中计算粒子大小和它们之间的距离
【发布时间】:2016-05-15 14:40:53
【问题描述】:

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

【问题讨论】:

  • 你的问题暗示了它,但你的粒子大小都一样,是吗?
  • @markE 是的,它们的大小都一样。

标签: javascript math canvas particles


【解决方案1】:

数学部分的解决方案可以在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>

【讨论】:

    【解决方案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 就适合屏幕了。

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

    质数永远不适合

    任何作为素数的计数都没有解,不可能将素数放入 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()

    【讨论】:

    • 感谢您的帮助!我在您的帖子中发现了一些有用的东西,并且现在正在运行一个非常好的原型。
    猜你喜欢
    • 1970-01-01
    • 2017-08-30
    • 2018-06-05
    • 2014-01-28
    • 2021-12-02
    • 1970-01-01
    • 2018-05-21
    • 1970-01-01
    • 2019-09-25
    相关资源
    最近更新 更多