我正在尝试显示带有粒子的图像。它可以工作,但粒子的数量取决于一个变量(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个)。
答案 0 :(得分:2)
数学部分的解决方案可以在this answer中找到。
要查找x的点数( n x ),我们可以使用该公式:
然后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()